Adding layer for generating dynamic point grids. r=bartvde (closes #3344)

git-svn-id: http://svn.openlayers.org/trunk/openlayers@12099 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
Tim Schaub
2011-06-17 18:18:40 +00:00
parent 6e3879f5b6
commit abdb336354
8 changed files with 799 additions and 0 deletions

75
examples/point-grid.html Normal file
View File

@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>OpenLayers Point Grid Example</title>
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
<link rel="stylesheet" href="style.css" type="text/css">
<style type="text/css">
.olControlAttribution {
left: 5px;
bottom: 5px;
}
</style>
</head>
<body>
<h1 id="title">Point Grid Example</h1>
<div id="tags">
point grid
</div>
<div id="shortdesc">Use a PointGrid layer to display a grid of regularly spaced points</div>
<div id="map" class="smallmap"></div>
Grid rotation:
<select name="rotation" id="rotation">
<option value="-45">-45</option>
<option value="-30">-30</option>
<option value="-15">-15</option>
<option value="0">0</option>
<option value="15">15</option>
<option value="30">30</option>
<option value="45">45</option>
</select>
&nbsp;
Grid spacing:
<select name="dx" id="dx">
<option value="10">10</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="25">25</option>
<option value="30">30</option>
</select> x
<select name="dy" id="dy">
<option value="10">10</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="25">25</option>
<option value="30">30</option>
</select>
&nbsp;
Max points:
<select name="max" id="max">
<option value="150">150</option>
<option value="250">250</option>
<option value="350">350</option>
</select>
<div class="docs">
<p>
This example demonstrates a <code>OpenLayers.Layer.PointGrid</code>
layer to render a regularly spaced grid of point features.
</p><p>
See the <a href="point-grid.js" target="_blank">
point-grid.js source</a> to see how this is done.
</p>
</div>
<script src="../lib/OpenLayers.js"></script>
<script src="point-grid.js"></script>
</body>
</html>

33
examples/point-grid.js Normal file
View File

@@ -0,0 +1,33 @@
var points = new OpenLayers.Layer.PointGrid({
isBaseLayer: true, dx: 15, dy: 15
});
var map = new OpenLayers.Map({
div: "map",
layers: [points],
center: new OpenLayers.LonLat(0, 0),
zoom: 2
});
var rotation = document.getElementById("rotation");
rotation.value = String(points.rotation);
rotation.onchange = function() {
points.setRotation(Number(rotation.value));
}
var dx = document.getElementById("dx");
var dy = document.getElementById("dy");
dx.value = String(points.dx);
dy.value = String(points.dy);
dx.onchange = function() {
points.setSpacing(Number(dx.value), Number(dy.value));
}
dy.onchange = function() {
points.setSpacing(Number(dx.value), Number(dy.value));
}
var max = document.getElementById("max");
max.value = String(points.maxFeatures);
max.onchange = function() {
points.setMaxFeatures(Number(max.value));
}

78
examples/snap-grid.html Normal file
View File

@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>OpenLayers Snap Grid Example</title>
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
<link rel="stylesheet" href="style.css" type="text/css">
<style type="text/css">
.olControlAttribution {
left: 5px;
bottom: 5px;
}
.olControlEditingToolbar .olControlModifyFeatureItemInactive {
background-position: -1px -1px;
}
.olControlEditingToolbar .olControlModifyFeatureItemActive {
background-position: -1px -24px;
}
</style>
</head>
<body>
<h1 id="title">Snap Grid Example</h1>
<div id="tags">
snap grid
</div>
<div id="shortdesc">Use a PointGrid layer and a Snapping control to snap to a grid of regularly spaced points</div>
<div id="map" class="smallmap"></div>
Grid rotation:
<select name="rotation" id="rotation">
<option value="-45">-45</option>
<option value="-30">-30</option>
<option value="-15">-15</option>
<option value="0">0</option>
<option value="15">15</option>
<option value="30">30</option>
<option value="45">45</option>
</select>
&nbsp;
Grid spacing:
<select name="spacing" id="spacing">
<option value="150">150</option>
<option value="300">300</option>
<option value="600">600</option>
<option value="1200">1200</option>
<option value="2400">2400</option>
</select>
&nbsp;
Max points:
<select name="max" id="max">
<option value="150">150</option>
<option value="250">250</option>
<option value="350">350</option>
</select>
<div class="docs">
<p>
This example demonstrates feature editing with snapping to a regular
grid. The map is configured with a <code>OpenLayers.Layer.PointGrid</code>
layer and a <code>OpenLayers.Control.Snapping</code> agent. For the
best performance, the point grid layer should not made visible.
Snapping still works with layers that are not visible.
</p><p>
See the <a href="snap-grid.js" target="_blank">
snap-grid.js source</a> to see how this is done.
</p>
</div>
<script src="../lib/OpenLayers.js"></script>
<script src="snap-grid.js"></script>
</body>
</html>

81
examples/snap-grid.js Normal file
View File

@@ -0,0 +1,81 @@
var points = new OpenLayers.Layer.PointGrid({
name: "Snap Grid",
dx: 600, dy: 600,
styleMap: new OpenLayers.StyleMap({
pointRadius: 1,
strokeColor: "#3333ff",
strokeWidth: 1,
fillOpacity: 1,
fillColor: "#ffffff",
graphicName: "square"
})
});
var lines = new OpenLayers.Layer.Vector("Lines", {
styleMap: new OpenLayers.StyleMap({
pointRadius: 3,
strokeColor: "#ff3300",
strokeWidth: 3,
fillOpacity: 0
})
});
var map = new OpenLayers.Map({
div: "map",
layers: [new OpenLayers.Layer.OSM(), points, lines],
controls: [
new OpenLayers.Control.Navigation(),
new OpenLayers.Control.LayerSwitcher(),
new OpenLayers.Control.Attribution()
],
restrictedExtent: new OpenLayers.Bounds(
1035374, 7448940, 1074510, 7468508
),
center: new OpenLayers.LonLat(1054942, 7458724),
zoom: 13
});
// configure the snapping agent
var snap = new OpenLayers.Control.Snapping({
layer: lines,
targets: [{
layer: points,
tolerance: 15
}]
});
snap.activate();
// add some editing tools to a panel
var panel = new OpenLayers.Control.Panel({
displayClass: "olControlEditingToolbar"
});
var draw = new OpenLayers.Control.DrawFeature(
lines, OpenLayers.Handler.Path,
{displayClass: "olControlDrawFeaturePath", title: "Draw Features"}
);
modify = new OpenLayers.Control.ModifyFeature(
lines, {displayClass: "olControlModifyFeature", title: "Modify Features"}
);
panel.addControls([
new OpenLayers.Control.Navigation({title: "Navigate"}),
modify, draw
]);
map.addControl(panel);
var rotation = document.getElementById("rotation");
rotation.value = String(points.rotation);
rotation.onchange = function() {
points.setRotation(Number(rotation.value));
}
var spacing = document.getElementById("spacing");
spacing.value = String(points.dx);
spacing.onchange = function() {
points.setSpacing(Number(spacing.value));
}
var max = document.getElementById("max");
max.value = String(points.maxFeatures);
max.onchange = function() {
points.setMaxFeatures(Number(max.value));
}

View File

@@ -232,6 +232,7 @@
"OpenLayers/Renderer/Canvas.js",
"OpenLayers/Renderer/VML.js",
"OpenLayers/Layer/Vector.js",
"OpenLayers/Layer/PointGrid.js",
"OpenLayers/Layer/Vector/RootContainer.js",
"OpenLayers/Strategy.js",
"OpenLayers/Strategy/Filter.js",

View File

@@ -0,0 +1,299 @@
/* 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/Layer/Vector.js
* @requires OpenLayers/Geometry/Polygon.js
*/
/**
* Class: OpenLayers.Layer.PointGrid
* A point grid layer dynamically generates a regularly spaced grid of point
* features. This is a specialty layer for cases where an application needs
* a regular grid of points. It can be used, for example, in an editing
* environment to snap to a grid.
*
* Create a new vector layer with the <OpenLayers.Layer.PointGrid> constructor.
* (code)
* // create a grid with points spaced at 10 map units
* var points = new OpenLayers.Layer.PointGrid({dx: 10, dy: 10});
*
* // create a grid with different x/y spacing rotated 15 degrees clockwise.
* var points = new OpenLayers.Layer.PointGrid({dx: 5, dy: 10, rotation: 15});
* (end)
*
* Inherits from:
* - <OpenLayers.Layer.Vector>
*/
OpenLayers.Layer.PointGrid = OpenLayers.Class(OpenLayers.Layer.Vector, {
/**
* APIProperty: dx
* {Number} Point grid spacing in the x-axis direction (map units).
* Read-only. Use the <setSpacing> method to modify this value.
*/
dx: null,
/**
* APIProperty: dy
* {Number} Point grid spacing in the y-axis direction (map units).
* Read-only. Use the <setSpacing> method to modify this value.
*/
dy: null,
/**
* APIProperty: ratio
* {Number} Ratio of the desired grid size to the map viewport size.
* Default is 1.5. Larger ratios mean the grid is recalculated less often
* while panning. The <maxFeatures> setting has precedence when determining
* grid size. Read-only. Use the <setRatio> method to modify this value.
*/
ratio: 1.5,
/**
* APIProperty: maxFeatures
* {Number} The maximum number of points to generate in the grid. Default
* is 250. Read-only. Use the <setMaxFeatures> method to modify this value.
*/
maxFeatures: 250,
/**
* APIProperty: rotation
* {Number} Grid rotation (in degrees clockwise from the positive x-axis).
* Default is 0. Read-only. Use the <setRotation> method to modify this
* value.
*/
rotation: 0,
/**
* APIProperty: origin
* {OpenLayers.LonLat} Grid origin. The grid lattice will be aligned with
* the origin. If not set at construction, the center of the map's maximum
* extent is used. Read-only. Use the <setOrigin> method to modify this
* value.
*/
origin: null,
/**
* Property: gridBounds
* {<OpenLayers.Bounds>} Internally cached grid bounds (with optional
* rotation applied).
*/
gridBounds: null,
/**
* Constructor: OpenLayers.Layer.PointGrid
* Creates a new point grid layer.
*
* Parameters:
* config - {Object} An object containing all configuration properties for
* the layer. The <dx> and <dy> properties are required to be set at
* construction. Any other layer properties may be set in this object.
*/
initialize: function(config) {
config = config || {};
OpenLayers.Layer.Vector.prototype.initialize.apply(this, [config.name, config]);
},
/**
* Method: setMap
* The layer has been added to the map.
*
* Parameters:
* map - {<OpenLayers.Map>}
*/
setMap: function(map) {
OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
map.events.register("moveend", this, this.onMoveEnd);
},
/**
* Method: removeMap
* The layer has been removed from the map.
*
* Parameters:
* map - {<OpenLayers.Map>}
*/
removeMap: function(map) {
map.events.unregister("moveend", this, this.onMoveEnd);
OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments);
},
/**
* APIMethod: setRatio
* Set the grid <ratio> property and update the grid. Can only be called
* after the layer has been added to a map with a center/extent.
*
* Parameters:
* ratio - {Number}
*/
setRatio: function(ratio) {
this.ratio = ratio;
this.updateGrid(true);
},
/**
* APIMethod: setMaxFeatures
* Set the grid <maxFeatures> property and update the grid. Can only be
* called after the layer has been added to a map with a center/extent.
*
* Parameters:
* maxFeatures - {Number}
*/
setMaxFeatures: function(maxFeatures) {
this.maxFeatures = maxFeatures;
this.updateGrid(true);
},
/**
* APIMethod: setSpacing
* Set the grid <dx> and <dy> properties and update the grid. If only one
* argument is provided, it will be set as <dx> and <dy>. Can only be
* called after the layer has been added to a map with a center/extent.
*
* Parameters:
* dx - {Number}
* dy - {Number}
*/
setSpacing: function(dx, dy) {
this.dx = dx;
this.dy = dy || dx;
this.updateGrid(true);
},
/**
* APIMethod: setOrigin
* Set the grid <origin> property and update the grid. Can only be called
* after the layer has been added to a map with a center/extent.
*
* Parameters:
* origin - {<OpenLayers.LonLat>}
*/
setOrigin: function(origin) {
this.origin = origin;
this.updateGrid(true);
},
/**
* APIMethod: getOrigin
* Get the grid <origin> property.
*
* Returns:
* {<OpenLayers.LonLat>} The grid origin.
*/
getOrigin: function() {
if (!this.origin) {
this.origin = this.map.getExtent().getCenterLonLat();
}
return this.origin;
},
/**
* APIMethod: setRotation
* Set the grid <rotation> property and update the grid. Rotation values
* are in degrees clockwise from the positive x-axis (negative values
* for counter-clockwise rotation). Can only be called after the layer
* has been added to a map with a center/extent.
*
* Parameters:
* rotation - {Number} Degrees clockwise from the positive x-axis.
*/
setRotation: function(rotation) {
this.rotation = rotation;
this.updateGrid(true);
},
/**
* Method: onMoveEnd
* Listener for map "moveend" events.
*/
onMoveEnd: function() {
this.updateGrid();
},
/**
* Method: getViewBounds
* Gets the (potentially rotated) view bounds for grid calculations.
*
* Returns:
* {<OpenLayers.Bounds>}
*/
getViewBounds: function() {
var bounds = this.map.getExtent();
if (this.rotation) {
var origin = this.getOrigin();
var rotationOrigin = new OpenLayers.Geometry.Point(origin.lon, origin.lat);
var rect = bounds.toGeometry();
rect.rotate(-this.rotation, rotationOrigin);
bounds = rect.getBounds();
}
return bounds;
},
/**
* Method: updateGrid
* Update the grid.
*
* Parameters:
* force - {Boolean} Update the grid even if the previous bounds are still
* valid.
*/
updateGrid: function(force) {
if (force || this.invalidBounds()) {
var viewBounds = this.getViewBounds();
var origin = this.getOrigin();
var rotationOrigin = new OpenLayers.Geometry.Point(origin.lon, origin.lat);
var viewBoundsWidth = viewBounds.getWidth();
var viewBoundsHeight = viewBounds.getHeight();
var aspectRatio = viewBoundsWidth / viewBoundsHeight;
var maxHeight = Math.sqrt(this.dx * this.dy * this.maxFeatures / aspectRatio);
var maxWidth = maxHeight * aspectRatio;
var gridWidth = Math.min(viewBoundsWidth * this.ratio, maxWidth);
var gridHeight = Math.min(viewBoundsHeight * this.ratio, maxHeight);
var center = viewBounds.getCenterLonLat();
this.gridBounds = new OpenLayers.Bounds(
center.lon - (gridWidth / 2),
center.lat - (gridHeight / 2),
center.lon + (gridWidth / 2),
center.lat + (gridHeight / 2)
);
var rows = Math.floor(gridHeight / this.dy);
var cols = Math.floor(gridWidth / this.dx);
var gridLeft = origin.lon + (this.dx * Math.ceil((this.gridBounds.left - origin.lon) / this.dx));
var gridBottom = origin.lat + (this.dy * Math.ceil((this.gridBounds.bottom - origin.lat) / this.dy));
var features = new Array(rows * cols);
var x, y, point;
for (var i=0; i<cols; ++i) {
x = gridLeft + (i * this.dx);
for (var j=0; j<rows; ++j) {
y = gridBottom + (j * this.dy);
point = new OpenLayers.Geometry.Point(x, y);
if (this.rotation) {
point.rotate(this.rotation, rotationOrigin);
}
features[(i*rows)+j] = new OpenLayers.Feature.Vector(point);
}
}
this.destroyFeatures(this.features, {silent: true});
this.addFeatures(features, {silent: true});
}
},
/**
* Method: invalidBounds
* Determine whether the previously generated point grid is invalid.
* This occurs when the map bounds extends beyond the previously
* generated grid bounds.
*
* Returns:
* {Boolean}
*/
invalidBounds: function() {
return !this.gridBounds || !this.gridBounds.containsBounds(this.getViewBounds());
},
CLASS_NAME: "OpenLayers.Layer.PointGrid"
});

