Use Geometry Engine to show buffered geometry with Sketch View Model

2827
10
04-16-2018 05:05 PM
BlakeTerhune
MVP Regular Contributor

I want to combine the SketchViewModel for sketching temporary geometries with the GeometryEngine to show a buffer around the graphic as it's being drawn. I can't figure out how to listen for create sketch in order to generate the buffer on mousemove.

0 Kudos
10 Replies
_____
by
New Member

//

0 Kudos
BlakeTerhune
MVP Regular Contributor

I would prefer to get an event directly from the SketchViewModel. Something like "draw-complete" but for "draw-create." It looks like the on() method will let you register an event-handler but I'm not sure how to use it. Wish there were some samples or even a list of valid events!

0 Kudos
BlakeTerhune
MVP Regular Contributor

I guess I just needed to wait a week. Found this gem in the ArcGIS API for JavaScript 4.7 release notes:

Added events draw-cancel, draw-start, update-cancel, update-complete, update-start to esri/widgets/Sketch/SketchViewModel

SketchViewModel | API Reference | ArcGIS API for JavaScript 4.7 

BlakeTerhune
MVP Regular Contributor

I'm still struggling to fully implement this, so please feel free to post if you have any ideas!

0 Kudos
BlakeTerhune
MVP Regular Contributor

