Layer Predraw

Developer
Sep 13, 2010 at 8:05 PM

We need a way for an application using the DotSpatial Map component to have an opportunity to draw a backdrop on selected polygon layers prior to drawing the vectors.  This is not an unusual capability as I have seen it in  other GIS mapping components.  I have not been able to find this capability directly in DS.  If someone knows if this capability already exists, please let me know.  Assuming I haven't missed something, I see 2 possible approaches:

  1. EventApproach - Provide an event either on the Map, MapFrame, or MapLayer that is called just prior to IMapLayer.DrawRegions(...).  We could probably just let the event handler be responsible for drawing the contents (by calling Draw Regions itself.); this would allow the application to do Predrawing and Postdrawing within the same handler.  The handler should be passed everything you pass to DrawRegions.  Need to be sure it has access to the drawing device (i.e. Graphics object for GDI+) and the layer that is being drawn.
  2. InheritanceApproach - If this approach works well, it would not require a change to DS, or perhaps only some very minor ones.  The application would implement their own MapPolygonLayer (for the sake of this discussion, I'll call it MyMapPolygonLayer) which is derived from MapPolygonLayer. MyMapPolygonLayer would rely on MapPolygonLayer for everything except it would override DrawFeatures and do the pre-drawing prior to calling DrawFeatures on the base class.  I actually tested out this concept and it worked.  I had to implement the same MapPolygonLayer constructors I wished to use and then forward those to the MapPolygonLayer base constructors. Then, of course, I implemented the override of DrawFeatures.  This is shown below:

     public class MyMapPolygonLayer : MapPolygonLayer
    {
        public MyMapPolygonLayer(IFeatureSet fs)
            : base(fs)
        {
        }
        public override void DrawFeatures(MapArgs args, List<int> indices, List<Rectangle> clipRectangles, bool useChunks)
        {
            args.Device.Clear(Color.Aquamarine); // Simulate drawing backdrop here
            base.DrawFeatures(args, indices, clipRectangles, useChunks);
        }
    }

Approach #2 was pretty cool, taking advantage of the DS object model.  The biggest disadvantage I see with this approach is that you have to implement these derived classes for every layer type for which you need this behavior.  In our particular case, we only need it for polygon layers, but other users may think otherwise.

I'd be interested in learning what other people think.

Thanks,

Kyle

 

Developer
Sep 13, 2010 at 10:16 PM
Edited Sep 13, 2010 at 10:18 PM

It sounds like it's six in one hand, half a dozen in the other as far as the event vs. inheritance approach.  The reason I say that is that the developer will have to implement the same amount of custom drawing, regardless of how the pre-drawing is initiated.  So if they need custom drawing for all the types of feature layers, they would have to explicitly control that drawing somewhere.  Either there was a generic situation and then they have to test whether they have a polygon, point, or line in order to figure out what they should be doing, or else they use your #2 model, separate it out into 3 different classes, and allow the object model to take care of separating points from lines from polygons, but then tacking on their own fanciness in there for only the classes they need.

I think that the strength of using the #2 design is that it doesn't clutter up the system with windows messaging for each drawing pass, which I think might have the tendency to slow things down and prove complicated.  Instead it is clean and follows the same pattern that developers should be accustomed to from drawing custom controls or handling custom drawing in System.Windows.Forms objects.

I have no strong objection if you wanted to add in events, though, it's up to you.

Ted

 

Developer
Sep 14, 2010 at 1:46 PM

I think I like #2.  I mainly wanted to make sure I wasn't missing a capability that was already present and also to see if anyone else saw value in the event mechanism.  So, let's go with #2 for now.

Editor
Sep 15, 2010 at 9:42 AM

I am not sure I understand the use case, but couldn't you just add PreDrawLayer as a virtual method on MapPolygonLayer (or perhaps even lower in the hierarchy) and leave the implementation empty. People then subclass and override as necessary.

Having events has its advantages in that you can change the behaviour at runtime without creating subclasses. Only issue I see with events is that you may have multiple handlers attached to the event and the order that handlers fire is not guaranteed.

 

Developer
Sep 15, 2010 at 2:24 PM

The virtual method is a good idea.  It would be cleaner than overriding the multiple DrawFeatures methods on MapPolygonLayer like I did in approach #2.  I'll look at adding that in when I get a chance.  A PostDrawLayer might be useful as well. I was actually hoping to combine pre/post into one method that could be called DrawLayer where the subclass implementation could do it's predraw work, then call DrawLayer on MapPolygonLayer, then do its post-draw work. But that would mean the MapPolygonLayer implementation of DrawLayer cannot be empty (it must actually draw the layer).  Thanks.

Developer
Sep 15, 2010 at 3:47 PM

Keep in mind the inheritance hierarchy here.  I put the drawing at the highest level so that we can have GDI+, Open GL, DirectX or other drawing implementations that are not GDI+ compatible that are still able to share the same drawing code as far as having the symbology and legend behavior.  What this means is that the polygon, line, point base classes are split below the "MapPolygonLayer" level where the GDI+ pre-drawing and post-drawing will occur.  However we could introduce an empty "OnPreDraw" and "OnPostDraw" that get fired by the regular Draw method before and after it does its regular work, but take the same drawing arguments that the main OnDraw method does.  Since there are several locations available where this could be inserted, I will let you decide where you think the best place to override the functionality is.  Do you do it before you know which features to work with, or do you do it when you just know the rectangular extents?  Anyway, I have no issue with you adding in virtual members where you feel they will be helpful.

Ted

Developer
Sep 20, 2010 at 9:01 PM

Maybe I'm missing something but couldn't the same thing be achieved with a more generic drawing layer, which just like every other layer type would be above or below other layers in the stack of data layers. You could then handle your own drawing code for whatever you needed. Or am I missing something?

Developer
Sep 20, 2010 at 9:46 PM

Actually, that was my initial design, placing the rendering code on the bottom of the stack an having all layers and other things override the drawing.  The problem is that when we wanted to share the symbology business logic between the GDI+ map and the 3D DirectX map, we suddenly ended up with the whole rendering stack designed for the wrong display driver.  Arguably we could have just added virtual callers for DirectX, but I felt that forcing GDI users to have a reference to DirectX was mean.  That and DirectX is not supported by Mono, and so could not work for Dr. Maidments dream of running on a Mac.  So in order to share code, we inverted the principle, putting the rendering code at the highest level because we anticipate it being replaced in the future by a WPF, DirectX, OpenGL, or other display UI.  This would allow the applications to share the same business logic so that schemes can be saved and then loaded in the different medium.  In the case of the 3D map, it also allowed us to re-use the legend and the symbology dialogs that took a long time to build.  How appropriate this is, considering that 3D content may need aspects that are not present in 2D symbologies, is arguable.  However, I think that any more complicated symbology could be set up to inherit from the original 2D symbologies, and 2D code should be ok with ignoring the 3D terms if present.  Anyway, that's why we put the drawing code on top instead of on bottom.  C# does not allow multiple inheritance, so basically that's why the IMapLayer interface was created.  This interface got more and more restricted over time because it is not only implemented by every type of layer, but by groups and even the mapframe.  Originally it had lots of content related to buffers, when each layer had its own transparent bitmap.  Now, however, we have thrown that out because it was slow and ate up memory too fast when lots of layers got added, especially if the view extents were extended larger than the screen.  (Technically it still eats up memory too fast, just for the in-ram data objects, but that's an issue for another time :P)

Developer
Sep 20, 2010 at 10:00 PM

Woops, I misread Brian.  Sorry about that.  I read generic drawing layer and assumed you were implying our existing layers inherit from a common renderable base layer.  The lecture may still serve as useful for others reading this thread though ;).  Actually, if you want to control custom drawing in the layer like you are describing, you can just inherit from the MapImageLayer.  It gives you and extent and the pixel width and height of the view that you should create an image for.  Of course, this will look like an image layer in the legend, which is just a boring checkbox with the name.

To be honest, from what was described, I don't know why they aren't just using a "PicturePattern" for the polygon symbology.  But maybe they have some custom content that needs to be drawn that can't be controlled based on setting up an image for the picture pattern.  In the worst case, they should only have to implement a new kind of custom pattern, like what you did for the "Hatch" pattern.  If you want the legend to host feature symbology, you would need to use a MapFeatureLayer, or basically something that inherits from IFeatureLayer so that the legend knows how to treat it.

But yeah, if you really need a "drawing" layer like what Brian said, I'd just override the MapImageLayer.

Ted

 

Developer
Sep 21, 2010 at 2:38 PM

The actual use case is this:

We have the concept of a raster layer containing thousands of rasters.  It really isn't practical to have a separate map layer for each of the rasters (which is the norm for GIS viewers).  We also want to display the footprints of the rasters (with or without the imagery being displayed).  So, when the "raster" layer draws, we first draw the rasters (if they are visible), then draw the polygonal footprints.  The raster drawing occurs in the predraw.  Hope this clarifies things a bit.

Kyle

Developer
Sep 21, 2010 at 3:26 PM

Yes, ok, so that wouldn't quite work as a pattern.  The way that GDI+ tends to deal with polygons drawing a pattern is that the offset tends to be global, and each polygon drawn at the same time reveals that pattern.  You are basically describing an ESRI style raster catalog, and yes, we haven't implemented that on our own yet, so for the time being you may have to design it yourself, as you are doing.  You can of course use things like the MapFrame.DrawingLayers to add MapImageLayers in order to dynamically add and remove images, but those would still operate like our in-ram set of image layers.  To have reasonable memory performance with potentially thousands of images, you will want to only load one image at a time, draw it, and then let it go again, so I think you are on the right track about designing a kind of custom OnDrawFeatures behavior or whatever.

 

 

Editor
Sep 21, 2010 at 7:53 PM

Kyle, can't you use GDAL's virtual raster capabilities to group all your rasters in one layer and then have a seperate Polygon layer for the outlines?

cheers FObermaier

Developer
Sep 21, 2010 at 8:59 PM

Er... that might not be exposed in the base raster interfaces yet.  He could probably do it somehow with his own GDAL references, but since we are trying to set up the .Net interfaces so that GDAL references are optional and dynamically loaded, we would need to write the .Net classes/interfaces that would expose that functionality.  I also am not familiar with the virtual raster capabilities, so I wasn't really aware that there was something to expose that did this =).  After I finish refactoring we should talk more about this FObermaier, since it sounds like you have a handle on it.

Ted

 

Developer
Sep 21, 2010 at 9:10 PM

FObermaier,

Might be able to use the virtual raster.  I have used it in some isolated cases before.  But, our raster subsystem is already working quite well for us, is fairly modular, and we have a lot invested in it.  So, we really want to continue to use it if we can.  If we can do the layer predraw thing, it should slot in rather cleanly.

 

Thanks,

Kyle