231
tests/Layer/PointGrid.html Normal file
View File

@@ -0,0 +1,231 @@
<!DOCTYPE html>
<html>
<head>
<script src="../OLLoader.js"></script>
<script type="text/javascript">
function test_initialize(t) {
t.plan(1);
var layer = new OpenLayers.Layer.PointGrid();
t.ok(layer instanceof OpenLayers.Layer.PointGrid, "instance created");
layer.destroy();
}
function test_name(t) {
t.plan(1);
var layer = new OpenLayers.Layer.PointGrid({name: "foo"});
t.eq(layer.name, "foo", "name set like every other property");
layer.destroy();
}
function test_spacing(t) {
t.plan(7);
var layer = new OpenLayers.Layer.PointGrid({
isBaseLayer: true,
resolutions: [1],
maxExtent: new OpenLayers.Bounds(-100, -50, 100, 50),
dx: 10,
dy: 10,
ratio: 1
});
var map = new OpenLayers.Map({
div: "map",
layers: [layer],
center: new OpenLayers.LonLat(0, 0),
zoom: 0
});
t.eq(layer.features.length, 200, "200 features");
// set dx/dy together
layer.setSpacing(20);
t.eq(layer.dx, 20, "dx 20");
t.eq(layer.dy, 20, "dy 20");
t.eq(layer.features.length, 50, "50 features");
// set dx/dy independently
layer.setSpacing(50, 25);
t.eq(layer.dx, 50, "dx 50");
t.eq(layer.dy, 25, "dy 25");
t.eq(layer.features.length, 16, "16 features");
map.destroy();
}
function test_ratio(t) {
t.plan(3);
var layer = new OpenLayers.Layer.PointGrid({
isBaseLayer: true,
resolutions: [1],
maxExtent: new OpenLayers.Bounds(-100, -50, 100, 50),
dx: 25,
dy: 25,
ratio: 1
});
var map = new OpenLayers.Map({
div: "map",
layers: [layer],
center: new OpenLayers.LonLat(0, 0),
zoom: 0
});
t.eq(layer.features.length, 32, "32 features");
// increase ratio (1.5 -> 300 x 150)
layer.setRatio(1.5);
t.eq(layer.ratio, 1.5, "ratio 1.5");
t.eq(layer.features.length, 72, "72 features");
map.destroy();
}
function test_maxFeatures(t) {
t.plan(3);
var layer = new OpenLayers.Layer.PointGrid({
isBaseLayer: true,
resolutions: [1],
maxExtent: new OpenLayers.Bounds(-100, -50, 100, 50),
dx: 10,
dy: 10,
ratio: 1
});
var map = new OpenLayers.Map({
div: "map",
layers: [layer],
center: new OpenLayers.LonLat(0, 0),
zoom: 0
});
t.eq(layer.features.length, 200, "200 features");
// limit maxFeatures
layer.setMaxFeatures(150);
t.eq(layer.maxFeatures, 150, "maxFeatures 150");
t.ok(layer.features.length <= 150, "<= 150 features");
map.destroy();
}
function test_rotation(t) {
t.plan(6);
var layer = new OpenLayers.Layer.PointGrid({
isBaseLayer: true,
resolutions: [1],
maxExtent: new OpenLayers.Bounds(-100, -50, 100, 50),
dx: 10,
dy: 10,
ratio: 1
});
var map = new OpenLayers.Map({
div: "map",
layers: [layer],
center: new OpenLayers.LonLat(0, 0),
zoom: 0
});
function getRotation(layer) {
// grid starts at bottom left and goes up
var g0 = layer.features[0].geometry;
var g1 = layer.features[1].geometry;
// subtract 90 to get rotation of grid
return Math.atan2(g1.y - g0.y, g1.x - g0.x) * (180 / Math.PI) - 90;
}
t.eq(layer.rotation, 0, "0 rotation");
t.eq(getRotation(layer).toFixed(3), (0).toFixed(3), "0 grid")
// rotate grid 25 degrees counter-clockwise
layer.setRotation(25);
t.eq(layer.rotation, 25, "25 rotation");
t.eq(getRotation(layer).toFixed(3), (25).toFixed(3), "25 grid");
// rotate grid 45 degrees clockwise
layer.setRotation(-45);
t.eq(layer.rotation, -45, "-45 rotation");
t.eq(getRotation(layer).toFixed(3), (-45).toFixed(3), "-45 grid");
map.destroy();
}
function test_origin(t) {
t.plan(7);
var layer = new OpenLayers.Layer.PointGrid({
isBaseLayer: true,
resolutions: [1],
maxExtent: new OpenLayers.Bounds(-100, -50, 100, 50),
dx: 10,
dy: 10,
ratio: 1
});
var map = new OpenLayers.Map({
div: "map",
layers: [layer],
center: new OpenLayers.LonLat(0, 0),
zoom: 0
});
var origin = layer.getOrigin();
t.ok(map.getExtent().getCenterLonLat().equals(origin), "default is center of map extent");
var g0 = layer.features[0].geometry;
t.eq((g0.x - origin.lon) % layer.dx, 0, "a) lattice aligned with origin x");
t.eq((g0.y - origin.lat) % layer.dy, 0, "a) lattice aligned with origin y");
// set origin
layer.setOrigin(new OpenLayers.LonLat(-5, 12));
origin = layer.getOrigin();
t.eq(origin.lon, -5, "-5 origin x");
t.eq(origin.lat, 12, "12 origin y");
g0 = layer.features[0].geometry;
t.eq((g0.x - origin.lon) % layer.dx, 0, "b) lattice aligned with origin x");
t.eq((g0.y - origin.lat) % layer.dy, 0, "b) lattice aligned with origin y");
map.destroy();
}
function test_zoom(t) {
t.plan(2);
var layer = new OpenLayers.Layer.PointGrid({
isBaseLayer: true,
resolutions: [2, 1],
maxExtent: new OpenLayers.Bounds(-200, -100, 200, 100),
dx: 20,
dy: 20,
ratio: 1
});
var map = new OpenLayers.Map({
div: "map",
layers: [layer],
center: new OpenLayers.LonLat(0, 0),
zoom: 1
});
t.eq(layer.features.length, 50, "50 features at zoom 1");
map.zoomTo(0);
t.eq(layer.features.length, 200, "200 features at zoom 0")
map.destroy();
}
</script>
</head>
<body>
<div id="map" style="width:200px;height:100px"></div>
</body>
</html>

View File

@@ -156,6 +156,7 @@
<li>Layer/MapServer.html</li>
<li>Layer/Markers.html</li>
<li>Layer/MultiMap.html</li>
<li>Layer/PointGrid.html</li>
<li>Layer/PointTrack.html</li>
<li>Layer/SphericalMercator.html</li>
<li>Layer/Text.html</li>