Esri Leaflet

Editing feature layers

This sample uses Leaflet Draw to help edit the geometry of features in a hosted feature service.

<html>
<head>
  <meta charset=utf-8 />
  <title>Editing feature layers</title>
  <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />

  <!-- Load Leaflet from CDN-->
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.3/dist/leaflet.css" />
  <script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet-src.js"></script>

  <!-- Load Esri Leaflet from CDN -->
  <script src="https://unpkg.com/esri-leaflet@2.0.7"></script>

  <style>
    body { margin:0; padding:0; }
    #map { position: absolute; top:0; bottom:0; right:0; left:0; }
  </style>
</head>
<body>

<!-- Leaflet Draw -->
<script src="https://unpkg.com/leaflet-draw@0.4.7/dist/leaflet.draw-src.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw@0.4.7/dist/leaflet.draw-src.css">

<style>
  #info-pane {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 400;
    padding: 1em;
    background: white;
    text-align: right;
  }

  #form {
    display: none;
  }
</style>

<div id='map'></div>
<div id='info-pane' class='leaflet-bar'>
  <label id='greeting'>
    Let's edit!
  </label>
  <form action='#' id='form'>
    <label for='PEDDISTRIC'>
      Pedestrian District Name<br>
      <input id='PEDDISTRIC' type="text" value='' name='PEDDISTRIC'><br>
    </label>
    <label for='TRANPLANID'>
      Transportation Plan Id<br>
      <input id='TRANPLANID' type='text' value='' name='TRANPLANID' disabled='disabled'>
    </label>
  </form>
</div>
<script>
  // create the map
  var map = L.map('map').setView([45.512, -122.619], 12);
  L.esri.basemapLayer('Streets').addTo(map);

  // add our feature layer to the map
  var pedestrianDistricts = L.esri.featureLayer({
    url: 'https://services.arcgis.com/rOo16HdIMeOBI4Mb/arcgis/rest/services/PDX_Pedestrian_Districts/FeatureServer/0'
  }).addTo(map);

  // variable to track the layer being edited
  var currentlyEditing;
  var currentlyDeleting = false;

  // create a feature group for Leaflet Draw to hook into for delete functionality
  var drawnItems = L.featureGroup();
  map.addLayer(drawnItems);

  // track if we should disable custom editing as a result of other actions (create/delete)
  var disableEditing = false;

  // start editing a given layer
  function startEditing(layer) {
    document.getElementById("PEDDISTRIC").value = layer.feature.properties.PEDDISTRIC;
    // read only
    document.getElementById("TRANPLANID").value = layer.feature.properties.TRANPLANID;
    if (!disableEditing) {
      layer.editing.enable();
      currentlyEditing = layer;
    }
  }

  // stop editing a given layer
  function stopEditing() {
    // if a layer is being edited, finish up and disable editing on it afterward.
    if (currentlyEditing) {
      handleEdit(currentlyEditing);
      currentlyEditing.editing.disable();
    }
    currentlyEditing = undefined;
  }

  function handleEdit(layer) {
    // convert the layer to GeoJSON and build a new updated GeoJSON object for that feature
    layer.feature.properties.PEDDISTRIC = document.getElementById("PEDDISTRIC").value;
    pedestrianDistricts.updateFeature({
      type: 'Feature',
      id: layer.feature.id,
      geometry: layer.toGeoJSON().geometry,
      properties: layer.feature.properties
    }, function(error, response) {
      displayGreeting();
    });
  }

  function displayAttributes() {
    document.getElementById("greeting").innerHTML = null;
    document.getElementById("form").style.display = 'block';
  }

  function displayGreeting() {
    document.getElementById("greeting").innerHTML = "Lets edit!";
    document.getElementById("form").style.display = 'none';
  }

  // when the map is clicked, stop editing
  map.on('click', function(e) {
    stopEditing();
  });

  // when a pedestrian district is clicked, stop editing the current feature and edit the clicked feature
  pedestrianDistricts.on('click', function(e) {
    stopEditing();
    startEditing(e.layer);
    if (!currentlyDeleting) {
      displayAttributes();
    }
    // make sure map click event isn't fired. (seems like a bug)
    L.DomEvent.stopPropagation(e);
  });

  // when pedestrian districts start loading (because of pan/zoom) stop editing
  pedestrianDistricts.on('loading', function() {
    stopEditing();
  });

  // when new features are loaded clear our current guides and feature groups
  // then load the current features into the guides and feature group
  pedestrianDistricts.on('load', function() {
    // wipe the current layers available for deltion and clear the current guide layers.
    drawnItems.clearLayers();

    // for each feature push the layer representing that feature into the guides and deletion group
    pedestrianDistricts.eachFeature(function(layer) {
      drawnItems.addLayer(layer);
    });
  });

  // create a new Leaflet Draw control
  var drawControl = new L.Control.Draw({
    edit: {
      featureGroup: drawnItems, // allow editing/deleting of features in this group
      edit: false // disable the edit tool (since we are doing editing ourselves)
    },
    draw: {
      circle: false, // disable circles
      marker: false, // disable polylines
      polyline: false, // disable polylines
      polygon: {
        allowIntersection: false, // polygons cannot intersect thenselves
        drawError: {
          color: 'red', // color the shape will turn when intersects
          message: '<strong>Oh snap!<strong> you can\'t draw that!' // message that will show when intersect
        },
      }
    }
  });

  // add our drawing controls to the map
  map.addControl(drawControl);

  // when we start using creation tools disable our custom editing
  map.on('draw:createstart', function() {
    disableEditing = true;
  });

  // when we start using deletion tools, hide attributes and disable custom editing
  map.on('draw:deletestart', function() {
    disableEditing = true;
    currentlyDeleting = true;
  });

  // listen to the draw created event
  map.on('draw:created', function(e) {
    // add the feature as GeoJSON (feature will be converted to ArcGIS JSON internally)
    pedestrianDistricts.addFeature(e.layer.toGeoJSON());
    disableEditing = false;
  });

  // listen to the draw deleted event
  map.on('draw:deleted', function(e) {
    var delArray = [];
    e.layers.eachLayer(function(layer) {
      var id = layer.feature.id;
      delArray.push(id);
    });
    pedestrianDistricts.deleteFeatures(delArray, function(error, response) {
      if (error) {
        console.log(error, response);
      }
    });
    disableEditing = false;
    currentlyDeleting = false;
  });
</script>

</body>
</html>