Why do it?

To load and visualize vector data, LuciadRIA uses a loading strategy mechanism that determines how and when data is requested from an underlying data source. Understanding how the loading strategies work and how you can tweak the mechanism can help you improve the data loading performance and achieve specific user requirements.

How do the Loading strategies work?

LuciadRIA offers two alternative loading strategies: LoadEverything and LoadSpatially.

  • LoadEverything: instructs LuciadRIA to load all data at once, when a layer is added to the map. This is the preferred approach when the data set is relatively small and static.

  • LoadSpatially: queries the model for data inside the current view only, with a margin for expansion. If the map extent changes, the layer submits a new query to the model for features inside the new extent. This approach is suitable for dealing with a large amount of static or possibly dynamic data. You can tell LuciadRIA explicitly to use one of the loading strategies through the API, or you can let LuciadRIA implicitly decide which of the two strategies is selected.

If you do not explicitly configure the loading strategy on a feature layer, LuciadRIA selects the LoadSpatially strategy if the FeatureModel of the FeatureLayer supports spatial querying. A FeatureModel supports spatial querying only if its Store implements the spatialQuery method. Otherwise, the LoadEverything data loading strategy is used.

On the other hand, if you explicitly configure the LoadSpatially strategy, but the Store of the underlying model does not implement the spatialQuery method, LuciadRIA will switch to the LoadEverything mechanism.

WFSFeatureStore implements the optional spatialQuery method . This means that LuciadRIA will choose the LoadSpatially strategy when the model Store is WFSFeatureStore.

The role of the query provider

A loading strategy makes use of a QueryProvider. It specifies what data must be requested from the underlying data source.

LuciadRIA uses the QueryProvider of the loading strategy to determine a query object for specific scale ranges of the view. The query object is a query parameter that describes a filter condition.

When a user zooms in and out on the map, the map scale changes. If it changes in such a way that it ends up in another scale range defined by the QueryProvider, the loading strategy directs LuciadRIA to re-load data using a query object that corresponds with that scale range.

The default QueryProvider object requests all data for all scales.

Loading strategies and data re-loading

The data can be re-loaded in the following cases:

  • The map scale changes and crosses a scale break defined by the loading strategy’s QueryProvider.

  • LoadingStrategy.queryProvider.invalidate() is invoked by a user, indicating that the query must be refreshed.

  • The LoadSpatially strategy discovers that the view extent has changed

  • The loading strategy has the refresh property, that accepts:

    • number - the loading strategy refreshes every n milliseconds.

    • Date - the loading strategy will refresh once at that time.

    • null - the loading strategy never refreshes.

The re-loading behavior depends on the selected loading strategy:

  • LoadEverything: All the previous data is removed when the new vector data is loaded.

  • LoadSpatially: By default, new features are handled in this way:

    • Existing features that are absent in the new data are removed.

    • New features that did not exist in the previously loaded data are added.

    • New features that already existed in the previously loaded data are disregarded.

This default re-load approach works for static data with features that do not change. For dynamic data, however, the updates will not be reflected. To change this default behavior for dynamic data, you can provide a custom implementation for the LoadingStrategy.shouldUpdate function predicate. See Use case 5: big dynamic data depending on scale for an example.

For LuciadRIA 2018.1 and more recent versions

In a 3D scene, there can be areas that represent more than one scale level, as defined by the QueryProvider. In that case, LoadSpatially automatically creates the visible scale level zones and submits spatialQuery requests for each visible zone by passing the bounds of the zone and the corresponding query object. This multi-scale approach in 3D scenes reduces the memory usage as well as the visual clutter.

How to do it?

Use case 1: small data sets

Load all vector data in one go with the LoadEverything strategy. The default QueryProvider instance is used implicitly.

const wfsLayer = new FeatureLayer(wfsModel, {
  loadingStrategy: new LoadEverything()
});

Use case 2: Small data sets depending on scale

You can load all vector data in one go whenever scale breaks are crossed with the LoadEverything strategy and a custom QueryProvider.

class OGCQueryProvider extends QueryProvider {

  // 2 scale breaks define 3 scale levels (scale ranges)
  getQueryLevelScales(layer: FeatureLayer, map: Map) {
    return [
      1.0 / 20000000,
      1.0 / 500000
    ];
  };

  // Provides query objects for 3 scale levels: no data requested for level 0, request with a filter object for level 1, no restriction for most detailed level
  getQueryForLevel(level: number) {
    if (level == 0) {
      return QueryProvider.QUERY_NONE; // no data requested
    } else if (level === 1) {
      return {
        filter: eq(property("fclass"), literal("A")) // OGC filter
      }
    } else {
      return null; // no restriction
    }
  }
}

const wfsLayer = new FeatureLayer(wfsModel, {
  loadingStrategy: new LoadEverything({queryProvider: new OGCQueryProvider()})
});

Use case 3: big static data

Load all vector data for the visible view extent with the LoadSpatially strategy, using the default QueryProvider instance implicitly.

const wfsLayer = new FeatureLayer(wfsModel, {
  loadingStrategy: new LoadSpatially()
});

Use case 4: big static data depending on scale

You can load all vector data in one go whenever scale breaks are crossed using the LoadSpatially strategy with a custom QueryProvider.

const wfsLayer = new FeatureLayer(wfsModel, {
  loadingStrategy: new LoadSpatially({queryProvider: new CustomQueryProvider()})
});

Use case 5: big dynamic data depending on scale

You can load all vector data in one go whenever scale breaks are crossed using the LoadSpatially strategy with a custom QueryProvider.

The LoadSpatially instance defines a merging strategy for dynamically modified data based on a custom property.

const loadingStrategy = new LoadSpatially({queryProvider: new CustomQueryProvider()});
// will update a feature if the revision has changed
loadingStrategy.shouldUpdate = function(existingFeature, feature) {
  const {revision} = feature.properties;
  return revision && revision > existingFeature.properties.revision;
}
const wfsLayer = new FeatureLayer(wfsModel, { loadingStrategy });

You can consider clustering features if you still have a need for limiting the visual clutter of large amounts of data on your map.

Control data updates:

The data can be updated on request:

// no need to invalidate feature layer's painter, as updated features will be repainted automatically
wfsLayer.loadingStrategy.queryProvider.invalidate();

You can also set on a loading strategy the refresh property that specifies a time-based refresh mode. The example below shows how to automatically re-load data every 10 minutes.

const interval = 1000 * 60 * 10; // refresh every 10 minutes
const wfsLayer = new FeatureLayer(wfsModel, {
  loadingStrategy: new LoadEverything({refresh: interval})
});

// you can change the refresh property any time
wfsLayer.loadingStrategy.refresh = null; // no automatic refreshes