ThinkGeo.com    |     Documentation    |     Premium Support

Any way to index an inmemoryfeaturelayer?

Hi,


I'm working with inmemoryfeaturelayers, and it's becoming so large, that some form of indexing would be very useful.  I'd put it into a shapefile, but the immemoryfeaturelayer has 6800 datacolumns, so I can't do that.


Does anyone know fi I can index the layer directly, or some alternative method that might help?  Any ideas would be most appreciated!


 


Tom



Tom, 
  
   Your post brings a smile to my face,  6,800 data columns hehehe… I get a new surprise every day. :-)  I think we could whip something up.  On the index what kind were you thinking?  An index on the data columns or an index on the shapes to draw?  Also what kind of shapes are you storing in the InMemoryFeatureLayer, how many are there, and how long does it take now? 
  
 David

Hey David,


Hehe yeah it's a pretty ridiculous amount of data.  The inmemorylayer is approximately 1.4gb lol.


Well, as a background:  I've made a tile server web service tthat persists a ThinkGeo MapEngine.  It's quite fast for layers with fewer records.  As far as I can tell, the number of columns has virtually no effect on performance.  However, the number of shapes does.


 


What I'd love is a way to index on which shapes to draw.  Right now, the performance isnt terrible, but this system could have 100,000 users a day.  For my states layer(52 shapes) I can render tiles at a rate of about 0.05(faster than VE tiles) seconds per tile.  For my county layer, which has 3140 shapes, it's taking around 3 seconds per tile, which is too slow.  Given that both layers have 6800 columns, I think it's safe to say the columns arent causing the slowdown.  An index would make it much much faster.  


Anything you guys can whip up would be phenomenal! Please let me know if you need any additional information or anything like that!


 


Regards,


 


Tom



Tom, 
  
   That is awesome!  
  
   I am thinking of two approaches, one more complex than the other.  The first way would be to just add a simple index of the bounding boxes.  This would give you a speedup for sure on polygon based shapes.  I need to check to code to see if it isn’t doing this already but I suspect not.  It wasn’t really designed with that many shapes in mind. 
  
   The second and more hard core approach would be to use out R-Tree index.  We have it written in such a way that we can use memory streams instead of files.  In this way it could all still be in memory and would be pretty darn fast.  This is the fastest way but would take longer.   
  
 I might try the first way and see what you think.  After that I can get someone else to the the second way next week.  I will see if I can put this together today and have it for you tomorrow.  No promises as I have lots of forum stuff but I think it might not be too bad.  One caveat is that for this to work you would have to add and remove shapes using the edit tools.  You could not use the InternalFeatures as then I wont be able to keep my index working correctly.  A minor inconvenience for the speedup… 
  
 Sound good? 
  
 David

David,


 


That sounds awesome!  Adding and removing the shapes via edit tools shouldn't be a big issue, the layers are actually static once they are generated.  A little code showing adding features that way would be helpful if its not in the pre-built examples.  The first indexing method will probably get it going quite well, and I can wait a little on r-tree indexing, but that'd be fantastic.  This overall method is the fastest way I have found to render shapes when there are heavy user loads.  I'm quite pleased with how ThinkGeo has progressed, and even more with your(and Ben's) help on this stuff!  Thank you so much and please let me know if you need any additional information!


 


Regards,




Tom



Tom,


  I whipped this up for you.  I haven't tested this at all but it does compile, famous last words.  I am about to get off work so I thought I would send it to you to see how it works and try it out if you had time left today instead of waiting until tomorrow.  I tried to comment it as best I could with the little time I had.  I suggest you put break points in a debug through it to see how it works.


We have a sample of the transaction system in the How Do I samples but I will give you a quick rundown.


Layer.QueryTools.BeingTransaction();

Layer.QueryTools.Add(Feature) --- Make sure you set the Id on the feature as we use this, or maybe there is an overload that takes the feature and Id, I forget.

Layer.QueryTools.CommitTransaction()


Thats it, I hope you get a chance to use it an give me some feedback good or bad.  Also the intersection code we have in the core is not very fast.  I have a much better one but didn't have time to put it in.  I can do this tomorrow.


Happy debugging...


