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.
Solved! Go to Solution.
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>
According to the documentation, a Circle is a subclass of a Polygon.
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.
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.
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.
Tracy,
I will do some testing on my end to confirm this.
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>
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.