Select to view content in your preferred language

convert buffer Circle to polygon to use as Query.geometry

5543
7
Jump to solution
12-12-2014 08:00 AM
TracySchloss
Honored Contributor

I have a series of points that I have buffered and generated as Circles.  I now want those circles to be used to select features from my featureLayer.  I'm getting stuck on the input geometry, because Circle is not in the list of acceptable geometry types for Query.  It lists Extent, Point, Multipoint, Polyline, or Polygon

I need to be precise, so I don't want to just use the extent of the circle as my input.  I assume there is a way to convert my circles to polygons, but I haven't figured out how to do it yet. 

0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Emeritus

Tracy,

  I can confirm that a circle does working in a query.

  Here is a sample app:

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <!--The viewport meta tag is used to improve the presentation and behavior of the samples 
      on iOS devices-->
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <title>Buffer</title>
  <link rel="stylesheet" href="http://js.arcgis.com/3.11/dijit/themes/tundra/tundra.css">
  <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css">
  <style>
    html,
    body,
    #mapDiv {
      padding: 0;
      margin: 0;
      height: 100%;
    }
    #messages {
      background-color: #fff;
      box-shadow: 0 0 5px #888;
      font-size: 1.1em;
      max-width: 15em;
      padding: 0.5em;
      position: absolute;
      right: 20px;
      top: 20px;
      z-index: 40;
    }
    #drop {
      background-color: #fff;
      box-shadow: 0 0 5px #888;
      font-size: 1.1em;
      max-width: 15em;
      padding: 0.5em;
      position: absolute;
      right: 20px;
      top: 105px;
      z-index: 40;
    }
  </style>
  <script src="http://js.arcgis.com/3.11/"></script>
  <script>
    var map;


    require([
        "esri/map", "esri/layers/FeatureLayer",
        "esri/tasks/query", "esri/geometry/Circle", "esri/units",
        "esri/graphic", "esri/InfoTemplate", "esri/symbols/SimpleMarkerSymbol",
        "esri/symbols/SimpleLineSymbol", "esri/symbols/SimpleFillSymbol", "esri/renderers/SimpleRenderer",
        "esri/config", "esri/Color", "dojo/dom", "dijit/form/ComboBox", "dojo/domReady!"
      ], function (
      Map, FeatureLayer,
      Query, Circle, Units,
      Graphic, InfoTemplate, SimpleMarkerSymbol,
      SimpleLineSymbol, SimpleFillSymbol, SimpleRenderer,
      esriConfig, Color, dom
    ) {
      // use a proxy page if a URL generated by this page is greater than 2000 characters
      //
      // this should not be needed as nearly all query & select functions are performed on the client
      esriConfig.defaults.io.proxyUrl = "/proxy/";

      map = new Map("mapDiv", {
        basemap: "streets",
        center: [-81.00, 34.000],
        zoom: 14,
        slider: false
      });

      //add the census block points in on demand mode. Note that an info template has been defined so when 
      //selected features are clicked a popup window will appear displaying the content defined in the info template.
      var featureLayer = new FeatureLayer("http://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer/0", {
        outFields: ["POP2000", "HOUSEHOLDS", "HSE_UNITS", "TRACT", "BLOCK"]
      });

      // selection symbol used to draw the selected census block points within the buffer polygon
      var symbol = new SimpleMarkerSymbol(
        SimpleMarkerSymbol.STYLE_CIRCLE,
        6,
        new SimpleLineSymbol(
          SimpleLineSymbol.STYLE_NULL, new Color([200, 120, 101, 0.9]), 1),
        new Color([200, 0, 0, 1])
      );
      featureLayer.setSelectionSymbol(symbol);

      //make unselected features invisible
      var nullSymbol = new SimpleMarkerSymbol().setSize(0);
      featureLayer.setRenderer(new SimpleRenderer(nullSymbol));

      map.addLayer(featureLayer);


      var circleSymb = new SimpleFillSymbol(
        SimpleFillSymbol.STYLE_NULL,
        new SimpleLineSymbol(
          SimpleLineSymbol.STYLE_SHORTDASHDOTDOT,
          new Color([105, 105, 105]),
          2
        ), new Color([255, 255, 0, 0.25])
      );
      var circle;

      //when the map is clicked create a buffer around the click point of the specified distance.
      map.on("click", function (evt) {
        selbuf = document.FormSelection.BufferSelection.selectedIndex;
        var BufferSelection = document.FormSelection.BufferSelection.options[selbuf].value;

        circle = new Circle({
          center: evt.mapPoint,
          geodesic: true,
          radius: BufferSelection,
          radiusUnit: Units.MILES
        });
        map.graphics.clear();
        map.infoWindow.hide();
        var graphic = new Graphic(circle, circleSymb);
        map.graphics.add(graphic);

        var query = new Query();
        query.geometry = circle;
        featureLayer.selectFeatures(query, FeatureLayer.SELECTION_NEW, function (results) {
          var totalPopulation = sumPopulation(results);
          var r = "";
          r = "<b>The total Census Block population within the buffer is <i>" + totalPopulation + "</i>.</b>";
          dom.byId("messages").innerHTML = r;
        });
      });

      function sumPopulation(features) {
        var popTotal = 0;
        for (var x = 0; x < features.length; x++) {
          popTotal = popTotal + features.attributes["POP2000"];
        }
        return popTotal;
      }
    });
  </script>
