Query Features by Category and Keyword

314
3
Jump to solution
06-24-2020 01:31 PM
JaredPilbeam2
MVP Regular Contributor

I want to create a webmap that is based off of this data-set, for example: Will County EEC Mobile Site 

I'm not familiar enough with querying to be able to recreate the querying. My data-set has multiple categories and multiple keywords. I geocoded the spreadsheet, so now I have a point for each field. Like the example, I would like to have a orderly drop down for the category field and the keyword field.

Currently, I can query features by the category field, but as you can see it is very impractical being that there are too many choices. How can I create my dropdown to look like the above?

My current code (taken from sample code😞

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport"
          content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <title>Recycling Map</title>

    <link rel="stylesheet"
          href="https://js.arcgis.com/4.15/esri/themes/light/main.css" />
    <script src="https://js.arcgis.com/4.15/"></script>

    <style>
        html,
        body,
        #viewDiv {
            height: 100%;
            width: 100%;
            margin: 0;
            padding: 0;
        }

        #infoDiv {
            background-color: white;
            color: black;
            padding: 6px;
            width: 400px;
        }

        #results {
            font-weight: bolder;
            padding-top: 10px;
        }

        .slider {
            width: 100%;
            height: 60px;
        }

        #drop-downs {
            padding-bottom: 15px;
        }
    </style>

    <script>require([
        "esri/Map",
        "esri/views/MapView",
        "esri/layers/FeatureLayer",
        "esri/layers/GraphicsLayer",
        "esri/geometry/geometryEngine",
        "esri/Graphic",

      ], function(
        Map,
        MapView,
        FeatureLayer,
        GraphicsLayer,
        geometryEngine,
        Graphic,
      ) {
   

        var wellTypeSelect = document.getElementById("category");

        // oil and gas wells
        var wellsLayer = new FeatureLayer({
          portalItem: {
            // autocasts as new PortalItem()
            id: "f76b85e6c5314a0c96539d704fe92452"
          },
          outFields: ["*"],
          visible: false
        });

   

        // GraphicsLayer for displaying results
        var resultsLayer = new GraphicsLayer();

        var map = new Map({
          basemap: "dark-gray",
          layers: [wellsLayer, resultsLayer]
        });

        var view = new MapView({
          container: "viewDiv",
          map: map,
          center: [-87.95, 41.47],
          zoom: 10
        });
        view.ui.add("infoDiv", "top-left");

        // query all features from the wells layer
        view
          .when(function() {
            return wellsLayer.when(function() {
              var query = wellsLayer.createQuery();
              return wellsLayer.queryFeatures(query);
            });
          })
          .then(getValues)
          .then(getUniqueValues)
          .then(addToSelect)

        // return an array of all the values in the
        // STATUS2 field of the wells layer
        function getValues(response) {
          var features = response.features;
          var values = features.map(function(feature) {
            return feature.attributes.USER_Categ;
          });
          return values;
        }

        // return an array of unique values in
        // the STATUS2 field of the wells layer
        function getUniqueValues(values) {
          var uniqueValues = [];

          values.forEach(function(item, i) {
            if (
              (uniqueValues.length < 1 || uniqueValues.indexOf(item) === -1) &&
              item !== ""
            ) {
              uniqueValues.push(item);
            }
          });
          return uniqueValues;
        }

        // Add the unique values to the wells type
        // select element. This will allow the user
        // to filter wells by type.
        function addToSelect(values) {
          values.sort();
          values.forEach(function(value) {
            var option = document.createElement("option");
            option.text = value;
            wellTypeSelect.add(option);
          });

          return setWellsDefinitionExpression(wellTypeSelect.value);
        }

        // set the definition expression on the wells
        // layer to reflect the selection of the user
        function setWellsDefinitionExpression(newValue) {
          wellsLayer.definitionExpression = "USER_Categ = '" + newValue + "'";

          if (!wellsLayer.visible) {
            wellsLayer.visible = true;
          }

          return queryForWellGeometries();
        }

        // Get all the geometries of the wells layer
        // the createQuery() method creates a query
        // object that respects the definitionExpression
        // of the layer
        function queryForWellGeometries() {
          var wellsQuery = wellsLayer.createQuery();

          return wellsLayer.queryFeatures(wellsQuery).then(function(response) {
            wellsGeometries = response.features.map(function(feature) {
              return feature.geometry;
            });

            return wellsGeometries;
          });
        }

        // set a new definitionExpression on the wells layer
        wellTypeSelect.addEventListener("change", function() {
          var type = event.target.value;
          setWellsDefinitionExpression(type);
        });
      });</script>
</head>

<body>
    <div id="viewDiv"></div>
    <div id="infoDiv" class="esri-widget">
        <div id="drop-downs">
            Select Category:
            <select id="category" class="esri-widget"></select>
        </div>
        <div id="results" class="esri-widget"></div>
    </div>
