Creating a custom renderer

When working with vector layers, styling is a great feature which offers us a lot of possibilities: fill color and opacity, stroke color, labels and text colors, and so on. But, what if we need more?

Every OpenLayers.Layer.Vector instance contains a renderer that is responsible to render the layer's features (such as points, paths, and polygons) on the map using the best technologies available in the browser. These can be the HTML5 Canvas element (http://en.wikipedia.org/wiki/Canvas_element) available in many modern browsers (such as Firefox or Chrome), SVG (http://en.wikipedia.org/wiki/Scalable_Vector_Graphics), or VML (http://en.wikipedia.org/wiki/Vector_Markup_Language).

When OpenLayers.Layer.Vector is initialized, OpenLayers looks for the best available rendering engine and creates an instance of OpenLayers.Renderer that will render the features on the map.

The goal of this recipe is to show how we can create a new renderer to improve the visualization of the features.

Creating a custom renderer

The previous screenshot shows some styled point geometry features and was rendered using the default OpenLayers renderer.

The following screenshot shows the same features rendered with our new renderer implementation:

Creating a custom renderer

Note

We are going to extend OpenLayers.Renderer.Canvas, to improve visualizations. This renderer works using the HTML5 Canvas element. This means the new renderer only will work on HTML5 compliant browsers. You can check if the canvas element is supported by your browser at: http://html5test.com