</head>

<body>
  <span id="messages">Click on the map to select census block points within mile.</span>
  <span id="drop">
    <form name="FormSelection">
     <select name="BufferSelection"> 
           <option>1</option>
        <option>2</option>
        <option>10</option>
     </select>
    </form>
  </span>
  <div id="mapDiv"></div>
</body>

</html>

View solution in original post

0 Kudos
7 Replies
KenBuja
MVP Esteemed Contributor

According to the documentation, a Circle is a subclass of a Polygon.

0 Kudos
TracySchloss
Honored Contributor

That's what I thought to, but my featureLayer query keeps failing.  I put my buffered points into a graphicsLayer (the one you just helped me with the attributes).

I need to know if any features in my featureLayer as within a set distance to other features in that same layer.  Plus I have a 2nd layer that I need to query as well, which I'll get to next.   I figured using a separate graphicLayer would help with this, plus I needed the graphics to display anyway. 

function selectAllInBuffer(){

  primaryLayer.clearSelection();//my featureLayer

  arrayUtils.forEach(bufferLayer.graphics, function (graphic){//my graphics layer full of circles

    var query = new Query();

    query.geometry = graphic.geometry;

      primaryLayer.queryFeatures(query, FeatureLayer.SELECTION_ADD, function (result){

       console.log('queryResult');//just so I had a place to put a breakpoint

      });  

     

  });

Am I not accessing the graphics of my graphicsLayers correctly?  It seems pretty straight forward. 

Have you ever gotten a clear idea of the different between queryFeatures and selectFeatures?  It seems like I read something a while ago, but the explanation was too obscure to make much sense. 

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Tracy,

  Your queryFeatures method call is incorrect.

primaryLayer.queryFeatures(query, FeatureLayer.SELECTION_ADD, function (result){

It should just be:

primaryLayer.queryFeatures(query,  function (result){

You only do the FeatureLayer.SELECTION_ADD if you are using the

selectFeatures(query, selectionMethod?, callback?, errback?).

The difference between queryFeatures and selectFeatures is pretty big.

queryFeatures will return geometry and attributes that meet you query criteria where as selectFeatures will do the same and actually change the symbology of the selected features in your FeatureLayer.

TracySchloss
Honored Contributor

I could only get this to work with the extent of the graphic, not the actual geometry of the circle, which was my original question.  I guess the answer is, Circle isn't interpreted as a Polygon in this situation. 

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Tracy,

   I will do some testing on my end to confirm this.

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Tracy,

  I can confirm that a circle does working in a query.

  Here is a sample app:

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <!--The viewport meta tag is used to improve the presentation and behavior of the samples 
      on iOS devices-->
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <title>Buffer</title>
  <link rel="stylesheet" href="http://js.arcgis.com/3.11/dijit/themes/tundra/tundra.css">
  <link rel="stylesheet" href="http://js.arcgis.com/3.11/esri/css/esri.css">
  <style>
    html,
    body,
    #mapDiv {
      padding: 0;
      margin: 0;
      height: 100%;
    }
    #messages {
      background-color: #fff;
      box-shadow: 0 0 5px #888;
      font-size: 1.1em;
      max-width: 15em;
      padding: 0.5em;
      position: absolute;
      right: 20px;
      top: 20px;
      z-index: 40;
    }
    #drop {
      background-color: #fff;
      box-shadow: 0 0 5px #888;
      font-size: 1.1em;
      max-width: 15em;
      padding: 0.5em;
      position: absolute;
      right: 20px;
      top: 105px;
      z-index: 40;
    }
  </style>
  <script src="http://js.arcgis.com/3.11/"></script>
  <script>
    var map;


    require([
        "esri/map", "esri/layers/FeatureLayer",
        "esri/tasks/query", "esri/geometry/Circle", "esri/units",
        "esri/graphic", "esri/InfoTemplate", "esri/symbols/SimpleMarkerSymbol",
        "esri/symbols/SimpleLineSymbol", "esri/symbols/SimpleFillSymbol", "esri/renderers/SimpleRenderer",
        "esri/config", "esri/Color", "dojo/dom", "dijit/form/ComboBox", "dojo/domReady!"
      ], function (
      Map, FeatureLayer,
      Query, Circle, Units,
      Graphic, InfoTemplate, SimpleMarkerSymbol,
      SimpleLineSymbol, SimpleFillSymbol, SimpleRenderer,
      esriConfig, Color, dom
    ) {
      // use a proxy page if a URL generated by this page is greater than 2000 characters
      //
      // this should not be needed as nearly all query & select functions are performed on the client
      esriConfig.defaults.io.proxyUrl = "/proxy/";

      map = new Map("mapDiv", {
        basemap: "streets",
        center: [-81.00, 34.000],
        zoom: 14,
        slider: false
      });

      //add the census block points in on demand mode. Note that an info template has been defined so when 
      //selected features are clicked a popup window will appear displaying the content defined in the info template.
      var featureLayer = new FeatureLayer("http://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer/0", {
        outFields: ["POP2000", "HOUSEHOLDS", "HSE_UNITS", "TRACT", "BLOCK"]
      });

      // selection symbol used to draw the selected census block points within the buffer polygon
      var symbol = new SimpleMarkerSymbol(
        SimpleMarkerSymbol.STYLE_CIRCLE,
        6,
        new SimpleLineSymbol(
          SimpleLineSymbol.STYLE_NULL, new Color([200, 120, 101, 0.9]), 1),
        new Color([200, 0, 0, 1])
      );
      featureLayer.setSelectionSymbol(symbol);

      //make unselected features invisible
      var nullSymbol = new SimpleMarkerSymbol().setSize(0);
      featureLayer.setRenderer(new SimpleRenderer(nullSymbol));

      map.addLayer(featureLayer);


      var circleSymb = new SimpleFillSymbol(
        SimpleFillSymbol.STYLE_NULL,
        new SimpleLineSymbol(
          SimpleLineSymbol.STYLE_SHORTDASHDOTDOT,
          new Color([105, 105, 105]),
          2
        ), new Color([255, 255, 0, 0.25])
      );
      var circle;

      //when the map is clicked create a buffer around the click point of the specified distance.
      map.on("click", function (evt) {
        selbuf = document.FormSelection.BufferSelection.selectedIndex;
        var BufferSelection = document.FormSelection.BufferSelection.options[selbuf].value;

        circle = new Circle({
          center: evt.mapPoint,
          geodesic: true,
          radius: BufferSelection,
          radiusUnit: Units.MILES
        });
        map.graphics.clear();
        map.infoWindow.hide();
        var graphic = new Graphic(circle, circleSymb);
        map.graphics.add(graphic);

        var query = new Query();
        query.geometry = circle;
        featureLayer.selectFeatures(query, FeatureLayer.SELECTION_NEW, function (results) {
          var totalPopulation = sumPopulation(results);
          var r = "";
          r = "<b>The total Census Block population within the buffer is <i>" + totalPopulation + "</i>.</b>";
          dom.byId("messages").innerHTML = r;
        });
      });

      function sumPopulation(features) {
        var popTotal = 0;
        for (var x = 0; x < features.length; x++) {
          popTotal = popTotal + features.attributes["POP2000"];
        }
        return popTotal;
      }
    });
  </script>
</head>

<body>
  <span id="messages">Click on the map to select census block points within mile.</span>
  <span id="drop">
    <form name="FormSelection">
     <select name="BufferSelection"> 
           <option>1</option>
        <option>2</option>
        <option>10</option>
     </select>
    </form>
  </span>
  <div id="mapDiv"></div>
</body>

</html>
0 Kudos
TracySchloss
Honored Contributor

OK, I got it working.  Now I need to take it further.  I need to step through all circles and build a composite list of all nearby features.  The output needs to include both some of the attributes of the the input (objectid, name) followed by all the features in the comparison layer that are within it's buffer.

What I came up with seems right, but since I have multiple features now, not one,it seems like it needs to be changed to something with a deferred.  It doesn't seem to execute in the right sequence.

Here's the sequence:

Select a county name from a pick list.  Use that name to run a query task against the input layer.  The result is selected points

//zooms to county selected in the dropdown list

  function zoomCounty () {
    findTask = new FindTask((countyLayer.url));
    findParams = new FindParameters();
    findParams.returnGeometry = true;
    findParams.layerIds = [0];
    findParams.searchFields = ["COUNTYNAME"]; //Fields are CASE SENSITIVE
    findParams.outSpatialReference = spatialReference; 
    var countyName = registry.byId("countySelect").value;//Set the search text to the value selected in the list
//  var countyName = "Boone";
    currentCounty = countyName;
    if (countyName.length > 1){
          findParams.searchText = countyName;
/*
    findTask.execute(findParams, function (results) {
      map.setExtent(results[0].feature.geometry.getExtent().expand(1.5));
    });
*/
  updateLcarsGrid();
  }else{
    console.log('no county selected');
  }
  }

These are placed into a formatted grid

//Populates a list of LCARS for that county

function updateLcarsGrid(){
     var queryParams = new Query();
     queryParams.geometry = currentExtent;
     queryParams.spatialRelationship = Query.SPATIAL_REL_CONTAINS;
     queryParams.outFields = ["*"];
     queryParams.outSpatialReference = spatialReference;
     queryParams.returnGeometry = true;
     queryParams.where = "County = '" + currentCounty + "'";
     var queryTask = new QueryTask(lcarsLayer.url);
     queryTask.on ('error', queryErrorHandler);
     queryTask.execute(queryParams, updateLGridHandler);
  }
  function queryErrorHandler(err) {
      console.log ("error in queryTask is " + err.error);
  }        


  function updateLGridHandler(results){
    domClass.remove("dataDiv", "dataDivBlank");
    domClass.add("dataDiv", "dataDivOpen");
    // dom.byId("messages").innerHTML = "";
    var data = [];
    if (lgrid) {
      lgrid.refresh();
    }
    if (results.features.length > 0) {   
      data = arrayUtils.map(results.features, function(feature){
        return {
          'id': feature.attributes.OBJECTID,
          'facility_type': feature.attributes.Facility_Type,
          'regulatory_type': feature.attributes.Regulatory_Type,
          'facility': feature.attributes.Provider_Name,
          'address': feature.attributes.Address,
          'city': feature.attributes.City
        };
      });
      lgrid = new Grid({
        renderRow: renderRowFunction,
        showHeader: false
      }, "lcarsGridDiv");
      
      lgrid.renderArray(data);
      lgrid.sort('facility');
      
      lgrid.on('.dgrid-row:click', function(evt){
        gridClick = true;
        var row = lgrid.row(evt);
        var rowid = [row.data.id];
        gridAddr = row.data.address;
        gridClickHandler(rowid);
      });
      generateBufferGraphic(results.features);
      var compArray = selectAllInBuffer();
      console.log("end of selectAllInBuffer");
      
    }else {
      var countyName = registry.byId("countySelect").value;//Set the search text to the value selected in the list 
      //  var countyName = "Boone";
      if (countyName.length > 1) {
        findParams.searchText = countyName;       
        findTask.execute(findParams, function(results){
          map.setExtent(results[0].feature.geometry.getExtent().expand(1.5));
        });
        alert("No LCARS in this county. Select another from the list.");
      }
    }
  }

There is a pick list of distances.  This is input for generating circle buffers, which go into a graphicsLayer.

//create a buffer graphic for every LCARS
function generateBufferGraphic(features){
    bufferLayer.clear();  
    var radius = registry.byId("bufferSelect").value;
    var idPoint,circle; 
    compareSel.length = 0;  
  //added ts 12/30
    var query = new Query();
    query.returnGeometry = true;
    arrayUtils.forEach(features, function (feature){
      idPoint = feature.geometry;
       circle = new Circle({  
          center: idPoint,  
          geodesic: true,  
          radius: radius,  
          radiusUnit: "esriMiles"  
       });  
       var graphic = new Graphic(circle, bufferSymbol,feature.attributes);      
       graphic.attributes.Source = 'LCARS';
       bufferLayer.add(graphic); 
    });
        //Added "if" so the zoom wouldn't happen if the buffer alone changes.
  if (justBuffer == false) {
    var ext = graphicsUtils.graphicsExtent(bufferLayer.graphics).expand(2);
    map.setExtent(ext); 
}
justBuffer = false;
}

Then these graphics are used one at a time as the geometry for a comparison layer using a selectFeatures. The output from that selection might be zero, or multiple.  This seems to be where I'm failing. With breakpoints, I can see there are features within an individual circle.  By the time I finish all the looping, I always end up with zero!

function selectAllInBuffer(){
  lcarsLayer.clearSelection();
  compareLayer.clearSelection();
  compareSel.length = 0;
 // var graphics = bufferLayer.graphics;
  compareLayer.on('selection-complete',function (results){
     var len = results.features.length;
      if (len > 0) {     
          arrayUtils.forEach(results.features, function(feature){
            compareSel.push({"lcars":lcarsProv,"results":feature.attributes});
            console.log("compareSel length = " + compareSel.length);
          });  
      }else{
        console.log(" no nearby facilities");
      }
  });  
  arrayUtils.forEach(bufferLayer.graphics, function (graphic){
     query.geometry = graphic.geometry; 
     graphic_oid = graphic.attributes.OBJECTID;
     lcarsProv = graphic.attributes;  
    compareLayer.selectFeatures(query);
  });
  return compareSel;
}

This is sensitive data, or I'd post a link.  I'm hampered by a broken wrist, so I'm not quite up to making a jsfiddle.  Hopefully someone will spot something.  There are no errors generated from this code, I'm just not getting the results I expect.

0 Kudos