ThinkGeo.com    |     Documentation    |     Premium Support

Duplicating layers for labeling: performance question

I've seen in a number of threads on the forum that you recommend loading a layer twice when you need to label it, and placing the layer styled one on top so that labels display correctly, etc. We are using a Postgres/PostGIS system to store our layer data, and I just wanted to find out how the system handles it when two identical layers are loaded. The latency for fetching a layer in our environment can be up to 5 seconds, so I dont want to go back to the DB any more than I have to.


Does it go back to the database both times to fetch the data, or does it (somehow) recognise that this is the same layer, and just duplicate the existing one?


 


Would it be more performant for me to manually Clone() the layers I want to put labels on, and then add those FeatureLayers directly, rather than fetching the data from the DB again?



 


Janet,
Yes, Just as you worried, it will go back to the database twice to fetch the data if we use two identical layers, one is for road and another is for labeling.  But the workaround is that we can assign a same FeatureCache to both of the layers.  Here as following is the demo code:
 

 protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                Map1.CurrentExtent = new RectangleShape(-97.766, 30.291, -97.755, 30.286);
                Map1.MapBackground.BackgroundBrush = new GeoSolidBrush(GeoColor.FromArgb(255, 233, 232, 214));
                Map1.MapUnit = GeographyUnit.DecimalDegree;

                FeatureCache featureCache = new FeatureCache();
                featureCache.IsActive = true;

                ShapeFileFeatureLayer austinStreetsShapeLayer = new ShapeFileFeatureLayer(Server.MapPath(@"~\SampleData\USA\Austin\austinstreets.shp"));
                austinStreetsShapeLayer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle = LineStyles.LocalRoad1;
                // Please set the RequiredColumnNames that is same to label layer.
                austinStreetsShapeLayer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle.RequiredColumnNames.Add("FENAME"); 

                austinStreetsShapeLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
                austinStreetsShapeLayer.FeatureSource.GeoCache = featureCache;
                austinStreetsShapeLayer.DrawingMarginPercentage = 10;

                ShapeFileFeatureLayer austinStreetsLabelLayer = new ShapeFileFeatureLayer(Server.MapPath(@"~\SampleData\USA\Austin\austinstreets.shp"));
                austinStreetsLabelLayer.ZoomLevelSet.ZoomLevel01.DefaultTextStyle = TextStyles.LocalRoad1("FENAME");
                austinStreetsLabelLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

                austinStreetsLabelLayer.FeatureSource.GeoCache = featureCache;

                austinStreetsLabelLayer.DrawingMarginPercentage = 10;

                Map1.StaticOverlay.Layers.Add(austinStreetsShapeLayer);
                Map1.StaticOverlay.Layers.Add(austinStreetsLabelLayer);
            }
        }


Note: please set the same RequiredColumnNames property for both FeatureLayers.
Thanks,
Johnny
 

I have a further question about the FeatureCache system (I’m using it in combination with Postgres). If I load up a single layer twice (once for geometry and once for labeling), and give them the same FeatureCache, I would expect that the first instance of the layer would cache everything, and the second instance would get everything it needed from the cache, without ever going to the disk or DB. 
  
 However, during testing (by hooking into the ExecutingSqlStatement method), I have noted that the FeatureCache doesn’t completely eliminate disk/DB lookups for the second layer instance. If the first instance runs 10 queries whcih get cached, the second instance will still run 4 or 5 queries. I suspect it is running these queries for extents that did not have any features in them for the specified layer (eg. oceans); the cache has 0 features stored for those tiles, and the code is interpreting 0 features as “go to the source”. I can see how this might be useful, for instance if two layers that aren’t exactly the same are sharing a feature cache, but I know that my two layer instances are exactly the same, so I would like to eliminate ALL disk/DB lookups for the second instance, to improve performance. 
  
 I tried subclassing the FeatureCache class and implementing my own simple cache which only goes to the DB if the requested extent had never been cached before, and returning a fake feature if the extent had merely contained no features, but this was unsuccessful. 
  
 I hope I’ve explained my question well enough.

Hi Daniel,


Yes, just as you have mentioned, the mapSuite will request the DB again if the GetFeatures method of the FeatureCache returns 0 features, also including the scenario “no features in oceans”. Actually, we store the queried extent into a collection and check if it exists in GetFeaturesCore, and then query the features from cached QuadTree if the extent is cached in the collection, otherwise return false.
To get around your scenario, i think we just need to subclass our own FeatureCache and overwrite the “GetFeaturesCore” method. Here is the demo code which maybe is helpful to you:


    public class CustomFeatureCache : FeatureCache
    {
        // Defined to cache the queried extents 
        Collection<RectangleShape> cahedExtents = new Collection<RectangleShape>();

        protected override Collection<Feature> GetFeaturesCore(RectangleShape worldExtent)
        {
            Collection<Feature> returnValue = new Collection<Feature>();
            // Todo: Check the extent whether it has been cached.
            RectangleShape cachedExtent = null;
            foreach (RectangleShape extent in cahedExtents)
            {
                if (Contains(extent, worldExtent))
                {
                    cachedExtent = extent;
                    break;
                }
            }


            if (cachedExtent != null)
            {
                // Todo: the extent has been cached, we just need to get the features from cached quadtree
                returnValue = base.GetFeaturesCore(worldExtent);

                // Todo: if the returnValue is empty, it means that there is no features in current extent.
                if (returnValue.Count == 0)
                {
                    // Add a flag "0,0" point to present there is no feature in this extent
                    returnValue.Add(new Feature(new PointShape(0, 0)));
                }
            }

            return returnValue;
        }
    }


 Additionally, you need to deal with flag in event “CustomColumnFetch” of FeatureSource. Just need to clear the feature collection if it just includes the feature “Point(0,0)”.
 
Hope everything goes well with you.
 
Thanks,
Johnny

 



Thanks for the sample code. That helped me get it working.

Glad to hear that everything goes well with you. Any questions please let us know. 
  
 Thanks, 
 Johnny