David


 





    public class IndexedInMemoryFeatureLayer : FeatureLayer
    {
        public IndexedInMemoryFeatureLayer(IEnumerable<FeatureSourceColumn> 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<FeatureSourceColumn> Columns
        {
            get
            {
                if (!FeatureSource.IsOpen)
                {
                    FeatureSource.Open();
                }
                Collection<FeatureSourceColumn> 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.
        public override bool HasBoundingBox
        {
            get

            {
                return true;
            }
        }
    }


    public class IndexedInMemoryFeatureSource : InMemoryFeatureSource
    {
        Dictionary<string, RectangleShape> boundingBoxes = new Dictionary<string, RectangleShape>();

        public IndexedInMemoryFeatureSource(IEnumerable<FeatureSourceColumn> 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

            // Handle the deleted features
            foreach (string Id in transactions.DeleteBuffer)
            {
                boundingBoxes.Remove(Id);
            }

            // Handle the new features
            foreach (KeyValuePair<string, Feature> addTransaction in transactions.AddBuffer)
            {
                boundingBoxes.Add(addTransaction.Key, ((Feature)addTransaction.Value).GetBoundingBox());
            }

            // Handle edited features
            foreach (KeyValuePair<string, Feature> editTransaction in transactions.EditBuffer)
            {
                boundingBoxes[editTransaction.Key] = ((Feature)editTransaction.Value).GetBoundingBox();
            }

            return base.CommitTransactionCore(transactions);
        }

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

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

        private Collection<Feature> GetFeaturesBasedOnCachedBoundingBoxes(RectangleShape extent)
        {
            // Here we loop through our bounding box cache and find the
            // shapes that intersect the extent passed in.

            Collection<Feature> features = new Collection<Feature>();
            foreach (string key in boundingBoxes.Keys)
            {
                RectangleShape featureBoundingBox = (RectangleShape)boundingBoxes[key];
                if (featureBoundingBox.Intersects(extent))
                {
                    features.Add((Feature)InternalFeatures[key]);
                }
            }

            return features;
        }

        protected override RectangleShape GetBoundingBoxCore()
        {
            // Here we just optimize the get bounding box since this will
            // be faster than re-calculating them.

            Collection<BaseShape> canidates = new Collection<BaseShape>();
            foreach (string key in boundingBoxes.Keys)
            {
                canidates.Add((RectangleShape)boundingBoxes[key]);
            }

            return ExtentHelper.GetBoundingBoxOfItems(canidates);
        }
    }



Hi Tom,


I came across your post and the bit about making a "tile server web service that persists a ThinkGeo MapEngine" really caught my eyes. Can you expand on this? Is this something that can be consumed via OpenLayers as a WMS?


Thanks,


Rob



David, 
  
 I’ve implemented the IndexedInMemoryLayer.  The code worked out of the box pretty much.  There is a definite improvement.  I think r-tree indexing would definitely be a pretty big improvement over this method, but this can hold me over till next week.  When would you think you might be able to have an r-tree indexed inmemorylayer available for me?  we were hoping to send the tile server to live sometime next week. 
  
 Thanks again for all your help! 
  
 Regards, 
  
 Tom

Hi Rob, 
  
 To be honest, I’m not certain.  We use Virtual Earth as our base platform, and so my service pretty much just generates tiles on the fly when they are requested from Virtual Earth.  ThinkGeo does make a good tile server though.  In a lot of cases, their drawing performance exceeds the speed of pre-rendered tiles.  If you have more questions, don’t hesitate to ask! 
  
 Regards, 
  
 Tom

David, 
  
 After some more testing, I’d say there’s about a 10% improvement in performance.  On my States layer, its so freaking fast that VE can’t seem to request them fast enough.  The county layer, however, is still sluggish.  I’d say about 2.2 seconds per tile or thereabouts, down from an average of 3. 
  
 Regards, 
  
 Tom

Tom,


  I enhanced the class with a faster bounding box intersection.  The one in the core is a little slow and I thought this might give you a little more speed.  Next week I will be pretty busy but I will see if I can fit it in.  Also note this is just an idea and I am unsure if it will work.  I also wonder if I will be able to expose it to you without a new build.  If a new build is required it will take longer because I have to requisition one.


In any event try this out and let me know what happens.


David


 





    public class IndexedInMemoryFeatureSource : InMemoryFeatureSource
    {
        Dictionary<string, RectangleShape> boundingBoxes = new Dictionary<string, RectangleShape>();

        public IndexedInMemoryFeatureSource(IEnumerable<FeatureSourceColumn> 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

            // Handle the deleted features
            foreach (string Id in transactions.DeleteBuffer)
            {
                boundingBoxes.Remove(Id);
            }

            // Handle the new features
            foreach (KeyValuePair<string, Feature> addTransaction in transactions.AddBuffer)
            {
                boundingBoxes.Add(addTransaction.Key, ((Feature)addTransaction.Value).GetBoundingBox());
            }

            // Handle edited features
            foreach (KeyValuePair<string, Feature> editTransaction in transactions.EditBuffer)
            {
                boundingBoxes[editTransaction.Key] = ((Feature)editTransaction.Value).GetBoundingBox();
            }

            return base.CommitTransactionCore(transactions);
        }

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

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

        private Collection<Feature> GetFeaturesBasedOnCachedBoundingBoxes(RectangleShape extent)
        {
            // Here we loop through our bounding box cache and find the
            // shapes that intersect the extent passed in.

            Collection<Feature> features = new Collection<Feature>();
            foreach (string key in boundingBoxes.Keys)
            {
                RectangleShape featureBoundingBox = (RectangleShape)boundingBoxes[key];
                if (IntersectsFast(extent, featureBoundingBox))
                {
                    features.Add((Feature)InternalFeatures[key]);
                }
            }

            return features;
        }

        protected override RectangleShape GetBoundingBoxCore()
        {
            // Here we just optimize the get bounding box since this will
            // be faster than re-calculating them.

            Collection<BaseShape> canidates = new Collection<BaseShape>();
            foreach (string key in boundingBoxes.Keys)
            {
                canidates.Add((RectangleShape)boundingBoxes[key]);
            }

            return ExtentHelper.GetBoundingBoxOfItems(canidates);
        }

        // This is an faster intersection check.  We will replace this in the core but for
        // now you can use this and see what speed it brings you
        private bool IntersectsFast(RectangleShape rectangle1, RectangleShape rectangle2)
        {
            double rectangle1UpperLeftX = rectangle1.UpperLeftPoint.X;
            double rectangle1UpperLeftY = rectangle1.UpperLeftPoint.Y;
            double rectangle1LowerRightX = rectangle1.LowerRightPoint.X;
            double rectangle1LowerRightY = rectangle1.LowerRightPoint.Y;

            double rectangle2UpperLeftX = rectangle2.UpperLeftPoint.X;
            double rectangle2UpperLeftY = rectangle2.UpperLeftPoint.Y;
            double rectangle2LowerRightX = rectangle2.LowerRightPoint.X;
            double rectangle2LowerRightY = rectangle2.LowerRightPoint.Y;

            if (((rectangle1LowerRightX >= rectangle2UpperLeftX & rectangle1LowerRightX <= rectangle2LowerRightX) 
                & (rectangle1LowerRightY <= rectangle2UpperLeftY & rectangle1LowerRightY >= rectangle2LowerRightY))
                | ((rectangle1UpperLeftX >= rectangle2UpperLeftX & rectangle1UpperLeftX <= rectangle2LowerRightX) 
                & (rectangle1LowerRightY <= rectangle2UpperLeftY & rectangle1LowerRightY >= rectangle2LowerRightY)) 
                | ((rectangle1UpperLeftX >= rectangle2UpperLeftX & rectangle1UpperLeftX <= rectangle2LowerRightX) 
                & (rectangle1UpperLeftY <= rectangle2UpperLeftY & rectangle1UpperLeftY >= rectangle2LowerRightY)) 
                | ((rectangle1LowerRightX >= rectangle2UpperLeftX & rectangle1LowerRightX <= rectangle2LowerRightX) 
                & (rectangle1UpperLeftY <= rectangle2UpperLeftY & rectangle1UpperLeftY >= rectangle2LowerRightY)) 
                | ((rectangle1UpperLeftX >= rectangle2UpperLeftX & rectangle1UpperLeftX <= rectangle2LowerRightX) 
                & (rectangle2LowerRightY <= rectangle1UpperLeftY & rectangle2LowerRightY >= rectangle1LowerRightY)) 
                | ((rectangle1LowerRightX >= rectangle2UpperLeftX & rectangle1LowerRightX <= rectangle2LowerRightX) 
                & (rectangle2UpperLeftY <= rectangle1UpperLeftY & rectangle2UpperLeftY >= rectangle1LowerRightY)) 
                | ((rectangle1LowerRightY <= rectangle2UpperLeftY & rectangle1LowerRightY >= rectangle2LowerRightY) 
                & (rectangle2UpperLeftX >= rectangle1UpperLeftX & rectangle2UpperLeftX <= rectangle1LowerRightX)) 
                | ((rectangle1UpperLeftY <= rectangle2UpperLeftY & rectangle1UpperLeftY >= rectangle2LowerRightY) 
                & (rectangle2LowerRightX >= rectangle1UpperLeftX & rectangle2LowerRightX <= rectangle1LowerRightX)) 
                | ((rectangle2UpperLeftX >= rectangle1UpperLeftX & rectangle2UpperLeftX <= rectangle1LowerRightX) 
                & (rectangle2LowerRightY <= rectangle1UpperLeftY & rectangle2LowerRightY >= rectangle1LowerRightY)))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }



Hey David, 
  
 Thanks for the update.  I’ve tried the new code, and it’s definitely a little faster.  in most cases, a single tile takes a little over a second with the new method.  I’m sure the r-tree indexing will help, so please keep me posted on that if at all possible. 
  
 Regards, 
  
 Tom

Tom, 
  
  Great news.  So you mean we went from 3 seconds to 2.3 to a little over 1 second now for the larger set?  Not bad. 
  
 David

David, 
  
 Yeah I just put it on our development server and it seems to work fairly well.  For a quick fix, not bad at all.  Would it be ok to check in mid-next week and see how an r-tree method is coming along? 
  
 Regards, 
  
 Tom

Tom,


  I have something for you to try.  I remembered that we have a quad tree that is exposed.  This is just about as fast as the R-Tree and much easier for me to expose to you.  There are a few things to note.


1.This only supports adds.  I think this will be fine with you.

2.There is no more GetBoudningBox support.  Not sure of you need it.  I could be made to work but I didn't have time for it.

3.You 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.

4.In the code there is a part where I create an envolope.  For that API they ask for x1, x2, y1, and y2.  I assume this is the MaxX, MinX, MaxY and MinY but I do not get intelliense to verify this.  You may have to juggle the values around.  I didn't test it because I didn't have time to setup a project and data.

5.I am not sure how this will work with the way I remove the GetBoundingBox.  Only one way to find out!

6.I am pretty sure I am using the API right but not 100% sure.

7.You need to make sure to add the imports that I included.

8.You need to overwrite both classes and I changes some things in the Layer part as well.


I wanted to give you something to hopefully work for a pleasant weekend with no worried on speed.  I fear the R-Tree might be hard to expose.  Please let me know how it goes.


David


 





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

    public class IndexedInMemoryFeatureLayer : FeatureLayer
    {
        public IndexedInMemoryFeatureLayer(IEnumerable<FeatureSourceColumn> 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<FeatureSourceColumn> Columns
        {
            get
            {
                if (!FeatureSource.IsOpen)
                {
                    FeatureSource.Open();
                }
                Collection<FeatureSourceColumn> 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
    {
        Dictionary<string, RectangleShape> boundingBoxes = new Dictionary<string, RectangleShape>();
        Quadtree quadTree = new Quadtree();

        public IndexedInMemoryFeatureSource(IEnumerable<FeatureSourceColumn> 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

            // 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.Y, rectangle.UpperLeftPoint.Y, rectangle.LowerRightPoint.Y);
                quadTree.Insert(envelope, addTransaction.Key);
            }

            return base.CommitTransactionCore(transactions);
        }

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

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

        private Collection<Feature> GetFeaturesBasedOnCachedBoundingBoxes(RectangleShape extent)
        {            
            Envelope envelope = new Envelope(extent.UpperLeftPoint.X, extent.LowerRightPoint.Y, extent.UpperLeftPoint.Y, extent.LowerRightPoint.Y);
            List<string> RecordIds = (List<string>)quadTree.Query(envelope);

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

            return features;
        }

        //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<BaseShape> canidates = new Collection<BaseShape>();
        //    foreach (string key in boundingBoxes.Keys)
        //    {
        //        canidates.Add((RectangleShape)boundingBoxes[key]);
        //    }

        //    return ExtentHelper.GetBoundingBoxOfItems(canidates);
        //}
    }



Tom, 
  
  I forgot to mention one more thing.  When I talked about creating an envelope and that you may need to juggle the order of the parameter I forgot to mention there are TWO places in the code I do this.  Make sure you make the changes in both places otherwise you will get mixed results and go crazy. 
  
 David

David,


 


I'm not sure what's wrong, but this method is actually somewhat slower than with no indexing at all.  The states layer still loads pretty fast, but the county layer is much slower, with a much higher processing load.  I did change one line of code that failed to execute properly.


 


ArrayList RecordIds = (ArrayList)quadTree.Query(envelope);


 


Used to be a list<t>, but it wouldnt allow the conversion.


 


Let me know your thoughts!


 


Regards,


 


Tom



Tom, 
  
   This doesn’t surprise me as caching the bounding boxes and doing the quick math isn’t much overhead.  The QuadTree has efficiency problems depending on how large the polygons are compared to other ones and compared to how big the total area of all of them are.  It is really good when you have lots of very small features but slow when you have lots of large features that are about the same size and span across allot of the high level quadrants. 
  
   I am not sure how much better the R-Tree will do with this but I did also see on in that library that is exposed.  I will implement that and you can let me know how it goes.  If nothing else you have the fall back to go with the bounding boxes. 
  
   I might have some other ways to shave some time off your time but they have to do with manipulating the data if you would be up for that. 
  
 David

Tom,


  Here it is with an in-memory R-Tree.  The only thing I am not sure about is the Build method.  I called it after we add all the items.  I am not sure if this is right or even needs to be called but I think it is OK.  Let me know.


David


 





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<FeatureSourceColumn> 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<FeatureSourceColumn> Columns
        {
            get
            {
                if (!FeatureSource.IsOpen)
                {
                    FeatureSource.Open();
                }
                Collection<FeatureSourceColumn> 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<FeatureSourceColumn> 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.Y, rectangle.UpperLeftPoint.Y, rectangle.LowerRightPoint.Y);
                rTree.Insert(envelope, addTransaction.Key);
            }
            rTree.Build();

            return base.CommitTransactionCore(transactions);
        }

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

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

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

            Collection<Feature> features = new Collection<Feature>();
            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<BaseShape> canidates = new Collection<BaseShape>();
        //    foreach (string key in boundingBoxes.Keys)
        //    {
        //        canidates.Add((RectangleShape)boundingBoxes[key]);
        //    }

        //    return ExtentHelper.GetBoundingBoxOfItems(canidates);
        //}
    }



David,


 


Awesome, thanks!  It works wonderfully!  It's still not "lighting fast", but I think it's as good as it's gonna get.  It renders a little faster than my indexed County shp files now.  My last post about it being unbeleiveably slow, it turns out, was caused by having the bounding box information screwed, up, forcing the returnfeaturesbasedoncachedboundingboxes to return every one of the shapes and render each of them for each tile.  However, even after I corrected this, the av3erage tile wanted to render about 300 shapes, just showing how a quadtree routine can be inefficient in some cases.  With the r-tree indexing, the number comes down to an average of 50 or so, which is much more manageable.  Thank you so much for the help, you guys are awesome!  Below is the code that I ran(with the bounding box order corrections) and it works great.  Again, thank you guys so much for the quick turnaround!


 


Regards,


 


Tom


 


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<FeatureSourceColumn> 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<FeatureSourceColumn> Columns
        {
            get
            {
                if (!FeatureSource.IsOpen)
                {
                    FeatureSource.Open();
                }
                Collection<FeatureSourceColumn> 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<FeatureSourceColumn> 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<Feature> GetFeaturesForDrawingCore(RectangleShape boundingBox, double screenWidth, double screenHeight, IEnumerable<string> returningColumnNames)
        {
            return GetFeaturesBasedOnCachedBoundingBoxes(boundingBox);
        }

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

        private Collection<Feature> 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<Feature> features = new Collection<Feature>();
            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<BaseShape> canidates = new Collection<BaseShape>();
        //    foreach (string key in boundingBoxes.Keys)
        //    {
        //        canidates.Add((RectangleShape)boundingBoxes[key]);
        //    }

        //    return ExtentHelper.GetBoundingBoxOfItems(canidates);
        //}
    }