</body>
</html>
0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Esteemed Contributor

Jared,

  No that would be overkill/bad workflow. You would want to re-code the functions to handle both fields at the same time. Here is my re-work of the sample based on what you are wanting. Each function works with both values and now there is a button to filter the features instead.

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
  <title>Recycling Map</title>

  <link rel="stylesheet" href="https://js.arcgis.com/4.15/esri/themes/light/main.css" />
  <script src="https://js.arcgis.com/4.15/"></script>

  <style>
    html,
    body,
    #viewDiv {
      height: 100%;
      width: 100%;
      margin: 0;
      padding: 0;
    }

    #infoDiv {
      background-color: white;
      color: black;
      padding: 6px;
      width: 440px;
    }

    #results {
      font-weight: bolder;
      padding-top: 10px;
    }

    .slider {
      width: 100%;
      height: 60px;
    }

    #category,
    #keyword {
      margin-top: 8px;
      margin-bottom: 8px;
    }
  </style>

  <script>
    require([
      "esri/Map",
      "esri/views/MapView",
      "esri/layers/FeatureLayer",
      "esri/layers/GraphicsLayer",
      "esri/geometry/geometryEngine",
      "esri/Graphic",

    ], function (
      Map,
      MapView,
      FeatureLayer,
      GraphicsLayer,
      geometryEngine,
      Graphic,
    ) {


      var catTypeSelect = document.getElementById("category");
      var keyTypeSelect = document.getElementById("keyword");
      var filterButton = document.getElementById("filterBtn");

      // oil and gas wells
      var recycleLayer = new FeatureLayer({
        portalItem: {
          // autocasts as new PortalItem()
          id: "227061be60a14cc89946a978b440d227"
        },
        outFields: ["*"],
        visible: false
      });



      // GraphicsLayer for displaying results
      var resultsLayer = new GraphicsLayer();

      var map = new Map({
        basemap: "dark-gray",
        layers: [recycleLayer, resultsLayer]
      });

      var view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-87.95, 41.47],
        zoom: 10
      });
      view.ui.add("infoDiv", "top-left");

      // query all features from the wells layer
      view
        .when(function () {
          return recycleLayer.when(function () {
            var query = recycleLayer.createQuery();
            return recycleLayer.queryFeatures(query);
          });
        })
        .then(getValues)
        .then(getUniqueValues)
        .then(addToSelect)

      // return an array of all the values in the
      // STATUS2 field of the wells layer
      function getValues(response) {
        var features = response.features;
        var values = features.map(function (feature) {
          return {
            USER_Categ: feature.attributes.USER_Categ,
            USER_Keywo: feature.attributes.USER_Keywo
          }
        });
        return values;
      }

      // return an array of unique values in
      // the STATUS2 field of the wells layer
      function getUniqueValues(values) {
        var uniqueKeyValues = [];
        var uniqueCatValues = [];

        values.forEach(function (item, i) {
          var keyVal = item.USER_Keywo.split(";");
          var catVal = item.USER_Categ.split(";");
          catVal.map(function (val1) {
            if (
              (uniqueCatValues.length < 1 || uniqueCatValues.indexOf(val1) === -1) &&
              val1 !== ""
            ) {
              uniqueCatValues.push(val1);
            }
          });
          keyVal.map(function (val2) {
            if (
              (uniqueKeyValues.length < 1 || uniqueKeyValues.indexOf(val2) === -1) &&
              val2 !== ""
            ) {
              uniqueKeyValues.push(val2);
            }
          });
        });
        return {
          uKeyVals: uniqueKeyValues,
          uCatVals: uniqueCatValues
        };
      }

      // Add the unique values to the wells type
      // select element. This will allow the user
      // to filter wells by type.
      function addToSelect(values) {
        values.uCatVals.sort();
        values.uCatVals.forEach(function (value) {
          var option = document.createElement("option");
          option.text = value;
          catTypeSelect.add(option);
        });

        values.uKeyVals.sort();
        values.uKeyVals.forEach(function (value) {
          var option = document.createElement("option");
          option.text = value;
          keyTypeSelect.add(option);
        });

        return setDefinitionExpression();
      }

      // set the definition expression on the recycle
      // layer to reflect the selection of the user
      function setDefinitionExpression() {
        var sqlExp = "";
        if (catTypeSelect.selectedIndex > 0) {
          sqlExp += "USER_Categ LIKE '%" + catTypeSelect.options[catTypeSelect.selectedIndex].value + "%'";
        }
        if(keyTypeSelect.selectedIndex > 0) {
          if(sqlExp === ""){
            sqlExp += "USER_Keywo LIKE '%" + keyTypeSelect.options[keyTypeSelect.selectedIndex].value + "%'";
          }else{
            sqlExp += " AND USER_Keywo LIKE '%" + keyTypeSelect.options[keyTypeSelect.selectedIndex].value + "%'";
          }
        }
        recycleLayer.definitionExpression = sqlExp;

        if (!recycleLayer.visible) {
          recycleLayer.visible = true;
        }

        return queryForGeometries();
      }

      // Get all the geometries of the recycle layer
      // the createQuery() method creates a query
      // object that respects the definitionExpression
      // of the layer
      function queryForGeometries() {
        var rQuery = recycleLayer.createQuery();

        return recycleLayer.queryFeatures(rQuery).then(function (response) {
          rGeometries = response.features.map(function (feature) {
            return feature.geometry;
          });

          return rGeometries;
        });
      }

      filterButton.addEventListener("click", function() {
        setDefinitionExpression();
      });

      // set a new definitionExpression on the recycle layer
      // catTypeSelect.addEventListener("change", function () {
      //   var type = event.target.value;
      //   setDefinitionExpression(type, "cat");
      // });

      // set a new definitionExpression on the recycle layer
      // keyTypeSelect.addEventListener("change", function () {
      //   var type = event.target.value;
      //   setDefinitionExpression(type, "key");
      // });
    });
  </script>