So here's what I came up with so far based on the new 4.7 sketch example.

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <title>Sketch temporary geometries - 4.7</title>

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

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

    #topbar {
      background: #fff;
      position: absolute;
      top: 15px;
      right: 15px;
      padding: 10px;
    }

    .action-button {
      font-size: 16px;
      background-color: transparent;
      border: 1px solid #D3D3D3;
      color: #6e6e6e;
      height: 32px;
      width: 32px;
      text-align: center;
      box-shadow: 0 0 1px rgba(0, 0, 0, 0.3);
    }

    .action-button:hover,
    .action-button:focus {
      background: #0079c1;
      color: #e4e4e4;
    }

    .active {
      background: #0079c1;
      color: #e4e4e4;
    }
  </style>

  <script>
    require([
      "esri/views/MapView",
      "esri/Map",
      'esri/geometry/geometryEngine',
      "esri/widgets/Sketch/SketchViewModel",
      "esri/Graphic",
      "esri/layers/GraphicsLayer",
      "dojo/domReady!"
    ], function(
      MapView, Map,
      geometryEngine,
      SketchViewModel, Graphic, GraphicsLayer
    ) {

      // GraphicsLayers to hold graphics created via sketch view model
      var tempGraphicsLayer = new GraphicsLayer();
      var bufferGraphicsLayer = new GraphicsLayer();
      
      // Other global vars
      var updateGraphic, sketchStartEvent, viewPointerMoveEvent, bufferGraphic;
      var bufferDistance = 1000;
      var unit = 'kilometers'
      var unionResults = true;

      // Arctic Ocean Basemap
      var map = new Map({
        basemap: "gray",
        layers: [tempGraphicsLayer, bufferGraphicsLayer]
      });

      var view = new MapView({
        container: "viewDiv",
        map: map,
        zoom: 3
      });

      var pointSymbol = { // symbol used for points
        type: "simple-marker", // autocasts as new SimpleMarkerSymbol()
        style: "square",
        color: "#8A2BE2",
        size: "16px",
        outline: { // autocasts as new SimpleLineSymbol()
          color: [255, 255, 255],
          width: 3 // points
        }
      }
      var polylineSymbol = { // symbol used for polylines
        type: "simple-line", // autocasts as new SimpleLineSymbol()
        color: "#8A2BE2",
        width: "4",
        style: "dash"
      }

      var polygonSymbol = { // symbol used for polygons
        type: "simple-fill", // autocasts as new SimpleFillSymbol()
        color: "rgba(138,43,226, 0.8)",
        style: "solid",
        outline: {
          color: "white",
          width: 1
        }
      }

      view.when(function() {
        // create a new sketch view model
        var sketchViewModel = new SketchViewModel({
          view: view,
          layer: tempGraphicsLayer,
          pointSymbol: pointSymbol,
          polylineSymbol: polylineSymbol,
          polygonSymbol: polygonSymbol
        });

        setUpClickHandler();
        
        // ************************************************************
        // Get the completed graphic from the event and add it to view.
        // This event fires when user presses
        //  * "C" key to finish sketching point, polygon or polyline.
        //  * Double-clicks to finish sketching polyline or polygon.
        //  * Clicks to finish sketching a point geometry.
        // ***********************************************************
        sketchViewModel.on("draw-complete", addGraphic);
        sketchViewModel.on("update-complete", addGraphic);
        sketchViewModel.on("update-cancel", addGraphic);

        function addGraphic(evt) {
          viewPointerMoveEvent.remove();
          
          var geometry = evt.geometry;
          var symbol;

          // Choose a valid symbol based on return geometry
          switch (geometry.type) {
            case "point":
              symbol = pointSymbol;
              break;
            case "polyline":
              symbol = polylineSymbol;
              break;
            default:
              symbol = polygonSymbol;
              break;
          }
          // Create a new graphic; add it to the GraphicsLayer
          var graphic = new Graphic({
            geometry: geometry,
            symbol: symbol
          });
          tempGraphicsLayer.add(graphic);
          
          // Recreate buffer with final sketch geometry.
          // The live buffer displayed before the sketch is complete can be
          // offset from the graphic if it was created while the map was
          // moving or redrawing.
          generateBufferGraphic(geometry);
          // Move buffer graphic from bufferGraphicsLayer to tempGraphicsLayer
          // so it persists with the temp graphic.
          // Generate new point buffer from the final sketch geometry.
          tempGraphicsLayer.add(bufferGraphicsLayer.graphics.items[0]);
          bufferGraphicsLayer.removeAll();
          
          // Remove stored reference to the updated graphic
          // Required in 'update-complete' callback specifically
          updateGraphic = null;
          
          // Reset sketch GraphicsLayer so the sketch event on
          // draw-start is cleared out for the next sketch
          sketchViewModel.reset();
        }

        // *************************************
        // activate the sketch to create a point
        // *************************************
        var drawPointButton = document.getElementById("pointButton");
        drawPointButton.onclick = function() {
          // set the sketch to create a point geometry
          sketchViewModel.create("point");
          setActiveButton(this);
        };

        // ****************************************
        // activate the sketch to create a polyline
        // ****************************************
        var drawLineButton = document.getElementById("polylineButton");
        drawLineButton.onclick = function() {
          // set the sketch to create a polyline geometry
          sketchViewModel.create("polyline");
          setActiveButton(this);
        };

        // ***************************************
        // activate the sketch to create a polygon
        // ***************************************
        var drawPolygonButton = document.getElementById("polygonButton");
        drawPolygonButton.onclick = function() {
          // set the sketch to create a polygon geometry
          sketchViewModel.create("polygon");
          setActiveButton(this);
        };

        // ***************************************
        // activate the sketch to create a rectangle
        // ***************************************
        var drawRectangleButton = document.getElementById(
          "rectangleButton");
        drawRectangleButton.onclick = function() {
          // set the sketch to create a polygon geometry
          sketchViewModel.create("rectangle");
          setActiveButton(this);
        };

        // ***************************************
        // activate the sketch to create a circle
        // ***************************************
        var drawCircleButton = document.getElementById("circleButton");
        drawCircleButton.onclick = function() {
          // set the sketch to create a polygon geometry
          sketchViewModel.create("circle");
          setActiveButton(this);
        };

        // **************
        // reset button
        // **************
        document.getElementById("resetBtn").onclick = function() {
          sketchViewModel.reset();
          tempGraphicsLayer.removeAll();
          bufferGraphicsLayer.removeAll();
          setActiveButton();
        };

        function setActiveButton(selectedButton) {
          // focus the view to activate keyboard shortcuts for sketching
          view.focus();
          var elements = document.getElementsByClassName("active");
          for (var i = 0; i < elements.length; i++) {
            elements[i].classList.remove("active");
          }
          if (selectedButton) {
            selectedButton.classList.add("active");
          }
        }

        // ************************************************************************************
        // set up logic to handle geometry update and reflect the update on "tempGraphicsLayer"
        // ************************************************************************************
        function setUpClickHandler() {
          view.on("click", function(evt) {
            view.hitTest(evt).then(function(response) {
              var results = response.results;
              // Found a valid graphic
              if (results.length && results[results.length - 1]
                .graphic) {
                // Check if we're already editing a graphic
                if (!updateGraphic) {
                  // Save a reference to the graphic we intend to update
                  updateGraphic = results[results.length - 1].graphic;
                  // Remove the graphic from the GraphicsLayer
                  // Sketch will handle displaying the graphic while being updated
                  tempGraphicsLayer.remove(updateGraphic);
                  sketchViewModel.update(updateGraphic.geometry);
                }
              }
            });
          });
        }

        // **************************************
        // sketchViewModel on draw-start
        // Fires after create() method is called.
        // **************************************
        sketchStartEvent = sketchViewModel.on('draw-start', sketchStartHandler);
        function sketchStartHandler(sketchEvt) {
          var sketchGeometry;
          viewPointerMoveEvent = view.on('pointer-move', viewPointerMoveHandler);
          function viewPointerMoveHandler(viewEvt) {
            if (sketchEvt.target.graphic && sketchEvt.target.draw.activeAction.vertices.length > 1) {
              sketchGeometry = sketchEvt.target.graphic.geometry;
            } else {
              // convert screen coordinates to map coordinates
              sketchGeometry = view.toMap({
                x: viewEvt.x,
                y: viewEvt.y
              });
            }
            generateBufferGraphic(sketchGeometry);
          };
        };

        // ***********************
        // Generate buffer graphic
        // ***********************
        function generateBufferGraphic(geometry, bufferDistance = 500, unit = 'kilometers', unionResults = true) {
          bufferGraphicsLayer.removeAll();
          var buffer = geometryEngine.geodesicBuffer(geometry, bufferDistance, unit, unionResults);
          bufferGraphicsLayer.add(new Graphic({
            geometry: buffer,
            symbol: polygonSymbol
          }));
        }

      });
    });
  </script>
