ThinkGeo.com    |     Documentation    |     Premium Support

Loading layers twice just for labels!?

Ok, here's one for performance. 


I have created my own FeatureSource / FeatureLayer. After working through a whole bunch of stuff that I do not wish on my worst enemy (namely creating my own indexing algorithm / writing binary files etc), I am vastly more conscious about performance and microseconds.


That brings me to labels. It seems that if I want to display the labels for my layers... looking at the sample (DrawAndLabelANiceLookingRoad), I have to add the layer twice (first with the line style, then with the text style). If I add a DefaultTextStyle and DefaultLineStyle to the same layer, they both seem to render, but the text is behind the layer?


 



 


Is there a way to reverse this and draw the lines before the labels so that I can add my layers once (and half the read time)? The only other alternative is for me to take the information from the first layer file parse, and share it with the second layer, and that's just extra hacking.



Brendan, 
  
   I see the issue you are having and there are some complex reasons for it so hold on.  First before I go into a total brain dump on this one way to fix this issue is for you to not use the default properties on the ZoomLevel like DefaultTextStyle.  Instead use the CustomStyles collection and add the LineStyle first and then the TextStyle.  We need to make this drawing order change in the code as it will come up again and again so thanks for pointing this out. 
  
   Having said all of that there are some more things you need to understand otherwise you will fall into another trap later on.  Inside of the Style’s Draw method is where we make calls the GeoCanvas to draw everything.  When we make the call like GeoCanvas.DrawLine we have a parameter that is called drawing level, it is an enumeration and I think we have five of them.  What happens is for every drawing level used we create a temporary bitmap.  When you draw a simple line then it draws on level one by default.  If you have a complex line like a street that you might want to display as a solid thick black line with a thinner white line inside then what we do is draw the thick black like on level 1 and then the thinner white line on level 2.   
  
   When we are done with drawing that layer and the GeoCanvas.Flush or EndDrawing is called we composite the bitmaps in layer drawing order and it create the final bitmap.  There are a number of reasons why do that.  First is that if we were to draw the black and white line in order by feature the effect would not look very good because of the caps at the end of the lines.  The white area would not seamlessly merge into an adjoining line correctly.  You would get bad looking artifacts of where the black line would intersect the white line a little bit.  The other method we had in 2.x would be to draw all the black lines first, then loop through the features again and draw the white lines.  The problem with that is at any moment if you wanted to interrupt the drawing process then you would get a picture of all black lines with some white lines but not all of them.  The other reason is that it makes the code in the GeoCanvas much more elegant and avoids caching a bunch of future drawing requests.  In our system we draw the each record for every style once and no need for extra looping.   
  
   The reason I mention this is because the LineStyle and TextStyle both by default draw on level 1.  While in your case it doesn’t matter because your line is just one single line and not a composite line you wouldn’t notice.  If you later made the line have an outer and inner pen then what would happen is that the inner pen would draw on top of your labels while the outer pen would be behind them. 
  
   One change we could make is for the TextStyle to be default draw on level 4 or higher.  The downside of this is that it could still happen if someone created  style that used all five drawing levels.  It would however cover the major cases.  We could also add a new drawing level called Label which would be reserved for labels.  There are some problems with this for things like halo pens etc. 
  
   This brings me to another point.  I understand the reason you want to do this is for performance but I want to caution you on some side effects.  The first is typically people expect labels to draw on top of everything else.  In your case they would not.  If you had another layer on top of your roads say of points then it might happen that a point might overlap your label and cover it.  People might find this is strange.  The second point is that if you draw all of your label levels separately then you could put them into their own overlay and get all of control there over caching etc.  In the future as we get threading corrected it maybe possible to do the labels on a separate thread from the roads etc.  Or more possible we could do the foundation layers first and then display them on the map and next work on the labels.  This would give the user more instant feedback. 
  
   Another thing I wanted to bring up is performance.  I strongly suggest you try with separate layers and see what the measurements are.  In my experience there is little overhead to go back and get the data.  Let me first preface that with it applies to shape files.  If you are doing with a spatial database then this is not the case as there is overhead in the data request etc.  The reason is that there are so many caches in between you and your data on e disk it is most likely that after the initial call the data is already hot in one of the caches.  The disk has a 16 – 32 + megabyte cache and the OS caches quite a bit on its own.  In many tests I have run on some unrelated things I can see the same pattern over and over.  The first time the map loads the cost to draw a map is say 1 second and then every refresh if maybe 0.5 seconds.  If you monitor the disk active you can see there aren’t any disk reads.  In fact when I deal with people who use the InMemoryFeatureSource they can’t get over that it is just not that much faster than shape files on many cases.  The reasons is after you take the first hit to take it off disk it tends to stick around in memory anyway.  One prominent book I read in the past talked about how you can degrade performance by keeping too much stuff in your own caches.  You may have the data two or three times in memory and there also overhead to managing it.  I trust Windows caching system is pretty efficient. 
  
   I am not trying to talk you out of your strategy however what I am strongly suggesting is that you measure, measure and measure some more.  Try some things that dump your disk cache etc.  One of traps we can fall in is to work on a new technique that is supposed to be faster an dthen since we are working on it that data is hot in cache so we get good numbers.  Only every once and while we test the old way.  Since it is not in cache it does not perform well and we validate our new method with that. 
  
   Having said all of that I know you are a smart guy and I would not be surprised if you came back tomorrow and told me in detail how we could get a 50% performance increase by doing X or Y.  I am mainly saying this to others that might look at this thread and want to go down the optimization road prematurely.   
  
 With all of that said good luck and let me know how it goes, I am hear to light your way in any way I can. 
    
  
 David

