0

The OpenLayers folks gave us a good starting off point with https://openlayers.org/en/latest/examples/vector-esri.html but it is lacking in at least one way. If you modify that example by setting the initial zoom level to 0 the request for features will fail to return all of them. Only one query will return results:

https://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Petroleum/KSFields/FeatureServer/0/query/?f=json&returnGeometry=true&spatialRel=esriSpatialRelIntersects&geometry=%7B%22xmin%22%3A-20037508.342789244%2C%22ymin%22%3A0%2C%22xmax%22%3A0%2C%22ymax%22%3A20037508.342789244%2C%22spatialReference%22%3A%7B%22wkid%22%3A102100%7D%7D&geometryType=esriGeometryEnvelope&inSR=102100&outFields=*&outSR=102100&callback=jQuery351038373015585341097_1629727417011&_=1629727417032

This URL will return 1000 features and the tileStrategy (or maybe VectorSource) has no idea that this is an indication of a partial result and happily marks that tile as fully loaded.

So my question is directed to anyone that as either solved or is aware of an existing solution to effectively render ArcGIS FeatureServer data on an OpenLayers map.

I have found one very bad solution:

map.on('moveend', () => {
  vectorSource.refresh();
});

It will clear the entire tile cache and re-query for all features. I would like to limit the "refresh" to just those tile that represent 1000 features (or whatever represents the max feature count).

The beginnings of a solution may be to record all extents that return 1000 features and not even render them:

  if (1000 === features.length) {
    extentsToClear.push(extent);
  } else {
    vectorSource.addFeatures(features);
  }

When the map extent changes, clear these extents:

map.on("moveend", () => {
  while (extentsToClear.length) {
    vectorSource.removeLoadedExtent(extentsToClear.pop());
  }
});

2 Answers 2

1

The simplest, but not guaranteed way, would be to ensure that requests are always small by setting a minZoom on the strategy tilegrid. So instead of one large request at level 9 there would be 4 smaller requests at level 10:

  strategy: tileStrategy(
    createXYZ({
      tileSize: 512,
      minZoom: 10
    })
  ),

The alternative, which would need more coding, would be to test features.length in the loader and if it is 1000 instead of returning success to divide the extent and try each of those again inside the loader (multiple fetches could be handled using Promise.all).

3
  • I like the minZoom idea! I was unaware of that option. The 4x split of the extent could get very expensive...imagine a FeatureService with 1 million features. I was thinking a hybrid clusting solution might be out there.
    – ca0v
    Aug 23, 2021 at 15:07
  • 1
    This opens at zoom 7 and loads many small tiles instead of a few big ones codesandbox.io/s/vector-esri-forked-12pvq Consequently you will see far more features than in the original example when it opened at the same level.
    – Mike
    Aug 23, 2021 at 15:30
  • I like it but my preference is to instead reduce the tileSize. For this sample I used a tile size of 64 (see it at codesandbox.io/embed/…). But yes, modifying the createXYZ options seems to be the quick solution.
    – ca0v
    Aug 23, 2021 at 15:40
1

Here is a solution that blots out the areas that are returning an incomplete feature set and re-queries that area when the map changes. A more advanced solution might wait until the zoom level exceeds the present one. This at least give the user an impression of an incomplete result set.

enter image description here

Zoom in once more and the box drops away:

enter image description here

This was achieved by creating a placeholder feature:

  if (1000 === features.length) {
    extentsToClear.push(extent);
    const feature = makeFeature(extent);
    featuresToClear.push(feature);
    vectorSource.addFeatures([feature]);
  } else {
    vectorSource.addFeatures(features);
  }

And clearing the feature and the problematic extent whenever the map extent change (a better solution would be to wait until the zoom level increases):

map.on("moveend", () => {
  while (extentsToClear.length) {
    vectorSource.removeLoadedExtent(extentsToClear.pop());
  }
  while (featuresToClear.length) {
    vectorSource.removeFeature(featuresToClear.pop());
  }
});

You can try it here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.