ThinkGeo.com    |     Documentation    |     Premium Support

OgcApiFeatureLayer and feature cache

Hi,

We are using LayerOverlay (with tile cache) and OgcApiFeatureLayer. With drawing map everything is working as expected and features for certain area are fetched only once and then tile cache is used to draw the map.

But when we use OgcApiFeatureLayer.QueryTools.GetFeaturesWithinDistanceOf() to get features near current GPS position on each API call new request is sent to server causing lots of network traffic (also subscription is billed based on count of request so this causes lot’s of costs as well). It looks like that FeatureSource contains FeatureCache can that be used to get rid of those requests (I tried to set it as active but it did not have any effect) ?

Br, Simo

Hi Simo,

OgcApi layer doesn’t support caching. The Ogc API servers are not based on XYZ (which is easy to cache intuitively) but based on extents. So whenever GetFeaturesWithinDistanceOf is called, a new request is sent to the server.

You can implement the cache by yourself using TileMatrix. Here I created a quick demo for you. Please read the comments and make sure you understand the code, as you need to make changes to fit your server. Also please pull the latest beta 14.4.0-beta008, which fixed the issue where an exception is thrown when loading the geoJson if it is empty.

class MyOgcApiFeatureLayer : OgcApiFeatureLayer
{
    public MyOgcApiFeatureLayer(string url, string collectionId, int bulkSize = 100)
        : base(url, collectionId, bulkSize)
    {
        this.FeatureSource = new MyOgcApiFeatureSource(url, CollectionId, bulkSize);
    }
}

class MyOgcApiFeatureSource : OgcApiFeatureSource
{
    private TileMatrix tileMatrix;

    public MyOgcApiFeatureSource(string url, string collectionId, int bulkSize = 100)
        : base(url, collectionId, bulkSize)
    {   }

    protected override void OpenCore()
    {
        base.OpenCore();

        var bbox = GetBoundingBoxCore();

        // This is very important! It's different based on the server. You don't want each cell be too large or too small. 
        // The following args means supposing the scale is 100000, I create a grids system, in which each grid is 256pixel * 256pixel, to cover the bbox. 
        tileMatrix = new TileMatrix(100000, 256, 256, bbox, GeographyUnit.DecimalDegree);

        // The following code gave you an idea how many cells it would be in the matrix to cover the entire bbox. 
        var columnCount = tileMatrix.GetColumnCount();
        var rowCount = tileMatrix.GetRowCount();
    }

    protected override Collection<Feature> GetFeaturesInsideBoundingBoxCore(RectangleShape boundingBox, IEnumerable<string> returningColumnNames)
    {
        //return base.GetFeaturesInsideBoundingBoxCore(boundingBox, returningColumnNames);

        var features = new Collection<Feature>();

        // get the cells in the matrix, which covers the giving boundingBox
        var cells = tileMatrix.GetIntersectingCells(boundingBox);

        foreach (var cell in cells)
        {
            var cellBbox = cell.BoundingBox;
            var cachFileName = $"{cell.X}-{cell.Y}.cache";

            var featuresInOneCell = new Collection<Feature>();

            if (System.IO.File.Exists(cachFileName)) // load it from cache
            {
                var geoJson = File.ReadAllText(cachFileName);
                featuresInOneCell = Feature.CreateFeaturesFromGeoJson(geoJson);
            }
            else // cache not exist
            {
                featuresInOneCell = base.GetFeaturesInsideBoundingBoxCore(cellBbox, returningColumnNames);
               // here I just simply save it to json.
                var geoJson = featuresInOneCell.GetGeoJson();
                File.WriteAllText(cachFileName, geoJson); // write to cache
            }

            foreach (var feature in featuresInOneCell)
                features.Add(feature);
        }

        return features;
    }
}

Thanks,
Ben

Hi,

In our case GPS position can change several times (~10 times max) in second so I’m not sure if this kind of cache solution using file system would be feasible e.g from performance point of view. I think that we need to figure out more lightweight solution to do this.

Br, Simo

Hi Simo,

You are gonna build the cache when rendering OgcApiFeatureLayer in the LayerOverlay, don’t need to build the cache for GPS query as long as the query area is within the current extent.

Thanks,
Ben

Hi,

Yes, building cache happens on while rendering but if I understood correctly using it would require searching/opening those files from file system as well. It might cause some performance issues if done frequently.

BTW, I’m not that familiar with these online maps so how to figure out that scale you we using on your example ?

Br, Simo

Hi Simo,

I’m a bit confused; I think the goal is to avoid sending request to the server when calling OgcApiFeatureLayer.QueryTools.GetFeaturesWithinDistanceOf(), is that right? If yes, it should be fine if we build up the cache on the local file system, unless you are looking for caching everything into memory or some other kind of caching?


Here is some quick description about scale:

Scale is simply the ratio between a distance on your screen and the corresponding distance in the real world. For instance, a scale of 1000 means that 1 inch on your screen represents 1000 inches in reality.

Let’s say you want to create a tile matrix where 100 pixels on the screen represent 50 meters on the ground. What scale would that be? Let’s break it down:

  1. Convert pixels to inches:
    100 pixels ≈ 1.04 inches (assuming 96 DPI, because 100 / 96 ≈ 1.04).
  2. Calculate ground distance per inch:
    If 1.04 inches correspond to 50 meters, then 1 inch represents about 50 / 1.04 ≈ 48 meters .
  3. Convert meters to inches:
    Since 1 meter is approximately 39.37 inches, then 48 meters is about 48 × 39.37 ≈ 1890 inches .
    This means one inch on the screen represents about 1890 inches in the real world, giving you a scale of roughly 1:1890 .

Using the ThinkGeo API
If 100 pixels represent 50 meters, the resolution is 0.5 meters per pixel. You can then get the scale like this:

var scale = MapUtil.GetScaleFromResolution(0.5, GeographyUnit.Meter);


So, let’s say the bbox of the server is 500 meter by 500 meter, scale of 1890 represents 0.5 meter per pixel, so the 500x500 meter extent can be present by a 1000x1000 pixel image. So the following code means creating a tileMatrix using 256x256 pixel tile to fill up the 1000x1000 extent.

tileMatrix = new TileMatrix(1890, 256, 256, bbox, GeographyUnit.Meter);

Thanks,
Ben