Recommended pattern for updating layer data

2983
11
Jump to solution
12-18-2017 06:25 PM
yangwen
New Contributor III

Background: My map is created by instantiating feature layers via the API.  I'm not interfacing with an ArcGIS MapServer directly.

For my map's layers, I have a need to update the attributes associated with each geometry in the layer.  The reason is the attribute data determines the color of each geometry's symbol.  So imagine an update mechanism that gets the latest attribute values from the server.

Right now, I have a fairly crude async timer that periodically does the following actions to remove existing layers, and then instantiates new layers using the updated data (layersData)

this.map.layers.removeAll();
this.addDataToMap(layersData, this.map);

While this works, I end up with a quick flash of the plotted symbols whenever the layers get removed and re-added.  Is there another pattern allowable by the JS API that allows more seamless update/replacement of active layers?

One thing to note, the plotted graphics may not all have unique ids.  So this makes a delta detection approach problematic.  I basically just want a way to replace the existing layers with new layers, without a UI reflow of the empty layers state.  Thanks

0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Emeritus

Yang,

   You are correct. My code was for 3.x. I did not see any tags or text in your question where you mentioned 4.x. In 4.x much has changed. The FeatureLayer has a source property which take a collection of graphics. You can add your initial graphics to the source property and then take the FeatureLayer.source.removeAll() to clear and FeatureLayer.source.addMany(arrayOfGraphics) to add new ones.

View solution in original post

11 Replies
RobertScheitlin__GISP
MVP Emeritus

Yang,

   If you continue with your current workflow of removing the layer and adding a new layer back you will never get rid of the flash. The better approach is to use a FeatureLayer from a Collection and clear the layer each time a new request is made for the data and re add the new graphics to the FeatureLayer each refresh of your attributes.

I use this approach in my apps where the data is not coming from ArcGIS Server.

//Example featureCollection    
      featureCollection = {
        "layerDefinition": null,
        "featureSet": {
          "features": [],
          "geometryType": "esriGeometryPoint"
        }
      }
//example featureCollection layerDefinition
    featureCollection.layerDefinition = {
      "geometryType": "esriGeometryPoint",
      "objectIdField": "ObjectID",
      "drawingInfo": {
        "renderer": {
          "type": "simple",
          "symbol": {
            "type": "esriPMS",
            "url": "images/i_unit3.png",
            "contentType": "image/png",
            "width": 60 * 72 / 96,
            "height": 29 * 72 / 96
          }
        }
      },
      "fields": [{
        "name": "ObjectID",
        "alias": "ObjectID",
        "type": "esriFieldTypeOID"
      }, {
        "name": "ID",
        "alias": "ID",
        "type": "esriFieldTypeString"
      }, {
        "name": "UnitID",
        "alias": "Unit ID",
        "type": "esriFieldTypeString"
      }, {
        "name": "Lat",
        "alias": "Latitude",
        "type": "esriFieldTypeDouble"
      }, {
        "name": "Lon",
        "alias": "Longitude",
        "type": "esriFieldTypeDouble"
      }, {
        "name": "Date_Time",
        "alias": "Date Time",
        "type": "esriFieldTypeDate"
      }, {
        "name": "description",
        "alias": "Description",
        "type": "esriFieldTypeString"
      }, {
        "name": "title",
        "alias": "Title",
        "type": "esriFieldTypeString"
      }]
    };

    featureLayer = new FeatureLayer(featureCollection, {
       id: "unitLayer",
       infoTemplate: popupTemplate
    });
    map.addLayer(featureLayer);



//In your update function
featureLayer.clear();

// before your loop of the results
var features = [];

//inside the loop of your results
features.push(graphic);

//After the loop
featureLayer.applyEdits(features, null, null);
yangwen
New Contributor III

Thanks for reply.  You seem to be referencing 3.x JS API.  Looking at the 4.x API, I don't see any equivalents methods..  For example,


The 4.x FeatureLayer class does not mention any concept of "clearing" the graphics within the layer.  FeatureLayer | API Reference | ArcGIS API for JavaScript 4.6 

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Yang,

   You are correct. My code was for 3.x. I did not see any tags or text in your question where you mentioned 4.x. In 4.x much has changed. The FeatureLayer has a source property which take a collection of graphics. You can add your initial graphics to the source property and then take the FeatureLayer.source.removeAll() to clear and FeatureLayer.source.addMany(arrayOfGraphics) to add new ones.

yangwen
New Contributor III

Robert, so the way you described for 4.x is almost identical to how I'm doing it, as mentioned in my original post.  The only exception is I'm accessing the layers via the map object via map.layers.removeAll().  I assume the way I'm calling removeAll has the same effect as accessing the featurelayer.source.removeAll().  This pattern results in the flashing of symbols due to the empty state between removAll and addMany.  I'm trying to avoid this behavior.

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Yang,

   No they are not similar at all. You are removing the whole layer from the map causing a map refresh I am removing graphics from the FeatureLayer cause a layer to redraw.

yangwen
New Contributor III

Yes your suggestion worked! thanks

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Yang,

Don't forget to mark this question as answered by clicking on the "Mark Correct" link on the reply that answered your question.

0 Kudos
by Anonymous User
Not applicable

Hey Robert! 

Would you able to post a "more complete" version of this solution (i.e. the actual update functions // how you're getting the graphic data)?

I have an app that generates an updated json page (url) that is invoked in an ajax dataTable that I'm trying to sync up with the mapping component. Right now my method is:

1. connect to json URL and store the result as response;

2. Use the response's data to create a graphic so the fields map and the geometry fields are specified;

3. Use these graphic to create a new FeatureLayer; and

4. Add that FeatureLayer to the map (i.e. 'map.add(layer); return layer;')

I've tried putting a simple JS setInterval iterator to loop over the whole function, but not having any success with that method. If you could share how you accomplished this (or if I'm missing something) I'd be super appreciative! 

Cheers,

A

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Andrew, 

  My code for this is part of bigger projects each time so it is harder to dissect just the portion you need but here is an attempt.

// the request to the web service
      var requestHandle = esriRequest({
        url: urlstr,
        handleAs: "json",
        timeout: 10000
      }, {
        useProxy: true,
        usePost: false
      });

      requestHandle.then(requestSucceeded, requestFailed);

function requestSucceeded(response, io) {
  try {
    featureLayer.clear();
    var features = [];
    response.forEach(function (unit) {
      var attr = {};
      //code to populate the attributes
      attr.ID = unit.Id;
      attr.Lat = unit.Lat;
      attr.Lon = unit.Lon;
      ....
      var geometry = new Point(unit.Lon, unit.Lat),
        wm = webMercatorUtils.geographicToWebMercator(geometry),
        graphic = new Graphic(wm);
      graphic.setAttributes(attr);
      //make sure we only add the same unit once
      if (uniqueIDs.indexOf(unit.UnitId) === -1) {
        features.push(graphic);
        uniqueIDs.push(unit.UnitId);
        ....
      }
      if(features.length){
        featureLayer.applyEdits(features, null, null);
      }
    }
  }
}
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