| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711 |
- /* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for
- * full list of contributors). 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.
- *
- * Inherits:
- * - <OpenLayers.Renderer>
- */
- OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
-
- /**
- * APIProperty: hitDetection
- * {Boolean} Allow for hit detection of features. Default is true.
- */
- hitDetection: true,
-
- /**
- * Property: hitOverflow
- * {Number} The method for converting feature identifiers to color values
- * supports 16777215 sequential values. Two features cannot be
- * predictably detected if their identifiers differ by more than this
- * value. The hitOverflow allows for bigger numbers (but the
- * difference in values is still limited).
- */
- hitOverflow: 0,
- /**
- * 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: pendingRedraw
- * {Boolean} The renderer needs a redraw call to render features added while
- * the renderer was locked.
- */
- pendingRedraw: false,
-
- /**
- * Constructor: OpenLayers.Renderer.Canvas
- *
- * Parameters:
- * containerID - {<String>}
- * options - {Object} Optional properties to be set on the renderer.
- */
- initialize: function(containerID, options) {
- 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 = {};
- if (this.hitDetection) {
- this.hitCanvas = document.createElement("canvas");
- this.hitContext = this.hitCanvas.getContext("2d");
- }
- },
-
- /**
- * 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>}
- * featureId - {String}
- */
- eraseGeometry: function(geometry, featureId) {
- this.eraseFeatures(this.features[featureId][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: 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();
- var root = this.root;
- root.style.width = size.w + "px";
- root.style.height = size.h + "px";
- root.width = size.w;
- root.height = size.h;
- this.resolution = null;
- if (this.hitDetection) {
- var hitCanvas = this.hitCanvas;
- hitCanvas.style.width = size.w + "px";
- hitCanvas.style.height = size.h + "px";
- hitCanvas.width = size.w;
- hitCanvas.height = size.h;
- }
- },
-
- /**
- * Method: drawFeature
- * Draw the feature. Stores the feature in the features list,
- * then redraws the layer.
- *
- * Parameters:
- * feature - {<OpenLayers.Feature.Vector>}
- * style - {<Object>}
- *
- * Returns:
- * {Boolean} The feature has been drawn completely. If the feature has no
- * geometry, undefined will be returned. If the feature is not rendered
- * for other reasons, false will be returned.
- */
- drawFeature: function(feature, style) {
- var rendered;
- if (feature.geometry) {
- style = this.applyDefaultSymbolizer(style || feature.style);
- // don't render if display none or feature outside extent
- var bounds = feature.geometry.getBounds();
- rendered = (style.display !== "none") && !!bounds &&
- bounds.intersectsBounds(this.extent);
- if (rendered) {
- // keep track of what we have rendered for redraw
- this.features[feature.id] = [feature, style];
- }
- else {
- // remove from features tracked for redraw
- delete(this.features[feature.id]);
- }
- this.pendingRedraw = true;
- }
- if (this.pendingRedraw && !this.locked) {
- this.redraw();
- this.pendingRedraw = false;
- }
- return rendered;
- },
- /**
- * Method: drawGeometry
- * Used when looping (in redraw) over the features; draws
- * the canvas.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry>}
- * style - {Object}
- */
- drawGeometry: function(geometry, style, featureId) {
- 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, featureId);
- }
- return;
- }
- switch (geometry.CLASS_NAME) {
- case "OpenLayers.Geometry.Point":
- this.drawPoint(geometry, style, featureId);
- break;
- case "OpenLayers.Geometry.LineString":
- this.drawLineString(geometry, style, featureId);
- break;
- case "OpenLayers.Geometry.LinearRing":
- this.drawLinearRing(geometry, style, featureId);
- break;
- case "OpenLayers.Geometry.Polygon":
- this.drawPolygon(geometry, style, featureId);
- break;
- default:
- break;
- }
- },
- /**
- * Method: drawExternalGraphic
- * Called to draw External graphics.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry>}
- * style - {Object}
- * featureId - {String}
- */
- drawExternalGraphic: function(geometry, style, featureId) {
- var img = new Image();
- 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 onLoad = function() {
- if(!this.features[featureId]) {
- return;
- }
- var pt = this.getLocalXY(geometry);
- var p0 = pt[0];
- var p1 = pt[1];
- if(!isNaN(p0) && !isNaN(p1)) {
- var x = (p0 + xOffset) | 0;
- var y = (p1 + yOffset) | 0;
- var canvas = this.canvas;
- canvas.globalAlpha = opacity;
- var factor = OpenLayers.Renderer.Canvas.drawImageScaleFactor ||
- (OpenLayers.Renderer.Canvas.drawImageScaleFactor =
- /android 2.1/.test(navigator.userAgent.toLowerCase()) ?
- // 320 is the screen width of the G1 phone, for
- // which drawImage works out of the box.
- 320 / window.screen.width : 1
- );
- canvas.drawImage(
- img, x*factor, y*factor, width*factor, height*factor
- );
- if (this.hitDetection) {
- this.setHitContextStyle("fill", featureId);
- this.hitContext.fillRect(x, y, width, height);
- }
- }
- };
- img.onload = OpenLayers.Function.bind(onLoad, this);
- img.src = style.externalGraphic;
- },
- /**
- * 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: featureIdToHex
- * Convert a feature ID string into an RGB hex string.
- *
- * Parameters:
- * featureId - {String} Feature id
- *
- * Returns:
- * {String} RGB hex string.
- */
- featureIdToHex: function(featureId) {
- var id = Number(featureId.split("_").pop()) + 1; // zero for no feature
- if (id >= 16777216) {
- this.hitOverflow = id - 16777215;
- id = id % 16777216 + 1;
- }
- var hex = "000000" + id.toString(16);
- var len = hex.length;
- hex = "#" + hex.substring(len-6, len);
- return hex;
- },
-
- /**
- * Method: setHitContextStyle
- * Prepare the hit canvas for drawing by setting various global settings.
- *
- * Parameters:
- * type - {String} one of 'stroke', 'fill', or 'reset'
- * featureId - {String} The feature id.
- * symbolizer - {<OpenLayers.Symbolizer>} The symbolizer.
- */
- setHitContextStyle: function(type, featureId, symbolizer) {
- var hex = this.featureIdToHex(featureId);
- if (type == "fill") {
- this.hitContext.globalAlpha = 1.0;
- this.hitContext.fillStyle = hex;
- } else if (type == "stroke") {
- this.hitContext.globalAlpha = 1.0;
- this.hitContext.strokeStyle = hex;
- // bump up stroke width to deal with antialiasing
- this.hitContext.lineWidth = symbolizer.strokeWidth + 2;
- } else {
- this.hitContext.globalAlpha = 0;
- this.hitContext.lineWidth = 1;
- }
- },
- /**
- * Method: drawPoint
- * This method is only called by the renderer itself.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry>}
- * style - {Object}
- * featureId - {String}
- */
- drawPoint: function(geometry, style, featureId) {
- if(style.graphic !== false) {
- if(style.externalGraphic) {
- this.drawExternalGraphic(geometry, style, featureId);
- } else {
- var pt = this.getLocalXY(geometry);
- var p0 = pt[0];
- var p1 = pt[1];
- if(!isNaN(p0) && !isNaN(p1)) {
- var twoPi = Math.PI*2;
- var radius = style.pointRadius;
- if(style.fill !== false) {
- this.setCanvasStyle("fill", style);
- this.canvas.beginPath();
- this.canvas.arc(p0, p1, radius, 0, twoPi, true);
- this.canvas.fill();
- if (this.hitDetection) {
- this.setHitContextStyle("fill", featureId, style);
- this.hitContext.beginPath();
- this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
- this.hitContext.fill();
- }
- }
- if(style.stroke !== false) {
- this.setCanvasStyle("stroke", style);
- this.canvas.beginPath();
- this.canvas.arc(p0, p1, radius, 0, twoPi, true);
- this.canvas.stroke();
- if (this.hitDetection) {
- this.setHitContextStyle("stroke", featureId, style);
- this.hitContext.beginPath();
- this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
- this.hitContext.stroke();
- }
- this.setCanvasStyle("reset");
- }
- }
- }
- }
- },
-
- /**
- * Method: drawLineString
- * This method is only called by the renderer itself.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry>}
- * style - {Object}
- * featureId - {String}
- */
- drawLineString: function(geometry, style, featureId) {
- style = OpenLayers.Util.applyDefaults({fill: false}, style);
- this.drawLinearRing(geometry, style, featureId);
- },
-
- /**
- * Method: drawLinearRing
- * This method is only called by the renderer itself.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry>}
- * style - {Object}
- * featureId - {String}
- */
- drawLinearRing: function(geometry, style, featureId) {
- if (style.fill !== false) {
- this.setCanvasStyle("fill", style);
- this.renderPath(this.canvas, geometry, style, featureId, "fill");
- if (this.hitDetection) {
- this.setHitContextStyle("fill", featureId, style);
- this.renderPath(this.hitContext, geometry, style, featureId, "fill");
- }
- }
- if (style.stroke !== false) {
- this.setCanvasStyle("stroke", style);
- this.renderPath(this.canvas, geometry, style, featureId, "stroke");
- if (this.hitDetection) {
- this.setHitContextStyle("stroke", featureId, style);
- this.renderPath(this.hitContext, geometry, style, featureId, "stroke");
- }
- }
- this.setCanvasStyle("reset");
- },
-
- /**
- * Method: renderPath
- * Render a path with stroke and optional fill.
- */
- renderPath: function(context, geometry, style, featureId, type) {
- var components = geometry.components;
- var len = components.length;
- context.beginPath();
- var start = this.getLocalXY(components[0]);
- var x = start[0];
- var y = start[1];
- if (!isNaN(x) && !isNaN(y)) {
- context.moveTo(start[0], start[1]);
- for (var i=1; i<len; ++i) {
- var pt = this.getLocalXY(components[i]);
- context.lineTo(pt[0], pt[1]);
- }
- if (type === "fill") {
- context.fill();
- } else {
- context.stroke();
- }
- }
- },
-
- /**
- * Method: drawPolygon
- * This method is only called by the renderer itself.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry>}
- * style - {Object}
- * featureId - {String}
- */
- drawPolygon: function(geometry, style, featureId) {
- var components = geometry.components;
- var len = components.length;
- this.drawLinearRing(components[0], style, featureId);
- // erase inner rings
- for (var i=1; i<len; ++i) {
- /**
- * Note that this is overly agressive. Here we punch holes through
- * all previously rendered features on the same canvas. A better
- * solution for polygons with interior rings would be to draw the
- * polygon on a sketch canvas first. We could erase all holes
- * there and then copy the drawing to the layer canvas.
- * TODO: http://trac.osgeo.org/openlayers/ticket/3130
- */
- this.canvas.globalCompositeOperation = "destination-out";
- if (this.hitDetection) {
- this.hitContext.globalCompositeOperation = "destination-out";
- }
- this.drawLinearRing(
- components[i],
- OpenLayers.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style),
- featureId
- );
- this.canvas.globalCompositeOperation = "source-over";
- if (this.hitDetection) {
- this.hitContext.globalCompositeOperation = "source-over";
- }
- this.drawLinearRing(
- components[i],
- OpenLayers.Util.applyDefaults({fill: false}, style),
- featureId
- );
- }
- },
-
- /**
- * Method: drawText
- * This method is only called by the renderer itself.
- *
- * Parameters:
- * location - {<OpenLayers.Point>}
- * style - {Object}
- */
- drawText: function(location, style) {
- style = OpenLayers.Util.extend({
- fontColor: "#000000",
- labelAlign: "cm"
- }, style);
- var pt = this.getLocalXY(location);
- this.setCanvasStyle("reset");
- this.canvas.fillStyle = style.fontColor;
- this.canvas.globalAlpha = style.fontOpacity || 1.0;
- var fontStyle = [style.fontStyle ? style.fontStyle : "normal",
- "normal", // "font-variant" not supported
- style.fontWeight ? style.fontWeight : "normal",
- style.fontSize ? style.fontSize : "1em",
- style.fontFamily ? style.fontFamily : "sans-serif"].join(" ");
- var labelRows = style.label.split('\n');
- var numRows = labelRows.length;
- if (this.canvas.fillText) {
- // HTML5
- this.canvas.font = fontStyle;
- this.canvas.textAlign =
- OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
- "center";
- this.canvas.textBaseline =
- OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[1]] ||
- "middle";
- var vfactor =
- OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
- if (vfactor == null) {
- vfactor = -.5;
- }
- var lineHeight =
- this.canvas.measureText('Mg').height ||
- this.canvas.measureText('xx').width;
- pt[1] += lineHeight*vfactor*(numRows-1);
- for (var i = 0; i < numRows; i++) {
- this.canvas.fillText(labelRows[i], pt[0], pt[1] + (lineHeight*i));
- }
- } 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 hfactor =
- OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[0]];
- if (hfactor == null) {
- hfactor = -.5;
- }
- var vfactor =
- OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
- if (vfactor == null) {
- vfactor = -.5;
- }
- var lineHeight = this.canvas.mozMeasureText('xx');
- pt[1] += lineHeight*(1 + (vfactor*numRows));
- for (var i = 0; i < numRows; i++) {
- var x = pt[0] + (hfactor*this.canvas.mozMeasureText(labelRows[i]));
- var y = pt[1] + (i*lineHeight);
- this.canvas.translate(x, y);
- this.canvas.mozDrawText(labelRows[i]);
- this.canvas.translate(-x, -y);
- }
- }
- 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.
- */
- clear: function() {
- var height = this.root.height;
- var width = this.root.width;
- this.canvas.clearRect(0, 0, width, height);
- this.features = {};
- if (this.hitDetection) {
- this.hitContext.clearRect(0, 0, width, height);
- }
- },
- /**
- * Method: getFeatureIdFromEvent
- * Returns a feature id from an event on the renderer.
- *
- * Parameters:
- * evt - {<OpenLayers.Event>}
- *
- * Returns:
- * {<OpenLayers.Feature.Vector} A feature or null. This method returns a
- * feature instead of a feature id to avoid an unnecessary lookup on the
- * layer.
- */
- getFeatureIdFromEvent: function(evt) {
- var feature = null;
- if (this.hitDetection) {
- // this dragging check should go in the feature handler
- if (!this.map.dragging) {
- var xy = evt.xy;
- var x = xy.x | 0;
- var y = xy.y | 0;
- var data = this.hitContext.getImageData(x, y, 1, 1).data;
- if (data[3] === 255) { // antialiased
- var id = data[2] + (256 * (data[1] + (256 * data[0])));
- if (id) {
- feature = this.features["OpenLayers.Feature.Vector_" + (id - 1 + this.hitOverflow)][0];
- }
- }
- }
- }
- return feature;
- },
-
- /**
- * 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(!(OpenLayers.Util.isArray(features))) {
- 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) {
- var height = this.root.height;
- var width = this.root.width;
- this.canvas.clearRect(0, 0, width, height);
- if (this.hitDetection) {
- this.hitContext.clearRect(0, 0, width, height);
- }
- 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];
- this.drawGeometry(feature.geometry, style, feature.id);
- 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",
- "t": "top",
- "b": "bottom"
- };
- /**
- * Constant: OpenLayers.Renderer.Canvas.LABEL_FACTOR
- * {Object}
- */
- OpenLayers.Renderer.Canvas.LABEL_FACTOR = {
- "l": 0,
- "r": -1,
- "t": 0,
- "b": -1
- };
- /**
- * Constant: OpenLayers.Renderer.Canvas.drawImageScaleFactor
- * {Number} Scale factor to apply to the canvas drawImage arguments. This
- * is always 1 except for Android 2.1 devices, to work around
- * http://code.google.com/p/android/issues/detail?id=5141.
- */
- OpenLayers.Renderer.Canvas.drawImageScaleFactor = null;
|