How to do it...

  1. Once an HTML file is created with OpenLayers dependencies, the first step is to include the file with the new renderer class' implementation:
    <script type="text/javascript" src="./recipes/ch08/gradientRenderer.js"></script>
  2. Now, as usual you can add the div element that will hold the map:
    <div id="ch08_renderer" style="width: 100%; height: 95%;"></div>
  3. In the JavaScript section, add the following code to initialize the map and add a base layer:
        var map = new OpenLayers.Map("ch08_renderer");    
        var osm = new OpenLayers.Layer.OSM();
        map.addLayer(osm);
  4. Center the map's viewport:
        var center = new OpenLayers.LonLat(-80,40);
        center.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
        map.setCenter(center, 5);
  5. Create a StyleMap instance with a unique value rule based on the POP_RANK attribute of the features:
        var styles = {
            7: { pointRadius: 5, label: "${POP_RANK}", fillColor: "#FFF8DC", fillOpacity: 0.6},
            6: { pointRadius: 8, label: "${POP_RANK}", fillColor: "#FFE4C4", fillOpacity: 0.6},
            5: { pointRadius: 11, label: "${POP_RANK}", fillColor: "#DEB887", fillOpacity: 0.6},
            4: { pointRadius: 14, label: "${POP_RANK}", fillColor: "#DAA520", fillOpacity: 0.7},
            3: { pointRadius: 16, label: "${POP_RANK}", fillColor: "#CD853F", fillOpacity: 0.8},
            2: { pointRadius: 19, label: "${POP_RANK}", fillColor: "#A0522D", fillOpacity: 0.9},
            1: { pointRadius: 22, label: "${POP_RANK}", fillColor: "#B22222", fillOpacity: 1.0}
        };
    
        var styleMap = new OpenLayers.StyleMap();
        styleMap.addUniqueValueRules("default", "POP_RANK", styles);
  6. Create the vector layer and add it to the map:
        var vectorLayer = new OpenLayers.Layer.Vector("Cities", {
            styleMap: styleMap,
            renderers: ["Gradient"],
            protocol: new OpenLayers.Protocol.HTTP({
                url: "http://localhost:8080/openlayers-cookbook/recipes/data/world_cities.json",
                format: new OpenLayers.Format.GeoJSON()
            }),
            strategies: [new OpenLayers.Strategy.Fixed()]
        });
        map.addLayer(vectorLayer);
  7. Let's go to see the OpenLayers.Renderer.Gradient implementation that beautifies our point's features that are rendered with a nice gradient style. Start creating a JavaScript file named gradientRenderer.js, which we have included previously in the main program. Following good practices, we start the commenting in the file:
    /** 
     * Class: OpenLayers.Renderer.Gradient 
     * Improved canvas based rendered to draw points using gradient. 
     * 
     * Inherits: 
     *  - <OpenLayers.Renderer.Canvas> 
     */ 
  8. Now, create an OpenLayers.Renderer.Canvas subclass named OpenLayers.Renderer.Gradient:
    OpenLayers.Renderer.Gradient = OpenLayers.Class(OpenLayers.Renderer.Canvas, { 
  9. The first method to implement in a new OpenLayers class must be the initialize() method:
        /** 
         * Constructor: OpenLayers.Renderer.Gradient 
         * 
         * Parameters: 
         * containerID - {<String>} 
         * options - {Object} Optional properties to be set on the renderer. 
         */ 
        initialize: function(containerID, options) { 
            OpenLayers.Renderer.Canvas.prototype.initialize.apply(this, arguments); 
        }, 
  10. Next, we implement the drawPoint() method, inherited from the OpenLayers.Renderer.Canvas class, which is responsible to render the point geometry features of the layer. Now, the method receives three parameters: the geometry object, the style to apply, and the feature identifier attribute:
        /** 
         * Method: drawPoint 
         * This method is only called by the renderer itself. 
         * 
         * Parameters: 
         * geometry - {<OpenLayers.Geometry>} 
         * style    - {Object} 
         * featureId - {String} 
         */ 
        drawPoint: function(geometry, style, featureId) { 
  11. From the geometry parameter, compute the exact pixel position of the point. This can be done with the getLocalXY() method inherited from the OpenLayers.Renderer.Canvas class:
            var pt = this.getLocalXY(geometry); 
            var p0 = pt[0]; 
            var p1 = pt[1]; 
            
            if(!isNaN(p0) && !isNaN(p1)) {  
                if(style.fill !== false) { 
                    this.setCanvasStyle("fill", style); 
  12. Using the fillColor and fillOpacity properties create a string for the color to be applied for the gradient:
                    // Create color from fillColor and fillOpacity properties. 
                    var color = style.fillColor; 
                    color += "ff"; 
                    color = color.replace("#", "0x"); 
                 
                    var colorRGBA = 'rgba(' + 
                    ((color >> 24) & 0xFF) + ',' + 
                    ((color >> 16) & 0xFF) + ',' + 
                    ((color >>  8) & 0xFF) + ',' + 
                    style.fillOpacity + ')'; 
  13. Then create a canvas gradient, centered in the feature's location and with the radius value specified in the feature's style:
                    var gradient = this.canvas.createRadialGradient(p0, p1, 0, p0, p1, style.pointRadius); 
  14. Define the necessary steps so that the gradient goes from white to the previously created RGB color:
                    gradient.addColorStop(0, '#FFFFFF'), 
                    gradient.addColorStop(0.9, colorRGBA); 
                    gradient.addColorStop(1, 'rgba(1,255,0,0)'), 
                
                    this.canvas.fillStyle = gradient;            
                    this.canvas.fillRect(0, 0, this.root.width, this.root.height); 
    
                    this.canvas.fill(); 
                } 
            } 
        }, 
  15. Finally, identify the new class by setting the CLASS_NAME property:
        CLASS_NAME: "OpenLayers.Renderer.Gradient " 
    });

How it works...

The important point of this recipe resides in one of the properties we have specified for the vector layer, the renderers.

The renderers property allows us to specify the set of OpenLayers.Renderer that the layer can make use of. Usually this property is never used and by default, its value is: renderers: ['SVG', 'VML', 'Canvas']. This means the supported renderer instances the layer can use are OpenLayers.Renderer.SVG, OpenLayers.Renderer.VML, and OpenLayers.Renderer.Canvas.

For this recipe, we have created the class OpenLayers.Renderer.Gradient, which we will describe later. The setting renderers: ["Gradient"] means we only want to allow the layer to work with an OpenLayers.Renderer.Gradient instance.