</head>

<body>
  <div id="viewDiv"></div>
  <div id="infoDiv" class="esri-widget">
    <div id="drop-downs">
      Select Category:
      <select id="category" class="esri-widget"></select>
      Select Keyword:
      <select id="keyword" class="esri-widget"></select>
      <button id="filterBtn" class="esri-button esri-button--secondary">Filter</button>
    </div>
    <div id="results" class="esri-widget"></div>
  </div>
</body>

</html>

View solution in original post

0 Kudos
3 Replies
RobertScheitlin__GISP
MVP Esteemed Contributor

Jared,

   It is a simple matter of splitting the results string by the ";" delimiter inside your getUniqueValues function.

        // return an array of unique values in
        // the STATUS2 field of the wells layer
        function getUniqueValues(values) {
          var uniqueValues = [];

          values.forEach(function(item, i) {
            var uVal = item.split(";");
            uVal.map(function(val){
              if (
                (uniqueValues.length < 1 || uniqueValues.indexOf(val) === -1) &&
                item !== ""
              ) {
                uniqueValues.push(val);
              }
            });
          });
          return uniqueValues;
        }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Oh and update your query expression:

        // set the definition expression on the wells
        // layer to reflect the selection of the user
        function setWellsDefinitionExpression(newValue) {
          wellsLayer.definitionExpression = "USER_Categ LIKE '%" + newValue + "%'";

          if (!wellsLayer.visible) {
            wellsLayer.visible = true;
          }

          return queryForWellGeometries();
        }
JaredPilbeam2
MVP Regular Contributor

Hi Robert,

Thanks, that did a nice job of making the selections more readable:

But, I was also wondering how I can get two drop-downs to display. There is this one, Category, and then I would also like to be able to select by Keyword (USER_Keywo field).

Here's the feature layer view for reference: https://willcountygis.maps.arcgis.com/home/item.html?id=227061be60a14cc89946a978b440d227&view=table#... 

Would I have to duplicate all the query blocks of the script for the USER_Keywo field to do this?

0 Kudos
RobertScheitlin__GISP
MVP Esteemed Contributor

