11. Working with Spatial Data with ArcGIS JS API#

In this section, we’ll dive deeper into working with spatial data using the ArcGIS JavaScript API.

We’ll cover spatial queries, filtering, and understanding the data structure of web maps and feature layers on ArcGIS Online.

11.1. Performing Spatial Queries Using SQL#

In this section, we’ll explore how to perform spatial queries using SQL within a web map using the ArcGIS JavaScript API.

  • This involves setting up a feature layer, defining SQL where clauses for filtering data, and executing queries to display results on the map.

11.1.1. Feature Layer and Popup Template#

  • Define the feature layer with a popup template to display information about each feature.

    const trailsUrl =
      "https://services2.arcgis.com/VNo0ht0YPXJoI4oE/arcgis/rest/services/Trials/FeatureServer/0";
    
    const popupTemplate = {
      title: "Object ID: {OBJECTID}",
      fieldInfos: [
        {
          fieldName: "trailName",
          label: "trailName",
          format: { places: 0, digitSeperator: false },
        },
        {
          fieldName: "Shape__Length",
          label: "Trail_Length",
          format: { places: 0, digitSeperator: true },
        },
        {
          fieldName: "manageOrg",
          label: "manageOrg",
          format: { places: 0, digitSeperator: false },
        },
        {
          fieldName: "trailStatus",
          label: "trailStatus",
          format: { places: 0, digitSeperator: false },
        },
      ],
      content:
        "<b>Trail Name:</b> {trailName} <br>\
                <b>Trail Length:</b> {Shape__Length} Meter<br>\
                <b>Management Organization:</b> {manageOrg}<br>\
                <b>Trail Status:</b> {trailStatus}",
    };
    
    const featureLayer = new FeatureLayer({
      url: trailsUrl,
      popupTemplate: popupTemplate,
    });
    map.add(featureLayer);
    

11.1.2. Setting Up the QueryTask and Query#

  • Define the QueryTask and Query to execute spatial queries using SQL.

    const qTask = new QueryTask({ url: trailsUrl });
    const params = new Query({
      returnGeometry: true,
      outFields: ["*"],
    });
    

11.1.3. Defining SQL Where Clauses#

  • Create an array of SQL where clauses for the user to select from.

  • This array will be used to filter features based on different criteria.

    const querySQL = [
      "Choose a SQL where clause...",
      "hike = 'No'",
      "hike = 'Yes'",
      "bike = 'No'",
      "bike = 'Yes'",
      "horse = 'No'",
      "horse = 'Yes'",
    ];
    let whereClause = querySQL[0];
    
    // Add SQL UI
    const select = document.createElement("select");
    select.setAttribute("class", "esri-widget esri-select");
    select.setAttribute(
      "style",
      "width: 300px; font-family: 'Avenir Next'; font-size: 1em"
    );
    querySQL.forEach((query) => {
      let option = document.createElement("option");
      option.innerHTML = query;
      option.value = query;
      select.appendChild(option);
    });
    
    view.ui.add(select, "top-right");
    

11.1.4. Executing the SQL Query#

  • Listen for changes in the selection and execute the query based on the selected SQL where clause.

    select.addEventListener("change", (event) => {
      whereClause = event.target.value;
      executeQuery(whereClause);
    });
    
    function executeQuery(whereClause) {
      params.where = whereClause;
    
      qTask
        .execute(params)
        .then((results) => {
          console.log("Feature count: " + results.features.length);
          view.graphics.removeAll();
          view.graphics.addMany(results.features);
        })
        .catch((error) => {
          console.error("Error performing query:", error);
        });
    }
    

11.1.5. Additional Methods and Best Practices#

Setting Up Spatial Relationships

  • In addition to filtering by attributes, you can also filter by spatial relationships, such as within a given area or intersecting another feature.

  • Here’s an example of setting up a spatial relationship query:

    const spatialQueryParams = new Query({
      returnGeometry: true,
      outFields: ["*"],
      spatialRelationship: "intersects", // Can be intersects, contains, etc.
      geometry: someGeometry, // Geometry to compare against
    });
    
    qTask.execute(spatialQueryParams).then((results) => {
      console.log("Features found: ", results.features);
    });
    

Improving Query Performance

  • For better performance, especially with large datasets:

    • Limit the fields you retrieve using outFields.

    • Use indexed fields in your SQL where clause.

    • Paginate results if dealing with large numbers of features.

    params.outFields = ["OBJECTID", "trailName", "Shape__Length"];
    params.where = "Shape__Length > 1000"; // Example where clause to filter long trails
    
    qTask.executeForCount(params).then((count) => {
      console.log("Total features found: ", count);
    });
    