Brendan, 
  
 One other thing.  I will take some time and talk with our core team to see what else we can do.  Maybe there is a yet unknown third solution out there.  We love challenges! 
  
 David

Thanks for the insight… we’re doing something similar (multiple layers to layout stuff) when it comes to the WPF overlay. 
  
 I’m timing everything in the GetFeaturesInsideBoundingBoxCore method… and it takes the same amount of time for both the shape pass and the label pass. I’m storing the “database” information (names etc) inside my data file (so no dbf file). This means that the second layer has to get all the same information again… which is taking the same time (to within a few milliseconds). 
  
 I have control of all the FeatureSource, so I can work out a way to link my feature layers. I was thinking that if I add the layer twice, I can set a flag that will ensure that it doesn’t try load shapes, but only asked for an already cached list of shapes with labels (from the first layer’s pass). 
  
 Of course, it would be nice to have a solution built into the GDI. That will be quicker to load I expect. 
  
 I’ll let you know how that turns out. 
  
 Thanks.

Brendan, 
  
   On thing you can try is the cache property on the FeatureSource.  We may have this, can;t remember the status of it.  If you used this then you would not have to share the same feature source but you could share the same cache.  Just make sure if the roads draw first you request the columns you will need for the labels.  This way the data will be in the cache.  I think this is a little oversight on our part. :-) 
  
   You can also inherit from cache and build your own.  The way the ones that comes with the system works is that it caches the bounding box and features associated with it.  If the same or smaller bounding box comes in then we use those records in memory.  There are many different ways to build a cache though and this was just one way we did rather quickly.  We have ideas fr a number of different way in memory on disk, using in memory spatial indexes etc.  Caching is wide open at this point and different strategies work better for different kinds of FeatureSources.  In your case you need to back it with memory as you use the local file system which is already pretty fast.  People that use spatial databases such as Oracle Spatial get a big bang from writing the cache to disk and persisting it.  It allows them to cache more data over time and cut down on their round trips. 
  
 David

Thanks David. The GeoCache property seems to help quite a bit. The trick is deciding when to use it because I don’t want to cache the world. I’ll work out a way that fits well for the project. Thanks.

Brendan, 
  
   If you have some scenerios or ideas let me know.  This was a last minute class and we made it simple but expected to expand the types of caches in the future.  Caching is a tricky business and any feedback on the API’s usefullness if you subclass is welcomed. 
  
 David

I’ll let you know how we end up using it / if we need to inherit it etc. I have a few ideas about how we might do it, but there’s no point talking about it now until I have concrete ideas. Thanks.

Brendan, 
  
  Great.  Let me know what you end up doing. 
  
 David

Hi, 
  
 I noticed that if I use the default line style and default text style that my label shows up below my line too.  While I understant I can work around this is this something that will be changed?  David aboves references maybing making the textstyle draw on level 4 or higher or even have it’s own specific level.  I would think a lot of people might run into this issue, is there any plan to implement this before the final release? 
  
 Thanks!

Clint, 
  
 I will make sure it is on the Tracking system and remind the team to discuss it for the upcoming release.  I think it is not that difficult to solve.  Thanks for pointing it out. 
  
 Ben.