Using Snapping with Graphic Layers (select by Feature Layers)

1379
2
Jump to solution
03-26-2018 10:09 AM
by Anonymous User
Not applicable

EDIT: I believe using the selectByFeatureLayers is ultimately what I need to leverage. If I can get this to enforce snapping, then I'll be able to get there on my own. I'm currently testing in the esri sandbox. Below is my app's source code.

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <title>Layer in a map service - [ON-DEMAND]</title>

  <link rel="stylesheet" href="https://js.arcgis.com/3.23/dijit/themes/claro/claro.css">
  <link rel="stylesheet" href="https://js.arcgis.com/3.23/esri/css/esri.css" />

  <script src="https://js.arcgis.com/3.23/"></script>
  <script>
    var map;

    require([
      "esri/InfoTemplate",
      "esri/map",
      "esri/layers/FeatureLayer",
      "esri/symbols/SimpleFillSymbol",
      "esri/symbols/SimpleLineSymbol",
      "esri/tasks/query",
      "esri/toolbars/draw",
      "dojo/dom",
      "dojo/on",
      "dojo/parser",
      "dojo/_base/array",
      "esri/Color",
      "dijit/form/Button",
      "dojo/domReady!"
    ],
      function (
        InfoTemplate, Map, FeatureLayer, SimpleFillSymbol, SimpleLineSymbol,
        Query, Draw, dom, on, parser, arrayUtil, Color
      ) {

        parser.parse();

        var selectionToolbar, featureLayer;

        map = new Map("map", {
          basemap: "topo",
          center: [-80.8431, 35.2271],
          zoom: 15
        });

        map.on("load", initSelectToolbar);

        var fieldsSelectionSymbol =
          new SimpleFillSymbol(SimpleFillSymbol.STYLE_SOLID,
            new SimpleLineSymbol(SimpleLineSymbol.STYLE_DASHDOT,
          new Color([255, 0, 0]), 2), new Color([255, 255, 0, 0.5]));

//        var content = "<b>Street Name</b>: ${WHOLESTNAME}" +
 //                     "<br><b>Street Range</b>: ${FULL_NAME}" +
 //                     "<br><b>Length</b>: ${SHAPE.STLength}";
  //      var infoTemplate = new InfoTemplate("${WHOLESTNAME}", content);

        featureLayer = new FeatureLayer("http://maps.ci.charlotte.nc.us/arcgis/rest/services/NBS/StreetAdoption/MapServer/2",
          {
            mode: FeatureLayer.MODE_ONDEMAND,
           // infoTemplate: infoTemplate,
            outFields: ["*"]
          });

        featureLayer.on("selection-complete", sumMilage);
        featureLayer.on("selection-clear", function () {
          dom.byId('messages').innerHTML = "<i>No Selected Roads</i>";
        });
        map.addLayer(featureLayer);

        on(dom.byId("selectFieldsButton"), "click", function () {
          selectionToolbar.activate(Draw.POLYLINE);
        });

        on(dom.byId("clearSelectionButton"), "click", function () {
          featureLayer.clearSelection();
        });

        function initSelectToolbar (event) {
          selectionToolbar = new Draw(event.map);
          var selectQuery = new Query();

          on(selectionToolbar, "DrawEnd", function (geometry) {
            selectionToolbar.deactivate();
            selectQuery.geometry = geometry;
            featureLayer.selectFeatures(selectQuery,
              FeatureLayer.SELECTION_NEW);
          });
        }

        function sumMilage (event) {
          var milageSum = 0;
          //summarize the cumulative gas production to display
          arrayUtil.forEach(event.features, function (feature) {
            milageSum += feature.attributes.STLength;
          });
          dom.byId('messages').innerHTML = "<b>Total Length of Segments Requested: " +
                                            milageSum + " Miles Selected </b>";
        }
      });
  </script>
</head>

