Hide Table of Contents
View Manage results from multiple queries sample in sandbox
Manage results from multiple queries

Description

A query task is the ideal choice for getting a small number of features from a single layer in a map service. Occasionally, it is necessary to query multiple layers simultaneously. If the requirement is to query all layers in a map service, then an identify task should be used. Otherwise, it can be useful to use multiple query tasks to query different layers. The potential hangup with this approach is how to know when all queries have finished. The answer is to use a dojo/promise/all. Using an instance of dojo/promise/all is an easy way to be notified when multiple promises or deferreds finish. dojo/promise/all is created by passing an array of promises or deferred. This is trivial when using queryTasks since the return value of a queryTask is a deferred. Once all the deferreds are resolved, the results are passed to a callback. Inside the callback, all results can be processed.

The code in the sample shows an exmaple of how this would work: initially, two queries and two query task objects are created. When the map is clicked, both query tasks are executed and an instance of dojo/promise/all is created using them. When both queries finish, all results are passed to the callback and the features returned are added to the map.

This sample also shows how to build an extent from a map click point. Specifically, the first part of the executeQueries function shows this:

// create an extent from the mapPoint that was clicked
// this is used to return features within 3 pixels of the click point
point = e.mapPoint;
pxWidth = app.map.extent.getWidth() / app.map.width;
padding = 3 * pxWidth;
qGeom = new esri.geometry.Extent({
  "xmin": point.x - padding,
  "ymin": point.y - padding,
  "xmax": point.x + padding,
  "ymax": point.y + padding,
  "spatialReference": point.spatialReference
});

First, the width of a pixel is calculated. Then, the pixel width is multiplied by three. This results in a tolerance that can be added or subtracted to the original point to get xmin/xmax and ymin/ymax values to build an extent. Once the extent is created, it is set as each query object's geometry.

Code

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
    <title>Manage results from multiple queries</title>
    <link rel="stylesheet" href="https://js.arcgis.com/3.22/dijit/themes/tundra/tundra.css">
    <link rel="stylesheet" href="https://js.arcgis.com/3.22/esri/css/esri.css">
    <style>
      html, body { height: 100%; width: 100%; margin: 0; padding: 0; }
      #map{ margin: 0; padding: 0; }
      #controls {
        position: absolute;
        height: 80px;
        font-family: arial;
        bottom: 10px;
        margin: 5px;
        padding: 5px;
        z-index: 40;
        background: #fff;
        color: #444;
        width: 440px;
        left: 10px;
        -moz-box-shadow: 0 0 5px #888;
        -webkit-box-shadow: 0 0 5px #888;
        box-shadow: 0 0 5px #888;
      }
      h3 { margin: 0 0 5px 0; border-bottom: 1px solid #444; }
      .label { display: inline-block; width: 140px; }
    </style>

    <script src="https://js.arcgis.com/3.22/"></script>
    <script>
      var app = {};

      require([
        "dojo/parser", "dojo/promise/all", "dojo/_base/connect", 
        "esri/Color", "dojo/_base/array", "dojo/dom",
        "esri/config", "esri/map", "esri/geometry/Extent",
        "esri/symbols/SimpleFillSymbol", "esri/layers/ArcGISDynamicMapServiceLayer",
        "esri/tasks/query", "esri/tasks/QueryTask",
        "dijit/layout/BorderContainer", "dijit/layout/ContentPane",
        "dojo/domReady!"
      ], function(
        parser, all, connect, 
        Color, arrayUtils, dom,
        esriConfig, Map, Extent,
        SimpleFillSymbol, ArcGISDynamicMapServiceLayer,
        Query, QueryTask
      ) {
        // create layout dijits
        parser.parse();
        // specify proxy for request with URL lengths > 2k characters
        esriConfig.defaults.io.proxyUrl = "/proxy/";

        app.map = new Map("map"); 
        var basemap = new ArcGISDynamicMapServiceLayer("https://sampleserver3.arcgisonline.com/ArcGIS/rest/services/BloomfieldHillsMichigan/Parcels/MapServer");
        app.map.addLayer(basemap);

        // query task and query for parcels
        app.qtParcels = new QueryTask("https://sampleserver3.arcgisonline.com/ArcGIS/rest/services/BloomfieldHillsMichigan/Parcels/MapServer/2");
        app.qParcels = new Query();
        // query task and query for landuse 
        app.qtBuildings = new QueryTask("https://sampleserver3.arcgisonline.com/ArcGIS/rest/services/BloomfieldHillsMichigan/Parcels/MapServer/0");
        app.qBuildings = new Query();

        app.qParcels.returnGeometry = app.qBuildings.returnGeometry = true;
        app.qParcels.outFields = app.qBuildings.outFields = ["*"];

        app.map.on("click", executeQueries);

        function executeQueries(e) {
          var parcels, buildings, promises, 
            qGeom, point, pxWidth, padding;

          // create an extent from the mapPoint that was clicked
          // this is used to return features within 3 pixels of the click point
          point = e.mapPoint;
          pxWidth = app.map.extent.getWidth() / app.map.width;
          padding = 3 * pxWidth;
          qGeom = new Extent({
            "xmin": point.x - padding,
            "ymin": point.y - padding,
            "xmax": point.x + padding,
            "ymax": point.y + padding,
            "spatialReference": point.spatialReference
          });

          // use the extent for the query geometry
          app.qParcels.geometry = app.qBuildings.geometry = qGeom;
          
          parcels = app.qtParcels.execute(app.qParcels);
          buildings = app.qtBuildings.execute(app.qBuildings);
          console.log("deferreds: ", parcels, buildings);
          promises = all([parcels, buildings]);
          promises.then(handleQueryResults);
          console.log("created d list");
        }

        function handleQueryResults(results) {
          console.log("queries finished: ", results);
          var parcels, buildings;

          // make sure both queries finished successfully
          if ( ! results[0].hasOwnProperty("features") ) {
            console.log("Parcels query failed.");
          }
          if ( ! results[1].hasOwnProperty("features") ) {
            console.log("Buildings query failed.");
          }

          // results from deferred lists are returned in the order they were created
          // so parcel results are first in the array and buildings results are second
          parcels = results[0].features;
          buildings = results[1].features;

          app.map.graphics.clear();

          // add the results to the map
          arrayUtils.forEach(parcels, function(feat) {
            feat.setSymbol(new SimpleFillSymbol());
            app.map.graphics.add(feat);
          });
          arrayUtils.forEach(buildings, function(feat) {
            feat.setSymbol(
              new SimpleFillSymbol()
                .setColor(new Color([255, 0, 255, 0.5]))
                .setOutline(null)
            );
            app.map.graphics.add(feat);
          });
          dom.byId("results").innerHTML = "Number of parcels:  " +  parcels.length + 
            "<br />Number of buildings:  " + buildings.length;
        }
      });
    </script>
  </head>
  
  <body class="tundra">
    <div data-dojo-type="dijit.layout.BorderContainer" 
         data-dojo-props="design:'headline',gutters:false" 
         style="width: 100%; height: 100%; margin: 0;">
      <div id="map" 
           data-dojo-type="dijit.layout.ContentPane" 
           data-dojo-props="region:'center'">
        
        <div id="controls">
          <h3>Multiple Query Tasks</h3>
          <div id="results">Click the map to query multiple layers.</div>
        </div>

      </div>
    </div>
  </body>
</html>