Let's describe in more detail how to initialize the vector layer:

  var vectorLayer = new OpenLayers.Layer.Vector("Cities", {
        styleMap: styleMap,
        renderers: ["Gradient"],
        protocol: new OpenLayers.Protocol.HTTP({
            url: "http://localhost:8080/openlayers-cookbook/recipes/data/world_cities.json",
            format: new OpenLayers.Format.GeoJSON()
        }),
        strategies: [new OpenLayers.Strategy.Fixed()]
    });

In addition to the renderers, we have used an OpenLayers.Protocol.HTTP instance with an OpenLayers.Format.GeoJSON instance, to load a GeoJSON file with some cities around the world. The features within the file have, among others, the POP_RANK attribute.

Thanks to the OpenLayers.Strategy.Fixed strategy instance, the layer loads the data source, through the previous protocol, only once. We have no need to load the file each time the map is zoomed.

Last, but not the least, we have set the styleMap property to a previously created OpenLayers.StyleMap instance:

    var styleMap = new OpenLayers.StyleMap();
    styleMap.addUniqueValueRules("default", "POP_RANK", styles);

This style map is defined making use of the unique value rule feature based on the POP_RANK attribute. This property takes values from 1 to 7, so we define a symbolizer hash style for each possible value, playing with the radius and fill color properties:

    var styles = {
        7: { pointRadius: 5, label: "${POP_RANK}", fillColor: "#FFF8DC", fillOpacity: 0.6},
        6: { pointRadius: 8, label: "${POP_RANK}", fillColor: "#FFE4C4", fillOpacity: 0.6},
        5: { pointRadius: 11, label: "${POP_RANK}", fillColor: "#DEB887", fillOpacity: 0.6},
        4: { pointRadius: 14, label: "${POP_RANK}", fillColor: "#DAA520", fillOpacity: 0.7},
        3: { pointRadius: 16, label: "${POP_RANK}", fillColor: "#CD853F", fillOpacity: 0.8},
        2: { pointRadius: 19, label: "${POP_RANK}", fillColor: "#A0522D", fillOpacity: 0.9},
        1: { pointRadius: 22, label: "${POP_RANK}", fillColor: "#B22222", fillOpacity: 1.0}
    };

For the main program, there is nothing more to comment, except for the way we have centered the map's viewport.

Because the base layer is OpenStreetMap, which by default uses an EPSG:900913 projection, and we have specified the center in the EPSG:4326 projection, we need to transform the coordinates to the appropriate map's projection.

There's more...

Usually, this initialize method starts calling the initialize method of its parent class and then sets the concrete properties of the instance.

In this case, our class has no specific property to initialize, so it is not strictly necessary to implement this method, but as an example, (and as a good practice) we have written the call to the parent class:

    initialize: function(containerID, options) {
        OpenLayers.Renderer.Canvas.prototype.initialize.apply(this, arguments); 
    }, 

The process followed within the OpenLayers.Renderer.Canvas class to render the features of a layer is a bit complex, but can be summarized as:

  • For each feature the class checks its geometry
  • Depending on the geometry type the class invokes a different method specially designed to render points, lines, or polygons

Because our renderer is implemented to beautify points we only have rewritten the drawPoint method, which is responsible to render point geometries.

The renderer we have defined here uses the HTML5 canvas element, because of this, the main part of the recipe is related to this technology.

Note

Lots of information about the HTML5 canvas element can be found on the Internet. We want to point to this tutorial from the Mozilla project: https://developer.mozilla.org/en/Canvas_tutorial.

A great exercise would be to create an SVG version of this renderer. This way, the possibility to render gradient points would be available in more browsers.

See also

  • The Creating a custom control recipe
  • The Styling features using symbolizers recipe in Chapter 7, Styling Features
  • The Defining custom rules to style features recipe in Chapter 7, Styling Features
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.228.138