</head>

<body>
  <div id="viewDiv">
    <div id="topbar">
      <button class="action-button esri-icon-blank-map-pin" id="pointButton" type="button"
        title="Draw point"></button>
      <button class="action-button esri-icon-polyline" id="polylineButton" type="button"
        title="Draw polyline"></button>
      <button class="action-button esri-icon-polygon" id="polygonButton" type="button"
        title="Draw polygon"></button>
      <button class="action-button esri-icon-checkbox-unchecked" id="rectangleButton" type="button"
        title="Draw rectangle"></button>
      <button class="action-button esri-icon-radio-unchecked" id="circleButton" type="button"
        title="Draw circle"></button>
      <button class="action-button esri-icon-trash" id="resetBtn" type="button" title="Clear graphics"></button>
    </div>
  </div>
</body>

</html>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

It seems to work but I'm not sure how best to remove the on pointer move event listener for the view. If you look at the console log, there are errors when drawing line or polygons. How do I remove the event listener so it doesn't keep running? I also tried using event.stopPropagation() but it looks like that doesn't actually remove the listener and might cause memory issues in the browser because it keeps running.

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Blake,

   You have a variable assigned to that event so you just need to call

viewPointerMoveEvent.remove();
0 Kudos
BlakeTerhune
MVP Regular Contributor

That's what I'm doing. However, when drawing a line I get three console errors and five console errors on polygons; something like:

[esri.views.input.InputManager] There is no InputHandler group registered under the name `viewEvents_13`

Point sketches don't seem to have the error.

RobertScheitlin__GISP
MVP Emeritus

Blake,

  I am not sure the source of those errors but I have confirmed through debugging that you mouse move event listerner is being removed.

0 Kudos
BlakeTerhune
MVP Regular Contributor

Thank you for taking the time to debug that. May I ask what method you used for testing/debugging? I'd like to reproduce your results.

0 Kudos