Files
openlayers/lib/OpenLayers/Renderer/Canvas.js
ahocevar cc2e19d789 added support for text labels. This also adds getCentroid methods to all
geometries. Thanks crschmidt for the great help with this patch, and 
thanks to camptocamp for the initial work on this and rcoup for creating 
the first patches. r=crschmidt (closes #1895)


git-svn-id: http://svn.openlayers.org/trunk/openlayers@9262 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
2009-04-10 16:05:26 +00:00

496 lines
16 KiB
JavaScript

/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
* license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
* full text of the license. */
/**
* @requires OpenLayers/Renderer.js
*/
/**
* Class: OpenLayers.Renderer.Canvas
* A renderer based on the 2D 'canvas' drawing element.element
*
* Inherits:
* - <OpenLayers.Renderer>
*/
OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
/**
* Property: canvas
* {Canvas} The canvas context object.
*/
canvas: null,
/**
* Property: features
* {Object} Internal object of feature/style pairs for use in redrawing the layer.
*/
features: null,
/**
* Property: geometryMap
* {Object} Geometry -> Feature lookup table. Used by eraseGeometry to
* lookup features to remove from our internal table (this.features)
* when erasing geoms.
*/
geometryMap: null,
/**
* Constructor: OpenLayers.Renderer.Canvas
*
* Parameters:
* containerID - {<String>}
*/
initialize: function(containerID) {
OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
this.root = document.createElement("canvas");
this.container.appendChild(this.root);
this.canvas = this.root.getContext("2d");
this.features = {};
this.geometryMap = {};
},
/**
* Method: eraseGeometry
* Erase a geometry from the renderer. Because the Canvas renderer has
* 'memory' of the features that it has drawn, we have to remove the
* feature so it doesn't redraw.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
*/
eraseGeometry: function(geometry) {
this.eraseFeatures(this.features[this.geometryMap[geometry.id]][0]);
},
/**
* APIMethod: supported
*
* Returns:
* {Boolean} Whether or not the browser supports the renderer class
*/
supported: function() {
var canvas = document.createElement("canvas");
return !!canvas.getContext;
},
/**
* Method: setExtent
* Set the visible part of the layer.
*
* Resolution has probably changed, so we nullify the resolution
* cache (this.resolution), then redraw.
*
* Parameters:
* extent - {<OpenLayers.Bounds>}
*/
setExtent: function(extent) {
this.extent = extent.clone();
this.resolution = null;
this.redraw();
},
/**
* Method: setSize
* Sets the size of the drawing surface.
*
* Once the size is updated, redraw the canvas.
*
* Parameters:
* size - {<OpenLayers.Size>}
*/
setSize: function(size) {
this.size = size.clone();
this.root.style.width = size.w + "px";
this.root.style.height = size.h + "px";
this.root.width = size.w;
this.root.height = size.h;
this.resolution = null;
},
/**
* Method: drawFeature
* Draw the feature. Stores the feature in the features list,
* then redraws the layer.
*
* Parameters:
* feature - {<OpenLayers.Feature.Vector>}
* style - {<Object>}
*/
drawFeature: function(feature, style) {
if(style == null) {
style = feature.style;
}
style = OpenLayers.Util.extend({
'fillColor': '#000000',
'strokeColor': '#000000',
'strokeWidth': 2,
'fillOpacity': 1,
'strokeOpacity': 1
}, style);
this.features[feature.id] = [feature, style];
if (feature.geometry) {
this.geometryMap[feature.geometry.id] = feature.id;
}
this.redraw();
},
/**
* Method: drawGeometry
* Used when looping (in redraw) over the features; draws
* the canvas.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
*/
drawGeometry: function(geometry, style) {
var className = geometry.CLASS_NAME;
if ((className == "OpenLayers.Geometry.Collection") ||
(className == "OpenLayers.Geometry.MultiPoint") ||
(className == "OpenLayers.Geometry.MultiLineString") ||
(className == "OpenLayers.Geometry.MultiPolygon")) {
for (var i = 0; i < geometry.components.length; i++) {
this.drawGeometry(geometry.components[i], style);
}
return;
};
switch (geometry.CLASS_NAME) {
case "OpenLayers.Geometry.Point":
this.drawPoint(geometry, style);
break;
case "OpenLayers.Geometry.LineString":
this.drawLineString(geometry, style);
break;
case "OpenLayers.Geometry.LinearRing":
this.drawLinearRing(geometry, style);
break;
case "OpenLayers.Geometry.Polygon":
this.drawPolygon(geometry, style);
break;
default:
break;
}
},
/**
* Method: drawExternalGraphic
* Called to draw External graphics.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
*/
drawExternalGraphic: function(pt, style) {
var img = new Image();
img.src = style.externalGraphic;
if(style.graphicTitle) {
img.title=style.graphicTitle;
}
var width = style.graphicWidth || style.graphicHeight;
var height = style.graphicHeight || style.graphicWidth;
width = width ? width : style.pointRadius*2;
height = height ? height : style.pointRadius*2;
var xOffset = (style.graphicXOffset != undefined) ?
style.graphicXOffset : -(0.5 * width);
var yOffset = (style.graphicYOffset != undefined) ?
style.graphicYOffset : -(0.5 * height);
var opacity = style.graphicOpacity || style.fillOpacity;
var context = { img: img,
x: (pt[0]+xOffset),
y: (pt[1]+yOffset),
width: width,
height: height,
canvas: this.canvas };
img.onload = OpenLayers.Function.bind( function() {
this.canvas.drawImage(this.img, this.x,
this.y, this.width, this.height);
}, context);
},
/**
* Method: setCanvasStyle
* Prepare the canvas for drawing by setting various global settings.
*
* Parameters:
* type - {String} one of 'stroke', 'fill', or 'reset'
* style - {Object} Symbolizer hash
*/
setCanvasStyle: function(type, style) {
if (type == "fill") {
this.canvas.globalAlpha = style['fillOpacity'];
this.canvas.fillStyle = style['fillColor'];
} else if (type == "stroke") {
this.canvas.globalAlpha = style['strokeOpacity'];
this.canvas.strokeStyle = style['strokeColor'];
this.canvas.lineWidth = style['strokeWidth'];
} else {
this.canvas.globalAlpha = 0;
this.canvas.lineWidth = 1;
}
},
/**
* Method: drawPoint
* This method is only called by the renderer itself.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
*/
drawPoint: function(geometry, style) {
var pt = this.getLocalXY(geometry);
if (style.externalGraphic) {
this.drawExternalGraphic(pt, style);
} else {
this.setCanvasStyle("fill", style);
this.canvas.beginPath();
this.canvas.arc(pt[0], pt[1], 6, 0, Math.PI*2, true);
this.canvas.fill();
this.setCanvasStyle("stroke", style);
this.canvas.beginPath();
this.canvas.arc(pt[0], pt[1], 6, 0, Math.PI*2, true);
this.canvas.stroke();
this.setCanvasStyle("reset");
}
},
/**
* Method: drawLineString
* This method is only called by the renderer itself.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
*/
drawLineString: function(geometry, style) {
this.setCanvasStyle("stroke", style);
this.canvas.beginPath();
var start = this.getLocalXY(geometry.components[0]);
this.canvas.moveTo(start[0], start[1]);
for(var i = 1; i < geometry.components.length; i++) {
var pt = this.getLocalXY(geometry.components[i]);
this.canvas.lineTo(pt[0], pt[1]);
}
this.canvas.stroke();
this.setCanvasStyle("reset");
},
/**
* Method: drawLinearRing
* This method is only called by the renderer itself.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
*/
drawLinearRing: function(geometry, style) {
this.setCanvasStyle("fill", style);
this.canvas.beginPath();
var start = this.getLocalXY(geometry.components[0]);
this.canvas.moveTo(start[0], start[1]);
for(var i = 1; i < geometry.components.length - 1 ; i++) {
var pt = this.getLocalXY(geometry.components[i]);
this.canvas.lineTo(pt[0], pt[1]);
}
this.canvas.fill();
var oldWidth = this.canvas.lineWidth;
this.setCanvasStyle("stroke", style);
this.canvas.beginPath();
var start = this.getLocalXY(geometry.components[0]);
this.canvas.moveTo(start[0], start[1]);
for(var i = 1; i < geometry.components.length; i++) {
var pt = this.getLocalXY(geometry.components[i]);
this.canvas.lineTo(pt[0], pt[1]);
}
this.canvas.stroke();
this.setCanvasStyle("reset");
},
/**
* Method: drawPolygon
* This method is only called by the renderer itself.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
*/
drawPolygon: function(geometry, style) {
this.drawLinearRing(geometry.components[0], style);
for (var i = 1; i < geometry.components.length; i++) {
this.drawLinearRing(geometry.components[i], {
fillOpacity: 0,
strokeWidth: 0,
strokeOpacity: 0,
strokeColor: '#000000',
fillColor: '#000000'}
); // inner rings are 'empty'
}
},
/**
* Method: drawText
* This method is only called by the renderer itself.
*
* Parameters:
* location - {<OpenLayers.Point>}
* style - {Object}
*/
drawText: function(location, style) {
var pt = this.getLocalXY(location);
this.setCanvasStyle("reset");
this.canvas.fillStyle = style.fontColor;
this.canvas.globalAlpha = 1;
var fontStyle = style.fontWeight + " " + style.fontSize + " " + style.fontFamily;
if (this.canvas.fillText) {
// HTML5
var labelAlign =
OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
"middle";
this.canvas.font = fontStyle;
this.canvas.textAlign = labelAlign;
this.canvas.fillText(style.label, pt[0], pt[1]);
} else if (this.canvas.mozDrawText) {
// Mozilla pre-Gecko1.9.1 (<FF3.1)
this.canvas.mozTextStyle = fontStyle;
// No built-in text alignment, so we measure and adjust the position
var len = this.canvas.mozMeasureText(style.label);
switch(style.labelAlign[0]) {
case "l":
break;
case "r":
pt[0] -= len;
break;
case "c":
default:
pt[0] -= len / 2;
}
this.canvas.translate(pt[0], pt[1]);
this.canvas.mozDrawText(style.label);
this.canvas.translate(-1*pt[0], -1*pt[1]);
}
this.setCanvasStyle("reset");
},
/**
* Method: getLocalXY
* transform geographic xy into pixel xy
*
* Parameters:
* point - {<OpenLayers.Geometry.Point>}
*/
getLocalXY: function(point) {
var resolution = this.getResolution();
var extent = this.extent;
var x = (point.x / resolution + (-extent.left / resolution));
var y = ((extent.top / resolution) - point.y / resolution);
return [x, y];
},
/**
* Method: clear
* Clear all vectors from the renderer.
* virtual function.
*/
clear: function() {
this.canvas.clearRect(0, 0, this.root.width, this.root.height);
},
/**
* Method: getFeatureIdFromEvent
* Returns a feature id from an event on the renderer.
*
* Parameters:
* evt - {<OpenLayers.Event>}
*
* Returns:
* {String} A feature id or null.
*/
getFeatureIdFromEvent: function(evt) {
var loc = this.map.getLonLatFromPixel(evt.xy);
var resolution = this.getResolution();
var bounds = new OpenLayers.Bounds(loc.lon - resolution * 5,
loc.lat - resolution * 5,
loc.lon + resolution * 5,
loc.lat + resolution * 5);
var geom = bounds.toGeometry();
for (var feat in this.features) {
if (!this.features.hasOwnProperty(feat)) { continue; }
if (this.features[feat][0].geometry.intersects(geom)) {
return feat;
}
}
return null;
},
/**
* Method: eraseFeatures
* This is called by the layer to erase features; removes the feature from
* the list, then redraws the layer.
*
* Parameters:
* features - {Array(<OpenLayers.Feature.Vector>)}
*/
eraseFeatures: function(features) {
if(!(features instanceof Array)) {
features = [features];
}
for(var i=0; i<features.length; ++i) {
delete this.features[features[i].id];
}
this.redraw();
},
/**
* Method: redraw
* The real 'meat' of the function: any time things have changed,
* redraw() can be called to loop over all the data and (you guessed
* it) redraw it. Unlike Elements-based Renderers, we can't interact
* with things once they're drawn, to remove them, for example, so
* instead we have to just clear everything and draw from scratch.
*/
redraw: function() {
if (!this.locked) {
this.clear();
var labelMap = [];
var feature, style;
for (var id in this.features) {
if (!this.features.hasOwnProperty(id)) { continue; }
feature = this.features[id][0];
style = this.features[id][1];
if (!feature.geometry) { continue; }
this.drawGeometry(feature.geometry, style);
if(style.label) {
labelMap.push([feature, style]);
}
}
var item;
for (var i=0; len=labelMap.length, i<len; ++i) {
item = labelMap[i];
this.drawText(item[0].geometry.getCentroid(), item[1]);
}
}
},
CLASS_NAME: "OpenLayers.Renderer.Canvas"
});
/**
* Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN
* {Object}
*/
OpenLayers.Renderer.Canvas.LABEL_ALIGN = {
"l": "left",
"r": "right"
};