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.
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:
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
<script type="text/javascript" src="./recipes/ch08/gradientRenderer.js"></script>
div
element that will hold the map:<div id="ch08_renderer" style="width: 100%; height: 95%;"></div>
var map = new OpenLayers.Map("ch08_renderer"); var osm = new OpenLayers.Layer.OSM(); map.addLayer(osm);
var center = new OpenLayers.LonLat(-80,40); center.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913")); map.setCenter(center, 5);
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);
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);
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> */
OpenLayers.Renderer.Canvas
subclass named OpenLayers.Renderer.Gradient
:OpenLayers.Renderer.Gradient = OpenLayers.Class(OpenLayers.Renderer.Canvas, {
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); },
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) {
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);
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 + ')';
var gradient = this.canvas.createRadialGradient(p0, p1, 0, p0, p1, style.pointRadius);
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(); } } },
CLASS_NAME
property:CLASS_NAME: "OpenLayers.Renderer.Gradient " });
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.
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:
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.
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.
3.149.228.138