Jared,

  No that would be overkill/bad workflow. You would want to re-code the functions to handle both fields at the same time. Here is my re-work of the sample based on what you are wanting. Each function works with both values and now there is a button to filter the features instead.

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
  <title>Recycling Map</title>

  <link rel="stylesheet" href="https://js.arcgis.com/4.15/esri/themes/light/main.css" />
  <script src="https://js.arcgis.com/4.15/"></script>

  <style>
    html,
    body,
    #viewDiv {
      height: 100%;
      width: 100%;
      margin: 0;
      padding: 0;
    }

    #infoDiv {
      background-color: white;
      color: black;
      padding: 6px;
      width: 440px;
    }

    #results {
      font-weight: bolder;
      padding-top: 10px;
    }

    .slider {
      width: 100%;
      height: 60px;
    }

    #category,
    #keyword {
      margin-top: 8px;
      margin-bottom: 8px;
    }
  </style>

  <script>
    require([
      "esri/Map",
      "esri/views/MapView",
      "esri/layers/FeatureLayer",
      "esri/layers/GraphicsLayer",
      "esri/geometry/geometryEngine",
      "esri/Graphic",

    ], function (
      Map,
      MapView,
      FeatureLayer,
      GraphicsLayer,
      geometryEngine,
      Graphic,
    ) {


      var catTypeSelect = document.getElementById("category");
      var keyTypeSelect = document.getElementById("keyword");
      var filterButton = document.getElementById("filterBtn");

      // oil and gas wells
      var recycleLayer = new FeatureLayer({
        portalItem: {
          // autocasts as new PortalItem()
          id: "227061be60a14cc89946a978b440d227"
        },
        outFields: ["*"],
        visible: false
      });



      // GraphicsLayer for displaying results
      var resultsLayer = new GraphicsLayer();

      var map = new Map({
        basemap: "dark-gray",
        layers: [recycleLayer, resultsLayer]
      });

      var view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-87.95, 41.47],
        zoom: 10
      });
      view.ui.add("infoDiv", "top-left");

      // query all features from the wells layer
      view
        .when(function () {
          return recycleLayer.when(function () {
            var query = recycleLayer.createQuery();
            return recycleLayer.queryFeatures(query);
          });
        })
        .then(getValues)
        .then(getUniqueValues)
        .then(addToSelect)

      // return an array of all the values in the
      // STATUS2 field of the wells layer
      function getValues(response) {
        var features = response.features;
        var values = features.map(function (feature) {
          return {
            USER_Categ: feature.attributes.USER_Categ,
            USER_Keywo: feature.attributes.USER_Keywo
          }
        });
        return values;
      }

      // return an array of unique values in
      // the STATUS2 field of the wells layer
      function getUniqueValues(values) {
        var uniqueKeyValues = [];
        var uniqueCatValues = [];

        values.forEach(function (item, i) {
          var keyVal = item.USER_Keywo.split(";");
          var catVal = item.USER_Categ.split(";");
          catVal.map(function (val1) {
            if (
              (uniqueCatValues.length < 1 || uniqueCatValues.indexOf(val1) === -1) &&
              val1 !== ""
            ) {
              uniqueCatValues.push(val1);
            }
          });
          keyVal.map(function (val2) {
            if (
              (uniqueKeyValues.length < 1 || uniqueKeyValues.indexOf(val2) === -1) &&
              val2 !== ""
            ) {
              uniqueKeyValues.push(val2);
            }
          });
        });
        return {
          uKeyVals: uniqueKeyValues,
          uCatVals: uniqueCatValues
        };
      }

      // Add the unique values to the wells type
      // select element. This will allow the user
      // to filter wells by type.
      function addToSelect(values) {
        values.uCatVals.sort();
        values.uCatVals.forEach(function (value) {
          var option = document.createElement("option");
          option.text = value;
          catTypeSelect.add(option);
        });

        values.uKeyVals.sort();
        values.uKeyVals.forEach(function (value) {
          var option = document.createElement("option");
          option.text = value;
          keyTypeSelect.add(option);
        });

        return setDefinitionExpression();
      }

      // set the definition expression on the recycle
      // layer to reflect the selection of the user
      function setDefinitionExpression() {
        var sqlExp = "";
        if (catTypeSelect.selectedIndex > 0) {
          sqlExp += "USER_Categ LIKE '%" + catTypeSelect.options[catTypeSelect.selectedIndex].value + "%'";
        }
        if(keyTypeSelect.selectedIndex > 0) {
          if(sqlExp === ""){
            sqlExp += "USER_Keywo LIKE '%" + keyTypeSelect.options[keyTypeSelect.selectedIndex].value + "%'";
          }else{
            sqlExp += " AND USER_Keywo LIKE '%" + keyTypeSelect.options[keyTypeSelect.selectedIndex].value + "%'";
          }
        }
        recycleLayer.definitionExpression = sqlExp;

        if (!recycleLayer.visible) {
          recycleLayer.visible = true;
        }

        return queryForGeometries();
      }

      // Get all the geometries of the recycle layer
      // the createQuery() method creates a query
      // object that respects the definitionExpression
      // of the layer
      function queryForGeometries() {
        var rQuery = recycleLayer.createQuery();

        return recycleLayer.queryFeatures(rQuery).then(function (response) {
          rGeometries = response.features.map(function (feature) {
            return feature.geometry;
          });

          return rGeometries;
        });
      }

      filterButton.addEventListener("click", function() {
        setDefinitionExpression();
      });

      // set a new definitionExpression on the recycle layer
      // catTypeSelect.addEventListener("change", function () {
      //   var type = event.target.value;
      //   setDefinitionExpression(type, "cat");
      // });

      // set a new definitionExpression on the recycle layer
      // keyTypeSelect.addEventListener("change", function () {
      //   var type = event.target.value;
      //   setDefinitionExpression(type, "key");
      // });
    });
  </script>
</head>

<body>
  <div id="viewDiv"></div>
  <div id="infoDiv" class="esri-widget">
    <div id="drop-downs">
      Select Category:
      <select id="category" class="esri-widget"></select>
      Select Keyword:
      <select id="keyword" class="esri-widget"></select>
      <button id="filterBtn" class="esri-button esri-button--secondary">Filter</button>
    </div>
    <div id="results" class="esri-widget"></div>
  </div>
</body>

</html>
0 Kudos