11.2. Query a Feature Layer Using Sketch Widgets#

  • In this tutorial, we will learn how to use the Sketch widget to create geometries and perform spatial queries on a feature layer using the ArcGIS JavaScript API.

  • This tutorial will guide you through setting up the map, adding a Sketch widget, and performing queries based on user-drawn geometries.

  • In this tutorial, we will review again what we learn in pervious sections

11.2.1. Setting Up the Environment#

  • First, ensure you have the basic HTML structure and include the necessary ArcGIS JavaScript and CSS files.

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <meta
          name="viewport"
          content="initial-scale=1, maximum-scale=1, user-scalable=no"
        />
        <title>
          ArcGIS Maps SDK for JavaScript Tutorials: Query a feature layer
          (spatial)
        </title>
        <style>
          html,
          body,
          #viewDiv {
            padding: 0;
            margin: 0;
            height: 100%;
            width: 100%;
          }
        </style>
        <link
          rel="stylesheet"
          href="https://js.arcgis.com/4.29/esri/themes/light/main.css"
        />
        <script src="https://js.arcgis.com/4.29/"></script>
      </head>
    
      <body>
        <div id="viewDiv"></div>
      </body>
    </html>
    

11.2.2. Adding the Map and View#

  • Create the map and map view with a specified center and zoom level.

    require([
      "esri/config",
      "esri/Map",
      "esri/views/MapView",
      "esri/widgets/Sketch",
      "esri/layers/GraphicsLayer",
      "esri/layers/FeatureLayer",
    ], function (esriConfig, Map, MapView, Sketch, GraphicsLayer, FeatureLayer) {
      esriConfig.portalUrl = "https://jsapi.maps.arcgis.com";
    
      const map = new Map({
        basemap: "topo-vector", // basemap styles service
      });
    
      const view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-118.80543, 34.03], // Longitude, latitude
        zoom: 13,
      });
    
      // Continue with the next steps...
    });
    

11.2.3. Adding the Feature Layer#

  • Add the feature layer that you want to query to the map.

    const parcelLayer = new FeatureLayer({
      url: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/LA_County_Parcels/FeatureServer/0",
    });
    
    map.add(parcelLayer);
    

11.2.4. Adding the Sketch Widget#

  • Add a GraphicsLayer to the map for the sketch widget to use.

  • Then, create and configure the sketch widget.

    const graphicsLayerSketch = new GraphicsLayer();
    map.add(graphicsLayerSketch);
    
    const sketch = new Sketch({
      layer: graphicsLayerSketch,
      view: view,
      creationMode: "update", // Auto-select
    });
    
    view.ui.add(sketch, "top-right");
    

11.2.5. Querying the Feature Layer#

  • Set up the event listeners for the sketch widget to handle the creation and modification of geometries.

  • These geometries will be used to query the feature layer.

  1. Define the Query Object

  • The parcelQuery object defines the parameters for querying the feature layer. Key properties include:

    • spatialRelationship: Specifies the spatial relationship to apply. In this case, intersects means we are looking for features that intersect the drawn geometry.

    • geometry: The geometry drawn by the user, which will be used as the spatial filter.

    • outFields: An array of attribute field names to include in the query results. This determines which attributes of the features will be returned.

    • returnGeometry: A boolean that indicates whether the geometry of the features should be returned.

    function queryFeaturelayer(geometry) {
      const parcelQuery = {
        spatialRelationship: "intersects", // Relationship operation to apply
        geometry: geometry, // The sketch feature geometry
        outFields: ["APN", "UseType", "TaxRateCity", "Roll_LandValue"], // Attributes to return
        returnGeometry: true,
      };
    
  1. Executing the Query:

    • Call queryFeatures on the parcelLayer with the parcelQuery object.

    • Handle the promise returned by queryFeatures to process the results or catch any errors.

    // Execute the query
    parcelLayer.queryFeatures(parcelQuery)
     .then((results) => {
       console.log("Feature count: " + results.features.length);
       displayResults(results);
     })
     .catch((error) => {
       console.log(error);
     });
    }
    