<body class="claro">
  <button id="selectFieldsButton" data-dojo-type="dijit/form/Button">Select Roads</button>
  <button id="clearSelectionButton" data-dojo-type="dijit/form/Button">Clear Selection</button><br>
  <div id="map" style="position: relative; width:100%; height:500px; border:1px solid #000;"></div>
  <span id="messages"></span>
</body>

</html>

rscheitlin‌, any ideas?

Hello!

I'm looking to implement, what I imagined, would be a fairly straightforward workflow. End goal: to allow a user in a web app to select contiguous highway segments and return/pass the overlain segments ObjectIDs. The segments must, however, be longer than five miles. 

I've run into some issues though. I first tried building a Geoprocessing service that could handle this, but was not able to configure the service to run optimally with our data (executing summary-statistics and if/then branch logic on the service has a lot of overhead), so I turned to JS.

I first tried using the measurement widget in JS with snapping enabled. I got to the point where users could select contiguous roadways that would snap to features (I'd prefer they trace (i.e. non-linear features), but this will do for now). However I wasn't able to pass these linear geometries so that they could be used for an identify task so I tried something else.

I then tried to use the draw widget to create a linear feature, export its length after each vertex was added (not ideal) and then pass those geometries as well. No luck there either.

After I get this up and running, I know the next hurdle will be to apply a threshold for identify functions. Since roads intersect, if I select a straight segment, the perpendicular roads that cross that feature will also be returned. But I'm not there yet.

Any and all help is welcome and appreciated!

0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Emeritus

Andrew,

  Here are the changes for adding the snapping and getting the length data:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <title>Layer in a map service - [ON-DEMAND]</title>

  <link rel="stylesheet" href="https://js.arcgis.com/3.23/dijit/themes/claro/claro.css">
  <link rel="stylesheet" href="https://js.arcgis.com/3.23/esri/css/esri.css" />

  <script src="https://js.arcgis.com/3.23/"></script>
  <script>
    var map;

    require([
      "esri/InfoTemplate",
      "esri/map",
      "esri/layers/FeatureLayer",
      "esri/symbols/SimpleFillSymbol",
      "esri/symbols/SimpleLineSymbol",
      "esri/tasks/query",
      "esri/toolbars/draw",
      "dojo/dom",
      "dojo/on",
      "dojo/parser",
      "dojo/_base/array",
      "esri/Color",
      "dojo/keys",
      "esri/SnappingManager",
      "esri/sniff",
      "dijit/form/Button",
      "dojo/domReady!"
    ],
      function (
        InfoTemplate, Map, FeatureLayer, SimpleFillSymbol, SimpleLineSymbol,
        Query, Draw, dom, on, parser, arrayUtil, Color, keys, SnappingManager, has
      ) {

        parser.parse();

        var selectionToolbar, featureLayer;

        map = new Map("map", {
          basemap: "topo",
          center: [-80.8431, 35.2271],
          zoom: 15
        });

        map.on("load", initSelectToolbar);

        var fieldsSelectionSymbol =
          new SimpleFillSymbol(SimpleFillSymbol.STYLE_SOLID,
            new SimpleLineSymbol(SimpleLineSymbol.STYLE_DASHDOT,
          new Color([255, 0, 0]), 2), new Color([255, 255, 0, 0.5]));

//        var content = "<b>Street Name</b>: ${WHOLESTNAME}" +
 //                     "<br><b>Street Range</b>: ${FULL_NAME}" +
 //                     "<br><b>Length</b>: ${SHAPE.STLength}";
  //      var infoTemplate = new InfoTemplate("${WHOLESTNAME}", content);

        featureLayer = new FeatureLayer("http://maps.ci.charlotte.nc.us/arcgis/rest/services/NBS/StreetAdoption/MapServer/2",
          {
            mode: FeatureLayer.MODE_ONDEMAND,
           // infoTemplate: infoTemplate,
            outFields: ["*"]
          });

        featureLayer.on("selection-complete", sumMilage);
        featureLayer.on("selection-clear", function () {
          dom.byId('messages').innerHTML = "<i>No Selected Roads</i>";
        });
        map.addLayer(featureLayer);

        on(dom.byId("selectFieldsButton"), "click", function () {
          selectionToolbar.activate(Draw.POLYLINE);
        });

        on(dom.byId("clearSelectionButton"), "click", function () {
          featureLayer.clearSelection();
        });

        function initSelectToolbar (event) {
          selectionToolbar = new Draw(event.map);
          var selectQuery = new Query();
          var snapManager = event.map.enableSnapping({
            snapKey: has("mac") ? keys.META : keys.CTRL
          });
          var layerInfos = [{
            layer: featureLayer
          }];
          snapManager.setLayerInfos(layerInfos);

          on(selectionToolbar, "DrawEnd", function (geometry) {
            selectionToolbar.deactivate();
            selectQuery.geometry = geometry;
            featureLayer.selectFeatures(selectQuery,
              FeatureLayer.SELECTION_NEW);
          });
        }

        function sumMilage (event) {
          var milageSum = 0;
          //summarize the cumulative gas production to display
          arrayUtil.forEach(event.features, function (feature) {
            console.info(feature.attributes);
            milageSum += feature.attributes["Shape.STLength()"];
          });
          dom.byId('messages').innerHTML = "<b>Total Length of Segments Requested: " +
                                            milageSum + " Miles Selected </b>";
        }
      });
  </script>
</head>

<body class="claro">
  <button id="selectFieldsButton" data-dojo-type="dijit/form/Button">Select Roads</button>
  <button id="clearSelectionButton" data-dojo-type="dijit/form/Button">Clear Selection</button><br>
  <div id="map" style="position: relative; width:100%; height:500px; border:1px solid #000;"></div>
  <span id="messages"></span>
</body>

</html>

View solution in original post

2 Replies
RobertScheitlin__GISP
MVP Emeritus

Andrew,

  Here are the changes for adding the snapping and getting the length data:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <title>Layer in a map service - [ON-DEMAND]</title>

  <link rel="stylesheet" href="https://js.arcgis.com/3.23/dijit/themes/claro/claro.css">
  <link rel="stylesheet" href="https://js.arcgis.com/3.23/esri/css/esri.css" />

  <script src="https://js.arcgis.com/3.23/"></script>
  <script>
    var map;

    require([
      "esri/InfoTemplate",
      "esri/map",
      "esri/layers/FeatureLayer",
      "esri/symbols/SimpleFillSymbol",
      "esri/symbols/SimpleLineSymbol",
      "esri/tasks/query",
      "esri/toolbars/draw",
      "dojo/dom",
      "dojo/on",
      "dojo/parser",
      "dojo/_base/array",
      "esri/Color",
      "dojo/keys",
      "esri/SnappingManager",
      "esri/sniff",
      "dijit/form/Button",
      "dojo/domReady!"
    ],
      function (
        InfoTemplate, Map, FeatureLayer, SimpleFillSymbol, SimpleLineSymbol,
        Query, Draw, dom, on, parser, arrayUtil, Color, keys, SnappingManager, has
      ) {

        parser.parse();

        var selectionToolbar, featureLayer;

        map = new Map("map", {
          basemap: "topo",
          center: [-80.8431, 35.2271],
          zoom: 15
        });

        map.on("load", initSelectToolbar);

        var fieldsSelectionSymbol =
          new SimpleFillSymbol(SimpleFillSymbol.STYLE_SOLID,
            new SimpleLineSymbol(SimpleLineSymbol.STYLE_DASHDOT,
          new Color([255, 0, 0]), 2), new Color([255, 255, 0, 0.5]));

//        var content = "<b>Street Name</b>: ${WHOLESTNAME}" +
 //                     "<br><b>Street Range</b>: ${FULL_NAME}" +
 //                     "<br><b>Length</b>: ${SHAPE.STLength}";
  //      var infoTemplate = new InfoTemplate("${WHOLESTNAME}", content);

        featureLayer = new FeatureLayer("http://maps.ci.charlotte.nc.us/arcgis/rest/services/NBS/StreetAdoption/MapServer/2",
          {
            mode: FeatureLayer.MODE_ONDEMAND,
           // infoTemplate: infoTemplate,
            outFields: ["*"]
          });

        featureLayer.on("selection-complete", sumMilage);
        featureLayer.on("selection-clear", function () {
          dom.byId('messages').innerHTML = "<i>No Selected Roads</i>";
        });
        map.addLayer(featureLayer);

        on(dom.byId("selectFieldsButton"), "click", function () {
          selectionToolbar.activate(Draw.POLYLINE);
        });

        on(dom.byId("clearSelectionButton"), "click", function () {
          featureLayer.clearSelection();
        });

        function initSelectToolbar (event) {
          selectionToolbar = new Draw(event.map);
          var selectQuery = new Query();
          var snapManager = event.map.enableSnapping({
            snapKey: has("mac") ? keys.META : keys.CTRL
          });
          var layerInfos = [{
            layer: featureLayer
          }];
          snapManager.setLayerInfos(layerInfos);

          on(selectionToolbar, "DrawEnd", function (geometry) {
            selectionToolbar.deactivate();
            selectQuery.geometry = geometry;
            featureLayer.selectFeatures(selectQuery,
              FeatureLayer.SELECTION_NEW);
          });
        }

        function sumMilage (event) {
          var milageSum = 0;
          //summarize the cumulative gas production to display
          arrayUtil.forEach(event.features, function (feature) {
            console.info(feature.attributes);
            milageSum += feature.attributes["Shape.STLength()"];
          });
          dom.byId('messages').innerHTML = "<b>Total Length of Segments Requested: " +
                                            milageSum + " Miles Selected </b>";
        }
      });
  </script>
</head>

<body class="claro">
  <button id="selectFieldsButton" data-dojo-type="dijit/form/Button">Select Roads</button>
  <button id="clearSelectionButton" data-dojo-type="dijit/form/Button">Clear Selection</button><br>
  <div id="map" style="position: relative; width:100%; height:500px; border:1px solid #000;"></div>
  <span id="messages"></span>
</body>

</html>
by Anonymous User
Not applicable

Hey Robert!

I noticed something odd between the snapping behavior between this sample and one that uses the measurement widget (in the new version of the API). Specifically, the snapping is enforced on the draw event, not the selection event. Here is the sample I'm working with in that setup: 

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
    <title>Measure Tool</title>
    <link rel="stylesheet" href="https://js.arcgis.com/3.23/esri/themes/calcite/dijit/calcite.css">
    <link rel="stylesheet" href="https://js.arcgis.com/3.23/esri/themes/calcite/esri/esri.css">
    <style>
      html,body {
        height:100%;
        width:100%;
        margin:0;
      }
      body {
        background-color:#FFF;
        overflow:hidden;
        font-family:"Trebuchet MS";
      }
      #map {
        border:solid 2px #808775;
        -moz-border-radius:4px;
        -webkit-border-radius:4px;
        border-radius:4px;
        margin:5px;
        padding:0px;
      }
      #titlePane{
        width:280px;
      }
      #header {
      text-align: center;
      height: 60px;
      border-bottom: solid 1px #ccc;
    }
      #search {
         display: block;
         position: absolute;
         z-index: 2;
         top: 20px;
         left: 74px;
      }
      </style>
      <script src="https://js.arcgis.com/3.23/"></script>
    <script>
    var map;
    require([
        "dojo/dom",
        "esri/Color",
        "dojo/keys",
        "dojo/parser",

        "esri/config",
        "esri/sniff",
        "esri/map",
        "esri/dijit/Search",
        "esri/SnappingManager",
        "esri/dijit/Measurement",
        "esri/layers/FeatureLayer",
        "esri/renderers/SimpleRenderer",
        "esri/tasks/GeometryService",
        "esri/symbols/SimpleLineSymbol",
        "esri/symbols/SimpleFillSymbol",

        "esri/dijit/Scalebar",
        "dijit/layout/BorderContainer",
        "dijit/layout/ContentPane",
        "dijit/TitlePane",
        "dijit/form/CheckBox",
        "dojo/domReady!"
      ], function(
        dom, Color, keys, parser,
        esriConfig, has, Map, Search, SnappingManager, Measurement, FeatureLayer, SimpleRenderer, GeometryService, SimpleLineSymbol, SimpleFillSymbol
      ) {
        parser.parse();
        //This sample may require a proxy page to handle communications with the ArcGIS Server services. You will need to
        //replace the url below with the location of a proxy on your machine. See the 'Using the proxy page' help topic
        //for details on setting up a proxy page.
        esriConfig.defaults.io.proxyUrl = "/proxy/";
        esriConfig.defaults.io.alwaysUseProxy = false;

        //This service is for development and testing purposes only. We recommend that you create your own geometry service for use within your applications
        esriConfig.defaults.geometryService = new GeometryService("https://utility.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer");

        map = new Map("map", {
          basemap: "gray",
          center: [-80.8431, 35.2271],
          zoom: 15
        });
        
         var search = new Search({
            map: map
         }, "search");
         
        var roadLine = new SimpleLineSymbol();
          roadLine.setWidth(2);
          roadLine.setColor(new Color([56, 168, 0, 1]));

        var roadsLayer = new FeatureLayer("http://maps.ci.charlotte.nc.us/arcgis/rest/services/NBS/StreetAdoption/MapServer/2", {
          mode: FeatureLayer.MODE_ONDEMAND,
          outFields: ["*"]
        });
        roadsLayer.setRenderer(new SimpleRenderer(roadLine));
        map.addLayers([roadsLayer]);

        var snapManager = map.enableSnapping({
          alwaysSnap : true 
        });
        var layerInfos = [{
          layer: roadsLayer
        }];
        snapManager.setLayerInfos(layerInfos);

        var measurement = new Measurement({
          map: map,
          defaultLengthUnit: "esriMiles",
        }, dom.byId("measurementDiv"));
        measurement.startup();
        
        dojo.connect(measurement, "onMeasureEnd", function(toolName, geometry){
          measurement.resultValue.domNode.innerHTML = "x: "+ geometry.x + "<br/>y: " + geometry.y;
   });
      });
    </script>
  </head>

  <body class="calcite">
    <div id="mainWindow" data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="design:'headline',gutters:false"
    style="width:100%; height:100%; z-Index: 949;">
    <div data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="gutters:false" 
      region="left" style="width: 25%;height:100%;">
      <div id="leftPane" data-dojo-type="dijit/layout/ContentPane" 
        data-dojo-props="region:'center'"></div>
      <div id="header" data-dojo-type="dijit/layout/ContentPane" 
        data-dojo-props="region:'top'">
        <div id="featureCount" style="margin-bottom:5px;">Input Your Info to Start Adopting Streets</div>
        <div id="pager" style="display:none;"> 
          <a href='javascript:void(0);' id ="previous" class='nav' style="text-decoration: none;">
              &lt; Prev
          </a> &nbsp; 
          <a href='javascript:void(0);' id="next" class='nav'  style="text-decoration: none;">
              Next &gt;
          </a>
        </div>
      </div>
    </div>
      <div id="map" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region:'center'">
        <div id="search"></div>
        <div style="position:absolute; right:20px; top:10px; z-Index:950;">
          <div id="titlePane" data-dojo-type="dijit/TitlePane" data-dojo-props="title:'Adopt Streets', closable:false">
            <div id="measurementDiv"></div>
            <span style="font-size:smaller;padding:5px 5px;">Click on the Roads You'd Like to Adopt</span>
          </div>
        </div>
      </div>
    </div>
    <div id="sidePanel" style=""></div>
  </body>
</html>

My ultimate goal is for snapping to be enabled on the draw event, on the selection event and for the selection (i.e. the selected features) to be highlighted/have a symbology change. I'll keep toying with it, but if there's a way you know how to do it by modifying the sample you provided, I'd be over the moon! 

Thanks and hope all is well!

0 Kudos