New vector rendering for better performance and less renderer specific limitations. r=elemoine (closes #1675, closes #1656, closes #1631, closes #1431, closes #1709)

git-svn-id: http://svn.openlayers.org/trunk/openlayers@7930 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
ahocevar
2008-09-02 17:17:52 +00:00
parent ede7bef13c
commit c12cb25aee
12 changed files with 676 additions and 155 deletions

View File

@@ -31,14 +31,14 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
* {Integer} Firefox has a limitation where values larger or smaller than
* about 15000 in an SVG document lock the browser up. This
* works around it.
*/
*/
MAX_PIXEL: 15000,
/**
* Property: localResolution
* {Float}
/**
* Property: translationParameters
* {Object} Hash with "x" and "y" properties
*/
localResolution: null,
translationParameters: null,
/**
* Property: symbolSize
@@ -58,6 +58,7 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
}
OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
arguments);
this.translationParameters = {x: 0, y: 0};
},
/**
@@ -86,16 +87,20 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
* See #669 for more information
*
* Parameters:
* x - {Integer}
* y - {Integer}
* x - {Integer}
* y - {Integer}
* xyOnly - {Boolean} whether or not to just check for x and y, which means
* to not take the current translation parameters into account if true.
*
* Returns:
* {Boolean} Whether or not the 'x' and 'y' coordinates are in the
* valid range.
*/
inValidRange: function(x, y) {
return (x >= -this.MAX_PIXEL && x <= this.MAX_PIXEL &&
y >= -this.MAX_PIXEL && y <= this.MAX_PIXEL);
inValidRange: function(x, y, xyOnly) {
var left = x + (xyOnly ? 0 : this.translationParameters.x);
var top = y + (xyOnly ? 0 : this.translationParameters.y);
return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
},
/**
@@ -103,39 +108,66 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
*
* Parameters:
* extent - {<OpenLayers.Bounds>}
* resolutionChanged - {Boolean}
*
* Returns:
* {Boolean} true to notify the layer that the new extent does not exceed
* the coordinate range, and the features will not need to be redrawn.
* False otherwise.
*/
setExtent: function(extent) {
setExtent: function(extent, resolutionChanged) {
OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,
arguments);
var resolution = this.getResolution();
var left = -extent.left / resolution;
var top = extent.top / resolution;
// If the resolution has changed, start over changing the corner, because
// the features will redraw.
if (!this.localResolution || resolution != this.localResolution) {
this.left = -extent.left / resolution;
this.top = extent.top / resolution;
}
var left = 0;
var top = 0;
if (resolutionChanged) {
this.left = left;
this.top = top;
// Set the viewbox
var extentString = "0 0 " + this.size.w + " " + this.size.h;
// If the resolution has not changed, we already have features, and we need
// to adjust the viewbox to fit them.
if (this.localResolution && resolution == this.localResolution) {
left = (this.left) - (-extent.left / resolution);
top = (this.top) - (extent.top / resolution);
}
// Store resolution for use later.
this.localResolution = resolution;
// Set the viewbox -- the left/top will be pixels-dragged-since-res change,
// the width/height will be pixels.
var extentString = left + " " + top + " " +
this.size.w + " " + this.size.h;
//var extentString = extent.left / resolution + " " + -extent.top / resolution + " " +
this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
this.translate(0, 0);
return true;
} else {
var inRange = this.translate(left - this.left, top - this.top);
if (!inRange) {
// recenter the coordinate system
this.setExtent(extent, true);
}
return inRange;
}
},
/**
* Method: translate
* Transforms the SVG coordinate system
*
* Parameters:
* x - {Float}
* y - {Float}
*
* Returns:
* {Boolean} true if the translation parameters ar in the valid coordinates
* range, false otherwise.
*/
translate: function(x, y) {
if (!this.inValidRange(x, y, true)) {
return false;
} else {
var transformString = "";
if (x || y) {
transformString = "translate(" + x + "," + y + ")";
}
this.root.setAttributeNS(null, "transform", transformString);
this.translationParameters = {x: x, y: y};
return true;
}
},
/**
@@ -413,9 +445,12 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or false if the renderer could not draw the point
*/
drawPoint: function(node, geometry) {
this.drawCircle(node, geometry, 1);
return this.drawCircle(node, geometry, 1);
},
/**
@@ -426,6 +461,9 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
* radius - {Float}
*
* Returns:
* {DOMElement} or false if the renderer could not draw the circle
*/
drawCircle: function(node, geometry, radius) {
var resolution = this.getResolution();
@@ -436,10 +474,9 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
node.setAttributeNS(null, "cx", x);
node.setAttributeNS(null, "cy", y);
node.setAttributeNS(null, "r", radius);
return node;
} else {
node.setAttributeNS(null, "cx", "");
node.setAttributeNS(null, "cy", "");
node.setAttributeNS(null, "r", 0);
return false;
}
},
@@ -451,23 +488,41 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or null if the renderer could not draw all components of
* the linestring, or false if nothing could be drawn
*/
drawLineString: function(node, geometry) {
node.setAttributeNS(null, "points",
this.getComponentsString(geometry.components));
var componentsResult = this.getComponentsString(geometry.components);
if (componentsResult.path) {
node.setAttributeNS(null, "points", componentsResult.path);
return (componentsResult.complete ? node : null);
} else {
return false;
}
},
/**v
/**
* Method: drawLinearRing
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or null if the renderer could not draw all components
* of the linear ring, or false if nothing could be drawn
*/
drawLinearRing: function(node, geometry) {
node.setAttributeNS(null, "points",
this.getComponentsString(geometry.components));
var componentsResult = this.getComponentsString(geometry.components);
if (componentsResult.path) {
node.setAttributeNS(null, "points", componentsResult.path);
return (componentsResult.complete ? node : null);
} else {
return false;
}
},
/**
@@ -477,28 +532,35 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or null if the renderer could not draw all components
* of the polygon, or false if nothing could be drawn
*/
drawPolygon: function(node, geometry) {
var d = "";
var draw = true;
var complete = true;
var linearRingResult, path;
for (var j=0, len=geometry.components.length; j<len; j++) {
var linearRing = geometry.components[j];
d += " M";
for (var i=0, ilen=linearRing.components.length; i<ilen; i++) {
var component = this.getShortString(linearRing.components[i]);
if (component) {
d += " " + component;
} else {
draw = false;
}
linearRingResult = this.getComponentsString(
geometry.components[j].components, " ");
path = linearRingResult.path;
if (path) {
d += " " + path;
complete = linearRingResult.complete && complete;
} else {
draw = false;
}
}
d += " z";
if (draw) {
node.setAttributeNS(null, "d", d);
node.setAttributeNS(null, "fill-rule", "evenodd");
return complete ? node : null;
} else {
node.setAttributeNS(null, "d", "");
return false;
}
},
@@ -509,6 +571,9 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or false if the renderer could not draw the rectangle
*/
drawRectangle: function(node, geometry) {
var resolution = this.getResolution();
@@ -520,11 +585,9 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
node.setAttributeNS(null, "y", y);
node.setAttributeNS(null, "width", geometry.width / resolution);
node.setAttributeNS(null, "height", geometry.height / resolution);
return node;
} else {
node.setAttributeNS(null, "x", "");
node.setAttributeNS(null, "y", "");
node.setAttributeNS(null, "width", 0);
node.setAttributeNS(null, "height", 0);
return false;
}
},
@@ -535,6 +598,9 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or false if the renderer could not draw the surface
*/
drawSurface: function(node, geometry) {
@@ -559,8 +625,9 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
d += " Z";
if (draw) {
node.setAttributeNS(null, "d", d);
return node;
} else {
node.setAttributeNS(null, "d", "");
return false;
}
},
@@ -569,19 +636,93 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
*
* Parameters:
* components - {Array(<OpenLayers.Geometry.Point>)} Array of points
* separator - {String} character between coordinate pairs. Defaults to ","
*
* Returns:
* {Object} hash with properties "path" (the string created from the
* components and "complete" (false if the renderer was unable to
* draw all components)
*/
getComponentsString: function(components) {
getComponentsString: function(components, separator) {
var renderCmp = [];
var complete = true;
var len = components.length;
var strings = [];
for(var i=0, len=components.length; i<len; i++) {
var component = this.getShortString(components[i]);
if (component) {
strings.push(component);
var str, component, j;
for(var i=0; i<len; i++) {
component = components[i];
renderCmp.push(component);
str = this.getShortString(component);
if (str) {
strings.push(str);
} else {
// The current component is outside the valid range. Let's
// see if the previous or next component is inside the range.
// If so, add the coordinate of the intersection with the
// valid range bounds.
if (i > 0) {
if (this.getShortString(components[i + 1])) {
strings.push(this.clipLine(components[i],
components[i-1]));
}
}
if (i < len - 1) {
if (this.getShortString(components[i + 1])) {
strings.push(this.clipLine(components[i],
components[i+1]));
}
}
complete = false;
}
}
return strings.join(",");
return {
path: strings.join(separator || ","),
complete: complete
};
},
/**
* Method: clipLine
* Given two points (one inside the valid range, and one outside),
* clips the line betweeen the two points so that the new points are both
* inside the valid range.
*
* Parameters:
* badComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
* invalid point
* goodComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
* valid point
* Returns
* {String} the SVG coordinate pair of the clipped point (like
* getShortString), or an empty string if both passed componets are at
* the same point.
*/
clipLine: function(badComponent, goodComponent) {
if (goodComponent.equals(badComponent)) {
return "";
}
var resolution = this.getResolution();
var maxX = this.MAX_PIXEL - this.translationParameters.x;
var maxY = this.MAX_PIXEL - this.translationParameters.y;
var x1 = goodComponent.x / resolution + this.left;
var y1 = this.top - goodComponent.y / resolution;
var x2 = badComponent.x / resolution + this.left;
var y2 = this.top - badComponent.y / resolution;
var k;
if (x2 < -maxX || x2 > maxX) {
k = (y2 - y1) / (x2 - x1);
x2 = x2 < 0 ? -maxX : maxX;
y2 = y1 + (x2 - x1) * k;
}
if (y2 < -maxY || y2 > maxY) {
k = (x2 - x1) / (y2 - y1);
y2 = y2 < 0 ? -maxY : maxY;
x2 = x1 + (y2 - y1) * k;
}
return x2 + "," + y2;
},
/**
* Method: getShortString
*
@@ -589,7 +730,7 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
* point - {<OpenLayers.Geometry.Point>}
*
* Returns:
* {String}
* {String} or false if point is outside the valid range
*/
getShortString: function(point) {
var resolution = this.getResolution();