11.2.6. Displaying the Query Results#

  • Define a function to display the results of the query.

  • This function will create graphics for each feature returned by the query and add them to the map view.

  1. Symbol Definition

    • Define a symbol to style the features. Here, we use a simple-fill symbol with a semi-transparent fill color and a white outline.

    function displayResults(results) {
      const symbol = {
        type: "simple-fill",
        color: [150, 10, 100, 0.5],
        outline: {
          color: "white",
          width: 0.5,
        },
      };
    
  2. Popup Template

    • Define a popup template to display attribute information when a feature is clicked.

    • The title and content properties of the template use attribute values.

    const popupTemplate = {
      title: "Parcel {APN}",
      content:
        "Type: {UseType} <br> Land value: {Roll_LandValue} <br> Tax Rate City: {TaxRateCity}",
    };
    
  3. Assign Symbol and Popup Template, Clear Previous Graphics and Add New Graphics

    • For each feature in the results, assign the defined symbol and popup template.

    // Set symbol and popup
    results.features.map((feature) => {
      feature.symbol = symbol;
      feature.popupTemplate = popupTemplate;
      return feature;
    });
    
    // Clear display
    view.closePopup();
    view.graphics.removeAll();
    // Add features to graphics layer
    view.graphics.addMany(results.features);
    

11.2.7. Final Script#

<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<title>ArcGIS Maps SDK for JavaScript Tutorials: Query a feature layer (spatial)</title>
<style>
    html,
    body,
    #viewDiv {
    padding: 0;
    margin: 0;
    height: 100%;
    width: 100%;
    }
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.29/esri/themes/light/main.css">
<script src="https://js.arcgis.com/4.29/"></script>
<script>
    require([

    "esri/config",
    "esri/Map",
    "esri/views/MapView",

    "esri/widgets/Sketch",
    "esri/layers/GraphicsLayer",
    "esri/layers/FeatureLayer"

    ], function (esriConfig, Map, MapView, Sketch, GraphicsLayer, FeatureLayer) {

    esriConfig.portalUrl = "https://jsapi.maps.arcgis.com";

    const map = new Map({
        basemap: "topo-vector" // basemap styles service
    });

    const view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-118.80543, 34.03000], //Longitude, latitude
        zoom: 13
    });

    // Add sketch widget
    const graphicsLayerSketch = new GraphicsLayer();
    map.add(graphicsLayerSketch);

    const sketch = new Sketch({
        layer: graphicsLayerSketch,
        view: view,
        creationMode: "update" // Auto-select
    });

    view.ui.add(sketch, "top-right");

    // Add sketch events to listen for and execute query
    sketch.on("update", (event) => {

        // Create
        if (event.state === "start") {
        queryFeaturelayer(event.graphics[0].geometry);
        }
        if (event.state === "complete") {
        graphicsLayerSketch.remove(event.graphics[0]); // Clear the graphic when a user clicks off of it or sketches new one
        }
        // Change
        if (event.toolEventInfo && (event.toolEventInfo.type === "scale-stop" || event.toolEventInfo.type === "reshape-stop" || event.toolEventInfo.type === "move-stop")) {
        queryFeaturelayer(event.graphics[0].geometry);
        }

    });

    // Reference query layer
    const parcelLayer = new FeatureLayer({
        url: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/LA_County_Parcels/FeatureServer/0",
    });

    map.add(parcelLayer);

    function queryFeaturelayer(geometry) {

        const parcelQuery = {
        spatialRelationship: "intersects", // Relationship operation to apply
        geometry: geometry,  // The sketch feature geometry
        outFields: ["APN", "UseType", "TaxRateCity", "Roll_LandValue"], // Attributes to return
        returnGeometry: true
        };

        parcelLayer.queryFeatures(parcelQuery)
        .then((results) => {

            console.log("Feature count: " + results.features.length)

            displayResults(results);

        }).catch((error) => {
            console.log(error);
        });

    }

    // Show features (graphics)
    function displayResults(results) {

        // Create a blue polygon
        const symbol = {
        type: "simple-fill",
        color: [150, 10, 100, 0.5],
        outline: {
            color: "white",
            width: .5
        },
        };

        const popupTemplate = {
        title: "Parcel {APN}",
        content: "Type: {UseType} <br> Land value: {Roll_LandValue} <br> Tax Rate City: {TaxRateCity}"
        };

        // Set symbol and popup
        results.features.map((feature) => {
        feature.symbol = symbol;
        feature.popupTemplate = popupTemplate;
        return feature;
        });

        // Clear display
        view.closePopup();
        view.graphics.removeAll();
        // Add features to graphics layer
        view.graphics.addMany(results.features);

    }

    });
</script>
</head>

<body>
<div id="viewDiv"></div>
</body>

</html>