Register  |  Login
ThinkGeo - GPS Tracking and Mapping Solutions  |  Home  |  Cygnus Track  |   Code Community

Discussion Forums

The online community for users of Map Suite GIS components

RSS Feed Available AddThis - Bookmarking and Sharing Button Printer Friendly

PrevPrev NextNext

Sample Code: Indexed InMemoryFeatureLayer

Posted by David on 02-10-2009 08:04 AM

I wanted to share with you some code we worked on for Tom, one of our forum guests. He is working with an InMemoryFeatureLayer that has tens of thousands of objects in it. With this amount, it was quite slow to draw because we calculated the bounding boxes for each record in real time. To help solve his problem, we created this neat IndexedInMemoryFeatureLayer and IndexedInMemoryFeatureSource. It uses an in memory R-Tree index to speed up the query times when we want to know what shapes are in a rectangle.

There are a few caveats with the code, so please read carefully.

1. Currently it only indexes adds to the FeatureSource. If you edit or delete features, they will not update the R-Tree. It is also important to note that you cannot use the InternalFeatures method of the Layer or FeatureSource to add, update or remove records. We use the transaction system in the FeatureSource to control how records are added.

2. You have to use the QueryTools object to BeinTransaction, Add, then CommitTransaction. This is really straightforward and worth the speed gains if the data isn't changing much.

3. You also need to add a reference to your project for the following two DLLs: NetTopologySuite and GeoAPI. You can find them in the bin directory, as they are dependencies for us anyway.

If you are interested in supporting updating and deleting features, just let me know and we can add this. In the near future we will be including a similar class in the Core name space, so you can get this functionality right out of the box.

If you would like more information you can check out the thread that spawned this. It is an interesting read with a few variations. There are also some samples of how to use it.


using System.Collections.ObjectModel;
using System.Collections.Generic;
using GisSharpBlog.NetTopologySuite.Index.Strtree;
using GisSharpBlog.NetTopologySuite.Geometries;
using System.Collections;
using ThinkGeo.MapSuite.Core;

    public class IndexedInMemoryFeatureLayer : FeatureLayer
    {
        public IndexedInMemoryFeatureLayer(IEnumerable featureSourceColumns)
        {
            // Create our new improved feature source and set it.
            this.FeatureSource = new IndexedInMemoryFeatureSource(featureSourceColumns);
        }

        // Don't mind this code, just some plumbing.
        public Collection Columns
        {
            get
            {
                if (!FeatureSource.IsOpen)
                {
                    FeatureSource.Open();
                }
                Collection columns = ((InMemoryFeatureSource)FeatureSource).GetColumns();

                return columns;
            }
        }

        // Don't mind this code, just some plumbing.
        protected override void OpenCore()
        {
            base.OpenCore();

            if (FeatureSource.Projection != null && !FeatureSource.Projection.IsOpen)
            {
                FeatureSource.Projection.Open();
            }
        }

        // just say this layer supports getting a bounding box.
        // No longer supported
        public override bool HasBoundingBox
        {
            get
            {
                return false;
            }
        }
    }

    public class IndexedInMemoryFeatureSource : InMemoryFeatureSource
    {        
        STRtree rTree = new STRtree();

        public IndexedInMemoryFeatureSource(IEnumerable featureSourceColumns)
            : base(featureSourceColumns)
        {
        }

        protected override TransactionResult CommitTransactionCore(TransactionBuffer transactions)
        {
            // Here we are going to cache our own bounding boxes and then 
            // call the default logic to process the transactions

            // We only handle adds at this point
            //TODO: Thow an exception if they try and do a delete or an edit
            //TODO: Or add support for edits and ads
            
            // Handle the new features
            foreach (KeyValuePair<string, Feature> addTransaction in transactions.AddBuffer)
            {
                RectangleShape rectangle = ((Feature)addTransaction.Value).GetBoundingBox();
                Envelope envelope = new Envelope(rectangle.UpperLeftPoint.X, rectangle.LowerRightPoint.X, rectangle.UpperLeftPoint.Y, rectangle.LowerRightPoint.Y);
                rTree.Insert(envelope, addTransaction.Key);
            }
            rTree.Build();

            return base.CommitTransactionCore(transactions);
        }

        protected override Collection GetFeaturesForDrawingCore(RectangleShape boundingBox, double screenWidth, double screenHeight, IEnumerable<string> returningColumnNames)
        {
            return GetFeaturesBasedOnCachedBoundingBoxes(boundingBox);
        }

        protected override Collection GetFeaturesInsideBoundingBoxCore(RectangleShape boundingBox, IEnumerable<string> returningColumnNames)
        {
            return GetFeaturesBasedOnCachedBoundingBoxes(boundingBox);
        }

        private Collection GetFeaturesBasedOnCachedBoundingBoxes(RectangleShape extent)
        {            
            Envelope envelope = new Envelope(extent.UpperLeftPoint.X, extent.LowerRightPoint.X, extent.UpperLeftPoint.Y, extent.LowerRightPoint.Y);
            ArrayList RecordIds = (ArrayList)rTree.Query(envelope);

            Collection features = new Collection();
            foreach (string key in RecordIds)
            {
                features.Add((Feature)InternalFeatures[key]);
            }

            return features;
        }

        //TODO: Add support for this
        //No longer supported
        //protected override RectangleShape GetBoundingBoxCore()
        //{
        //    // Here we just optimize the get bounding box since this will
        //    // be faster than re-calculating them.
        //    Collection canidates = new Collection();
        //    foreach (string key in boundingBoxes.Keys)
        //    {
        //        canidates.Add((RectangleShape)boundingBoxes[key]);
        //    }
        //    return ExtentHelper.GetBoundingBoxOfItems(canidates);
        //}
    }
- Need sample code? Check out the Map Suite Code Community at http://code.thinkgeo.com

0 Comments

You are not authorized to post a reply.
Active Forums 4.2