Introduction

Renderers are used to draw additional features on a map. For an object to be drawn on the map, you need to implement the IMapRenderer interface. Then simply assiging the object to the Renderer property of the map will cause the object to be drawn on the map. This tutorial demonstrates the basics of implementing renderers, and provides an illustration of different render modes and the ability to adjust based on where other map objects appear.

Prerequisites

This tutorial assumes familiarity with Microsoft Visual Studio. You will need a copy of Visual Studio 2005 or later and a moderately specified desktop computer. You will also need a licensed copy of the Telogis GeoBase SDK. A 30-day free-of-charge trial may be downloaded from the GeoBase developer portal: http://geozone.geobase.info/. Two versions of the trial SDK are available, one loaded with US (West Coast) map data, the other version loaded with map data for the UK. The locations used in this tutorial assume US data.

This tutorial also assumes that you are already familiar with the basics of creating a GeoBase-specific Visual Studio Project and adding a map control to a GeoBase project.

Setting up

Open a new instance of Visual Studio and create a new Windows Forms application.  Add geobase.net.dll as a reference. If the target framework for your application is a Client Profile, switch it so that it targets the full .NET framework.

Create a simple Form that contains a MapCtrl and Set its Zoom property to 1 and its Center property to a new LatLon value of 34.052020, -118.218174. Your form should look something like the following:

 In the code-behind, add the following directive to the top of the Form1.cs file:

using Telogis.GeoBase;

Create a Renderer class

We are now ready to create an object that renders on the map. Any object that renders on the map must implement the IMapRenderer interface, so start by adding the following class declaration:

public class DotRenderer : IMapRenderer {
    
    public DotRenderer() {
    }

    // This implements the IMapRenderer interface
    public void Render(Graphics graphics, RenderContext rc) {
      
    }

    // Required for the IMapRenderer interface -- describes when this renderer draws
    public RenderMode RequiredRendermodes {
    }

}

Here we have the skeleton of the class we are going to create. The Render method and the RequiredRenderModes property are both required to implement the IMapRenderer interface. Typically, an object that draws on the map would include other properties and methods, but in our case, we will just draw some large dots in hard-wired locations to illustrate how renderers work.

First,  let's implement the RequiredRendermodes property.  GeoBase maps are drawn in stages, and each stage is represented by a different RenderMode.  The RequiredRendermodes property tells the map at which of the drawing stages it should call the Render method to let your object draw itself on the map. Because we want to see the effects of different render modes, let's tell the map to call our Render method for every one of the four possible stages:

   public RenderMode RequiredRendermodes {
        get { return RenderMode.PreMap | RenderMode.PreLabelling | 
                     RenderMode.Labelling | RenderMode.PostLabelling; }
    }

Our renderer will draw simple circles on the map -- one for each of these render modes. Before we do that, however, let's declare some class members for the center of those circles: one for each render mode:

    LatLon premapPoint = new LatLon(34.0534, -118.2204);
    LatLon prelabellingPoint = new LatLon(34.0534, -118.2155);
    LatLon labellingPoint = new LatLon(34.0511, -118.2155);
    LatLon postlabellingPoint = new LatLon(34.0511, -118.2204);

Now we are ready to implement the Render method to do the actual drawing. We will draw a different colored dot on the map at each stage, so that we can see how the different render modes work:

  public void Render(Graphics graphics, RenderContext rc) {
        Brush brush = null;
        LatLon center = rc.Map.Center;
        int x, y;

        // choose the brush and center based on the render mode
        switch (rc.Mode) {
            case RenderMode.PreMap:
                brush = new SolidBrush(Color.Red);
                center = premapPoint;
                break;
            case RenderMode.PreLabelling:
                brush = new SolidBrush(Color.Blue);
                center = prelabellingPoint;
                break;
            case RenderMode.Labelling:
                brush = new SolidBrush(Color.Yellow);
                center = labellingPoint;
                break;
            case RenderMode.PostLabelling:
                brush = new SolidBrush(Color.Green);
                center = postlabellingPoint;
                break;
        }
        if (rc.Map.Contains(center)) {
            // Convert center from lat/lon to screen coordinates
            rc.Map.LatLontoXY(out x, out y, center);
            // Compute boundaries of circle
            System.Drawing.Rectangle rect = new System.Drawing.Rectangle(x-100, y-100, 200, 200);

            graphics.FillEllipse(brush, rect);        
        }
    }

This method  makes use of two parameters. The graphics parameter provides us with a surface to draw on, and the rc parameter gives us access to the map and indicates the current render mode.

Now that we have a renderer, all we need to do is assign it to the map. We can do that in the Form1 constructor, right after the call to InitializeComponent:

public Form1() {
      InitializeComponent();
      mapCtrl1.Renderer = new DotRenderer();
}

If you run the application, you can see our three circles. Four circles are being rendered, but one is beneath the map and not visible:

The red circle was drawn in the RenderMode.PreMap stage. You can see that everything, including the streets of the map, are drawn on top of this circle concealing it. Next, the blue circle was drawn in the RenderMode.PreLabelling stage. It appears on top of the streets, but labels and highway shields appear on top of it. The yellow circle was drawn in the RenderMode.Labelling stage. It is drawn on top of the blue circle, but at the same time as labels and highway shields, so the labels and highway shields are still drawn on top of it. Finally, the green circle was drawn in the RenderMode.PostLabelling stage. It sits on top of everything, even the labels and highway shields.

Use the Place and Test methods

The RenderContext object that is passed to our Render method contains two useful methods, Place and Test,  that you can use to adjust to other objects on the map. Use of these methods is optional, but can help you ensure that your objects do not sit on top of each other (as our circles do).

The Place method is a way to indicate that you want to reserve the space where your object is drawn. Once an object has called Place, other objects can call the Test method to see if they are drawing on top of it.

Let's adjust our renderer so that it illustrates the use of Place and Test. In the PreLabelling stage (the blue circle), we will call Place. In the Labelling stage (the yellow circle), we will call Test, and avoid drawing on any objects that have called Place. To do this, replace the call to graphics.FillEllipse with the following:

if (rc.Mode == RenderMode.PreLabelling) {
    rc.Place(rect, 0, 0);
} 

if (rc.Mode == RenderMode.Labelling) { 
    if (rc.Test(rect, 0, 0)) {
        graphics.FillEllipse(brush, rect);
    }
}
else { 
    graphics.FillEllipse(brush, rect);
}

If we run the application again, we see the following:

There are two differences to notice:

  • The labels and highway shields no longer appear on top of the blue circle. This is because of the call to Place.
  • The yellow circle no longer appears -- this is because its call to Test indicated that it would be drawing on top of the blue circle.

Conclusion

In this tutorial, we have seen how to create a simple renderer for drawing on the map. We have seen how the different render modes work, and how to use Place and Testin a renderer to adjust to the appearance of other objects.

Published, Jul 7th 2016, 22:36

Tagged under: dotnet geobase maps