Adding in support for hit detection with canvas (and rendering holes in polygons). r=fredj (closes #3207)
git-svn-id: http://svn.openlayers.org/trunk/openlayers@11849 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
24
examples/canvas-hit-detection.html
Normal file
24
examples/canvas-hit-detection.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>OpenLayers Canvas Hit Detection Example</title>
|
||||
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
|
||||
<link rel="stylesheet" href="../theme/default/google.css" type="text/css">
|
||||
<link rel="stylesheet" href="style.css" type="text/css">
|
||||
<script src="../lib/OpenLayers.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="title">Feature Hit Detection with Canvas</h1>
|
||||
<p id="shortdesc">
|
||||
Demonstrates detection of feature hits with the canvas renderer.
|
||||
</p>
|
||||
<div id="map" class="smallmap"></div>
|
||||
<div id="docs">
|
||||
<p>
|
||||
View the <a href="canvas-hit-detection.js" target="_blank">canvas-hit-detection.js</a>
|
||||
source to see how this is done.
|
||||
</p>
|
||||
</div>
|
||||
<script src="canvas-hit-detection.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
88
examples/canvas-hit-detection.js
Normal file
88
examples/canvas-hit-detection.js
Normal file
@@ -0,0 +1,88 @@
|
||||
|
||||
// create some sample features
|
||||
var Feature = OpenLayers.Feature.Vector;
|
||||
var Geometry = OpenLayers.Geometry;
|
||||
var features = [
|
||||
new Feature(new Geometry.Point(-90, 45)),
|
||||
new Feature(
|
||||
new Geometry.Point(0, 45),
|
||||
{cls: "one"}
|
||||
),
|
||||
new Feature(
|
||||
new Geometry.Point(90, 45),
|
||||
{cls: "two"}
|
||||
),
|
||||
new Feature(
|
||||
Geometry.fromWKT("LINESTRING(-110 -60, -80 -40, -50 -60, -20 -40)")
|
||||
),
|
||||
new Feature(
|
||||
Geometry.fromWKT("POLYGON((20 -20, 110 -20, 110 -80, 20 -80, 20 -20), (40 -40, 90 -40, 90 -60, 40 -60, 40 -40))")
|
||||
)
|
||||
];
|
||||
|
||||
// create rule based styles
|
||||
var Rule = OpenLayers.Rule;
|
||||
var Filter = OpenLayers.Filter;
|
||||
var style = new OpenLayers.Style({
|
||||
pointRadius: 10,
|
||||
strokeWidth: 2,
|
||||
strokeOpacity: 0.7,
|
||||
strokeColor: "navy",
|
||||
fillColor: "#ffcc66",
|
||||
fillOpacity: 1
|
||||
}, {
|
||||
rules: [
|
||||
new Rule({
|
||||
filter: new Filter.Comparison({
|
||||
type: "==",
|
||||
property: "cls",
|
||||
value: "one"
|
||||
}),
|
||||
symbolizer: {
|
||||
externalGraphic: "../img/marker-blue.png"
|
||||
}
|
||||
}),
|
||||
new Rule({
|
||||
filter: new Filter.Comparison({
|
||||
type: "==",
|
||||
property: "cls",
|
||||
value: "two"
|
||||
}),
|
||||
symbolizer: {
|
||||
externalGraphic: "../img/marker-green.png"
|
||||
}
|
||||
}),
|
||||
new Rule({
|
||||
elseFilter: true,
|
||||
symbolizer: {
|
||||
graphicName: "circle"
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
var layer = new OpenLayers.Layer.Vector(null, {
|
||||
styleMap: new OpenLayers.StyleMap({
|
||||
"default": style,
|
||||
select: {
|
||||
fillColor: "red",
|
||||
pointRadius: 13,
|
||||
strokeColor: "yellow",
|
||||
strokeWidth: 3
|
||||
}
|
||||
}),
|
||||
isBaseLayer: true,
|
||||
renderers: ["Canvas"]
|
||||
});
|
||||
layer.addFeatures(features);
|
||||
|
||||
var map = new OpenLayers.Map({
|
||||
div: "map",
|
||||
layers: [layer],
|
||||
center: new OpenLayers.LonLat(0, 0),
|
||||
zoom: 0
|
||||
});
|
||||
|
||||
var select = new OpenLayers.Control.SelectFeature(layer, {hover: true});
|
||||
map.addControl(select);
|
||||
select.activate();
|
||||
52
examples/canvas-inspector.html
Normal file
52
examples/canvas-inspector.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>OpenLayers Canvas Inspector</title>
|
||||
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
|
||||
<link rel="stylesheet" href="../theme/default/google.css" type="text/css">
|
||||
<link rel="stylesheet" href="style.css" type="text/css">
|
||||
<script src="../lib/OpenLayers.js"></script>
|
||||
<script src="Jugl.js"></script>
|
||||
<style>
|
||||
#template {
|
||||
display: none;
|
||||
}
|
||||
#inspector table {
|
||||
border-right: 1px solid #666;
|
||||
border-bottom: 1px solid #666;
|
||||
}
|
||||
#inspector table td {
|
||||
font-size: 9px;
|
||||
text-align: center;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-top: 1px solid #666;
|
||||
border-left: 1px solid #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="title">Canvas Inspector</h1>
|
||||
<p id="shortdesc">
|
||||
Displays pixel values for canvas context.
|
||||
</p>
|
||||
<div id="map" class="smallmap"></div>
|
||||
<div id="docs">
|
||||
<p>
|
||||
View the <a href="canvas-inspector.js" target="_blank">canvas-inspector.js</a>
|
||||
source to see how this is done.
|
||||
</p>
|
||||
</div>
|
||||
<div id="inspector">
|
||||
</div>
|
||||
<table id="template">
|
||||
<tr jugl:repeat="row new Array(rows)">
|
||||
<td jugl:repeat="col new Array(cols)"
|
||||
jugl:attributes="id 'c' + repeat.col.index + 'r' + repeat.row.index">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<script src="canvas-inspector.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
91
examples/canvas-inspector.js
Normal file
91
examples/canvas-inspector.js
Normal file
@@ -0,0 +1,91 @@
|
||||
|
||||
var features = [
|
||||
|
||||
new OpenLayers.Feature.Vector(
|
||||
OpenLayers.Geometry.fromWKT(
|
||||
"LINESTRING(-90 90, 90 -90)"
|
||||
),
|
||||
{color: "#0f0000"}
|
||||
),
|
||||
|
||||
new OpenLayers.Feature.Vector(
|
||||
OpenLayers.Geometry.fromWKT(
|
||||
"LINESTRING(100 50, -100 -50)"
|
||||
),
|
||||
{color: "#00ff00"}
|
||||
)
|
||||
|
||||
];
|
||||
|
||||
var layer = new OpenLayers.Layer.Vector(null, {
|
||||
styleMap: new OpenLayers.StyleMap({
|
||||
strokeWidth: 3,
|
||||
strokeColor: "${color}"
|
||||
}),
|
||||
isBaseLayer: true,
|
||||
renderers: ["Canvas"],
|
||||
rendererOptions: {hitDetection: true}
|
||||
});
|
||||
layer.addFeatures(features);
|
||||
|
||||
var map = new OpenLayers.Map({
|
||||
div: "map",
|
||||
layers: [layer],
|
||||
center: new OpenLayers.LonLat(0, 0),
|
||||
zoom: 0
|
||||
});
|
||||
|
||||
var xOff = 2, yOff = 2;
|
||||
|
||||
var rows = 1 + (2 * yOff);
|
||||
var cols = 1 + (2 * xOff);
|
||||
|
||||
var template = new jugl.Template("template");
|
||||
template.process({
|
||||
clone: true,
|
||||
parent: "inspector",
|
||||
context: {
|
||||
rows: rows,
|
||||
cols: cols
|
||||
}
|
||||
});
|
||||
|
||||
function isDark(r, g, b, a) {
|
||||
a = a / 255;
|
||||
var da = 1 - a;
|
||||
// convert color values to decimal (assume white background)
|
||||
r = (a * r / 255) + da;
|
||||
g = (a * g / 255) + da;
|
||||
b = (a * b / 255) + da;
|
||||
// use w3C brightness measure
|
||||
var brightness = (r * 0.299) + (g * 0.587) + (b * 0.144);
|
||||
return brightness < 0.5;
|
||||
}
|
||||
|
||||
var context = layer.renderer.canvas; //layer.renderer.hitContext;
|
||||
var size = map.getSize();
|
||||
map.events.on({
|
||||
mousemove: function(event) {
|
||||
var x = event.xy.x - 1; // TODO: fix this elsewhere
|
||||
var y = event.xy.y;
|
||||
if ((x >= xOff) && (x < size.w - xOff) && (y >= yOff) && (y < size.h - yOff)) {
|
||||
var data = context.getImageData(x - xOff, y - yOff, rows, cols).data;
|
||||
var offset, red, green, blue, alpha, cell;
|
||||
for (var i=0; i<cols; ++i) {
|
||||
for (var j=0; j<rows; ++j) {
|
||||
offset = (i * 4) + (j * 4 * cols);
|
||||
red = data[offset];
|
||||
green = data[offset + 1];
|
||||
blue = data[offset + 2];
|
||||
alpha = data[offset + 3];
|
||||
cell = document.getElementById("c" + i + "r" + j);
|
||||
cell.innerHTML = "R: " + red + "<br>G: " + green + "<br>B: " + blue + "<br>A: " + alpha;
|
||||
cell.style.backgroundColor = "rgba(" + red + ", " + green + ", " + blue + ", " + (alpha / 255) + ")";
|
||||
cell.style.color = isDark(red, green, blue, alpha) ? "#ffffff" : "#000000";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -844,8 +844,16 @@ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
|
||||
OpenLayers.Console.error(OpenLayers.i18n("getFeatureError"));
|
||||
return null;
|
||||
}
|
||||
var feature = null;
|
||||
var featureId = this.renderer.getFeatureIdFromEvent(evt);
|
||||
return this.getFeatureById(featureId);
|
||||
if (featureId) {
|
||||
if (typeof featureId === "string") {
|
||||
feature = this.getFeatureById(featureId);
|
||||
} else {
|
||||
feature = featureId;
|
||||
}
|
||||
}
|
||||
return feature;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,6 +83,7 @@ OpenLayers.Renderer = OpenLayers.Class({
|
||||
*/
|
||||
initialize: function(containerID, options) {
|
||||
this.container = OpenLayers.Util.getElement(containerID);
|
||||
OpenLayers.Util.extend(this, options);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,22 @@
|
||||
*/
|
||||
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.
|
||||
@@ -33,13 +49,20 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
*
|
||||
* Parameters:
|
||||
* containerID - {<String>}
|
||||
* options - {Object} Optional properties to be set on the renderer.
|
||||
*/
|
||||
initialize: function(containerID) {
|
||||
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");
|
||||
this.hitGraphicCanvas = document.createElement("canvas");
|
||||
this.hitGraphicContext = this.hitGraphicCanvas.getContext("2d");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -78,11 +101,24 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
*/
|
||||
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;
|
||||
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;
|
||||
var hitGraphicCanvas = this.hitGraphicCanvas;
|
||||
hitGraphicCanvas.style.width = size.w + "px";
|
||||
hitGraphicCanvas.style.height = size.h + "px";
|
||||
hitGraphicCanvas.width = size.w;
|
||||
hitGraphicCanvas.height = size.h;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -112,29 +148,29 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
* geometry - {<OpenLayers.Geometry>}
|
||||
* style - {Object}
|
||||
*/
|
||||
drawGeometry: function(geometry, style) {
|
||||
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);
|
||||
this.drawGeometry(geometry.components[i], style, featureId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (geometry.CLASS_NAME) {
|
||||
case "OpenLayers.Geometry.Point":
|
||||
this.drawPoint(geometry, style);
|
||||
this.drawPoint(geometry, style, featureId);
|
||||
break;
|
||||
case "OpenLayers.Geometry.LineString":
|
||||
this.drawLineString(geometry, style);
|
||||
this.drawLineString(geometry, style, featureId);
|
||||
break;
|
||||
case "OpenLayers.Geometry.LinearRing":
|
||||
this.drawLinearRing(geometry, style);
|
||||
this.drawLinearRing(geometry, style, featureId);
|
||||
break;
|
||||
case "OpenLayers.Geometry.Polygon":
|
||||
this.drawPolygon(geometry, style);
|
||||
this.drawPolygon(geometry, style, featureId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -148,37 +184,70 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
* Parameters:
|
||||
* geometry - {<OpenLayers.Geometry>}
|
||||
* style - {Object}
|
||||
* featureId - {String}
|
||||
*/
|
||||
drawExternalGraphic: function(pt, style) {
|
||||
var img = new Image();
|
||||
drawExternalGraphic: function(pt, style, featureId) {
|
||||
var img = new Image();
|
||||
|
||||
if(style.graphicTitle) {
|
||||
img.title=style.graphicTitle;
|
||||
}
|
||||
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) ?
|
||||
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) ?
|
||||
var yOffset = (style.graphicYOffset != undefined) ?
|
||||
style.graphicYOffset : -(0.5 * height);
|
||||
|
||||
var context = { img: img,
|
||||
x: (pt[0]+xOffset),
|
||||
y: (pt[1]+yOffset),
|
||||
width: width,
|
||||
height: height,
|
||||
opacity: style.graphicOpacity || style.fillOpacity,
|
||||
canvas: this.canvas };
|
||||
var x = pt[0] + xOffset;
|
||||
var y = pt[1] + yOffset;
|
||||
|
||||
img.onload = OpenLayers.Function.bind( function() {
|
||||
this.canvas.globalAlpha = this.opacity;
|
||||
this.canvas.drawImage(this.img, this.x,
|
||||
this.y, this.width, this.height);
|
||||
}, context);
|
||||
img.src = style.externalGraphic;
|
||||
var numRows = this.root.width;
|
||||
var numCols = this.root.height;
|
||||
|
||||
var opacity = style.graphicOpacity || style.fillOpacity;
|
||||
|
||||
var rgb = this.featureIdToRGB(featureId);
|
||||
var red = rgb[0];
|
||||
var green = rgb[1];
|
||||
var blue = rgb[2];
|
||||
|
||||
var onLoad = function() {
|
||||
// TODO: check that we haven't moved
|
||||
var canvas = this.canvas;
|
||||
canvas.globalAlpha = opacity;
|
||||
canvas.drawImage(
|
||||
img, x, y, width, height
|
||||
);
|
||||
if (this.hitDetection) {
|
||||
var hitGraphicContext = this.hitGraphicContext;
|
||||
var hitContext = this.hitContext;
|
||||
hitGraphicContext.clearRect(0, 0, numRows, numCols);
|
||||
hitGraphicContext.drawImage(
|
||||
img, 0, 0, width, height
|
||||
);
|
||||
var imagePixels = hitGraphicContext.getImageData(0, 0, width, height).data;
|
||||
var indexData = hitContext.createImageData(width, height);
|
||||
var indexPixels = indexData.data;
|
||||
var pixelIndex;
|
||||
for (var i=0, len=imagePixels.length; i<len; i+=4) {
|
||||
// look for visible pixels
|
||||
if (imagePixels[i+3] > 0) {
|
||||
indexData[i] = red;
|
||||
indexPixels[i+1] = green;
|
||||
indexPixels[i+2] = blue;
|
||||
indexPixels[i+3] = 255;
|
||||
}
|
||||
}
|
||||
hitContext.putImageData(indexData, x, y);
|
||||
}
|
||||
};
|
||||
|
||||
img.onload = OpenLayers.Function.bind(onLoad, this);
|
||||
img.src = style.externalGraphic;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -190,10 +259,10 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
* style - {Object} Symbolizer hash
|
||||
*/
|
||||
setCanvasStyle: function(type, style) {
|
||||
if (type == "fill") {
|
||||
if (type === "fill") {
|
||||
this.canvas.globalAlpha = style['fillOpacity'];
|
||||
this.canvas.fillStyle = style['fillColor'];
|
||||
} else if (type == "stroke") {
|
||||
} else if (type === "stroke") {
|
||||
this.canvas.globalAlpha = style['strokeOpacity'];
|
||||
this.canvas.strokeStyle = style['strokeColor'];
|
||||
this.canvas.lineWidth = style['strokeWidth'];
|
||||
@@ -203,6 +272,72 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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: featureIdToRGB
|
||||
* Convert a feature ID string into an RGB array.
|
||||
*
|
||||
* Parameters:
|
||||
* featureId - {String} Feature id
|
||||
*
|
||||
* Returns:
|
||||
* {Array} RGB values.
|
||||
*/
|
||||
featureIdToRGB: function(featureId) {
|
||||
var hex = this.featureIdToHex(featureId);
|
||||
return [
|
||||
parseInt(hex.substring(1, 3), 16),
|
||||
parseInt(hex.substring(3, 5), 16),
|
||||
parseInt(hex.substring(5, 7), 16)
|
||||
];
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -210,27 +345,45 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
* Parameters:
|
||||
* geometry - {<OpenLayers.Geometry>}
|
||||
* style - {Object}
|
||||
* featureId - {String}
|
||||
*/
|
||||
drawPoint: function(geometry, style) {
|
||||
drawPoint: function(geometry, style, featureId) {
|
||||
if(style.graphic !== false) {
|
||||
var pt = this.getLocalXY(geometry);
|
||||
var p0 = pt[0];
|
||||
var p1 = pt[1];
|
||||
if (!isNaN(p0) && !isNaN(p1)) {
|
||||
if (style.externalGraphic) {
|
||||
this.drawExternalGraphic(pt, style, featureId);
|
||||
} else {
|
||||
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.externalGraphic) {
|
||||
this.drawExternalGraphic(pt, style);
|
||||
} else {
|
||||
if(style.fill !== false) {
|
||||
this.setCanvasStyle("fill", style);
|
||||
this.canvas.beginPath();
|
||||
this.canvas.arc(pt[0], pt[1], style.pointRadius, 0, Math.PI*2, true);
|
||||
this.canvas.fill();
|
||||
}
|
||||
|
||||
if(style.stroke !== false) {
|
||||
this.setCanvasStyle("stroke", style);
|
||||
this.canvas.beginPath();
|
||||
this.canvas.arc(pt[0], pt[1], style.pointRadius, 0, Math.PI*2, true);
|
||||
this.canvas.stroke();
|
||||
this.setCanvasStyle("reset");
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,20 +396,11 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
* Parameters:
|
||||
* geometry - {<OpenLayers.Geometry>}
|
||||
* style - {Object}
|
||||
* featureId - {String}
|
||||
*/
|
||||
drawLineString: function(geometry, style) {
|
||||
if(style.stroke !== false) {
|
||||
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");
|
||||
drawLineString: function(geometry, style, featureId) {
|
||||
style = OpenLayers.Util.applyDefaults({fill: false}, style);
|
||||
this.drawLinearRing(geometry, style, featureId);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -266,34 +410,53 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
* Parameters:
|
||||
* geometry - {<OpenLayers.Geometry>}
|
||||
* style - {Object}
|
||||
* featureId - {String}
|
||||
*/
|
||||
drawLinearRing: function(geometry, style) {
|
||||
if(style.fill !== false) {
|
||||
drawLinearRing: function(geometry, style, featureId) {
|
||||
if (style.fill !== false) {
|
||||
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.renderPath(this.canvas, geometry, style, featureId, "fill");
|
||||
if (this.hitDetection) {
|
||||
this.setHitContextStyle("fill", featureId, style);
|
||||
this.renderPath(this.hitContext, geometry, style, featureId, "fill");
|
||||
}
|
||||
this.canvas.fill();
|
||||
}
|
||||
|
||||
if(style.stroke !== false) {
|
||||
if (style.stroke !== false) {
|
||||
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.renderPath(this.canvas, geometry, style, featureId, "stroke");
|
||||
if (this.hitDetection) {
|
||||
this.setHitContextStyle("stroke", featureId, style);
|
||||
this.renderPath(this.hitContext, geometry, style, featureId, "stroke");
|
||||
}
|
||||
this.canvas.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.
|
||||
@@ -301,17 +464,40 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
* Parameters:
|
||||
* geometry - {<OpenLayers.Geometry>}
|
||||
* style - {Object}
|
||||
* featureId - {String}
|
||||
*/
|
||||
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'
|
||||
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
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -408,8 +594,13 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
* Clear all vectors from the renderer.
|
||||
*/
|
||||
clear: function() {
|
||||
this.canvas.clearRect(0, 0, this.root.width, this.root.height);
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -420,23 +611,28 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
* evt - {<OpenLayers.Event>}
|
||||
*
|
||||
* Returns:
|
||||
* {String} A feature id or null.
|
||||
* {<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 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;
|
||||
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 null;
|
||||
return feature;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -467,7 +663,12 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
*/
|
||||
redraw: function() {
|
||||
if (!this.locked) {
|
||||
this.canvas.clearRect(0, 0, this.root.width, this.root.height);
|
||||
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) {
|
||||
@@ -475,7 +676,7 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
|
||||
feature = this.features[id][0];
|
||||
style = this.features[id][1];
|
||||
if (!feature.geometry) { continue; }
|
||||
this.drawGeometry(feature.geometry, style);
|
||||
this.drawGeometry(feature.geometry, style, feature.id);
|
||||
if(style.label) {
|
||||
labelMap.push([feature, style]);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,75 @@
|
||||
t.eq(r.resolution, resolution, "resolution is correctly set");
|
||||
}
|
||||
|
||||
function test_featureIdToRGB(t) {
|
||||
if (!supported) {
|
||||
t.plan(0);
|
||||
return;
|
||||
}
|
||||
t.plan(2);
|
||||
var el = document.body;
|
||||
el.id = "foo";
|
||||
var renderer = new OpenLayers.Renderer.Canvas(el.id);
|
||||
|
||||
var cases = [{
|
||||
id: "foo_0", rgb: [0, 0, 1]
|
||||
}, {
|
||||
id: "foo_10", rgb: [0, 0, 11]
|
||||
}, {
|
||||
id: "foo_100", rgb: [0, 0, 101]
|
||||
}, {
|
||||
id: "foo_1000000", rgb: [15, 66, 65]
|
||||
}, {
|
||||
id: "foo_16777214", rgb: [255, 255, 255]
|
||||
}, {
|
||||
id: "foo_16777215", rgb: [0, 0, 1]
|
||||
}];
|
||||
t.plan(cases.length);
|
||||
|
||||
var c;
|
||||
for (var i=0; i<cases.length; ++i) {
|
||||
c = cases[i];
|
||||
t.eq(renderer.featureIdToRGB(c.id), c.rgb, c.id);
|
||||
}
|
||||
|
||||
renderer.destroy();
|
||||
}
|
||||
|
||||
function test_featureIdToHex(t) {
|
||||
if (!supported) {
|
||||
t.plan(0);
|
||||
return;
|
||||
}
|
||||
t.plan(2);
|
||||
var el = document.body;
|
||||
el.id = "foo";
|
||||
var renderer = new OpenLayers.Renderer.Canvas(el.id);
|
||||
|
||||
var cases = [{
|
||||
id: "foo_0", hex: "#000001"
|
||||
}, {
|
||||
id: "foo_10", hex: "#00000b"
|
||||
}, {
|
||||
id: "foo_100", hex: "#000065"
|
||||
}, {
|
||||
id: "foo_1000000", hex: "#0f4241"
|
||||
}, {
|
||||
id: "foo_16777214", hex: "#ffffff"
|
||||
}, {
|
||||
id: "foo_16777215", hex: "#000001"
|
||||
}];
|
||||
t.plan(cases.length);
|
||||
|
||||
var c;
|
||||
for (var i=0; i<cases.length; ++i) {
|
||||
c = cases[i];
|
||||
t.eq(renderer.featureIdToHex(c.id), c.hex, c.id);
|
||||
}
|
||||
|
||||
renderer.destroy();
|
||||
}
|
||||
|
||||
|
||||
function test_Renderer_Canvas_destroy(t) {
|
||||
if (!supported) { t.plan(0); return; }
|
||||
t.plan(5);
|
||||
@@ -78,6 +147,85 @@
|
||||
t.eq(r.map, null, "map nullified");
|
||||
}
|
||||
|
||||
function test_hitDetection(t) {
|
||||
if (!supported) {
|
||||
t.plan(0);
|
||||
return;
|
||||
}
|
||||
|
||||
var layer = new OpenLayers.Layer.Vector(null, {
|
||||
isBaseLayer: true,
|
||||
resolutions: [1],
|
||||
styleMap: new OpenLayers.StyleMap({
|
||||
pointRadius: 5,
|
||||
strokeWidth: 3,
|
||||
fillColor: "red",
|
||||
fillOpacity: 0.5,
|
||||
strokeColor: "blue",
|
||||
strokeOpacity: 0.75
|
||||
}),
|
||||
renderers: ["Canvas"]
|
||||
});
|
||||
|
||||
var map = new OpenLayers.Map({
|
||||
div: "map",
|
||||
controls: [],
|
||||
layers: [layer],
|
||||
center: new OpenLayers.LonLat(0, 0),
|
||||
zoom: 0
|
||||
});
|
||||
|
||||
layer.addFeatures([
|
||||
new OpenLayers.Feature.Vector(
|
||||
new OpenLayers.Geometry.Point(-100, 0)
|
||||
),
|
||||
new OpenLayers.Feature.Vector(
|
||||
OpenLayers.Geometry.fromWKT("LINESTRING(-50 0, 50 0)")
|
||||
),
|
||||
new OpenLayers.Feature.Vector(
|
||||
OpenLayers.Geometry.fromWKT("POLYGON((100 -25, 150 -25, 150 25, 100 25, 100 -25), (120 -5, 130 -5, 130 5, 120 5, 120 -5))")
|
||||
)
|
||||
]);
|
||||
|
||||
var cases = [{
|
||||
msg: "center of point", x: -100, y: 0, id: layer.features[0].id
|
||||
}, {
|
||||
msg: "edge of point", x: -103, y: 3, id: layer.features[0].id
|
||||
}, {
|
||||
msg: "outside point", x: -110, y: 3, id: null
|
||||
}, {
|
||||
msg: "center of line", x: 0, y: 0, id: layer.features[1].id
|
||||
}, {
|
||||
msg: "edge of line", x: 0, y: 1, id: layer.features[1].id
|
||||
}, {
|
||||
msg: "outside line", x: 0, y: 5, id: null
|
||||
}, {
|
||||
msg: "inside polygon", x: 110, y: 0, id: layer.features[2].id
|
||||
}, {
|
||||
msg: "edge of polygon", x: 99, y: 0, id: layer.features[2].id
|
||||
}, {
|
||||
msg: "inside polygon hole", x: 125, y: 0, id: null
|
||||
}, {
|
||||
msg: "outside polygon", x: 155, y: 0, id: null
|
||||
}];
|
||||
|
||||
function px(x, y) {
|
||||
return map.getPixelFromLonLat(
|
||||
new OpenLayers.LonLat(x, y)
|
||||
);
|
||||
}
|
||||
|
||||
var num = cases.length;
|
||||
t.plan(num);
|
||||
var c, feature;
|
||||
for (var i=0; i<num; ++i) {
|
||||
c = cases[i];
|
||||
feature = layer.renderer.getFeatureIdFromEvent({xy: px(c.x, c.y)});
|
||||
t.eq(feature && feature.id, c.id, c.msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user