+ This example demonstrates feature editing with snapping to a regular
+ grid. The map is configured with a OpenLayers.Layer.PointGrid
+ layer and a OpenLayers.Control.Snapping agent. For the
+ best performance, the point grid layer should not made visible.
+ Snapping still works with layers that are not visible.
+
+
+
+
+
+
diff --git a/examples/snap-grid.js b/examples/snap-grid.js
new file mode 100644
index 0000000000..81a72dab11
--- /dev/null
+++ b/examples/snap-grid.js
@@ -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));
+}
diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js
index fe395f7ddf..2351f0288c 100644
--- a/lib/OpenLayers.js
+++ b/lib/OpenLayers.js
@@ -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",
diff --git a/lib/OpenLayers/Layer/PointGrid.js b/lib/OpenLayers/Layer/PointGrid.js
new file mode 100644
index 0000000000..7a82835d52
--- /dev/null
+++ b/lib/OpenLayers/Layer/PointGrid.js
@@ -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 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.PointGrid = OpenLayers.Class(OpenLayers.Layer.Vector, {
+
+ /**
+ * APIProperty: dx
+ * {Number} Point grid spacing in the x-axis direction (map units).
+ * Read-only. Use the method to modify this value.
+ */
+ dx: null,
+
+ /**
+ * APIProperty: dy
+ * {Number} Point grid spacing in the y-axis direction (map units).
+ * Read-only. Use the 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 setting has precedence when determining
+ * grid size. Read-only. Use the 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 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 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 method to modify this
+ * value.
+ */
+ origin: null,
+
+ /**
+ * Property: gridBounds
+ * {} 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 and 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 - {}
+ */
+ 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 - {}
+ */
+ removeMap: function(map) {
+ map.events.unregister("moveend", this, this.onMoveEnd);
+ OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setRatio
+ * Set the grid 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 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 and properties and update the grid. If only one
+ * argument is provided, it will be set as and . 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 property and update the grid. Can only be called
+ * after the layer has been added to a map with a center/extent.
+ *
+ * Parameters:
+ * origin - {}
+ */
+ setOrigin: function(origin) {
+ this.origin = origin;
+ this.updateGrid(true);
+ },
+
+ /**
+ * APIMethod: getOrigin
+ * Get the grid property.
+ *
+ * Returns:
+ * {} The grid origin.
+ */
+ getOrigin: function() {
+ if (!this.origin) {
+ this.origin = this.map.getExtent().getCenterLonLat();
+ }
+ return this.origin;
+ },
+
+ /**
+ * APIMethod: setRotation
+ * Set the grid 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:
+ * {}
+ */
+ 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
+
+
+
+
+
+
+
+
+
diff --git a/tests/list-tests.html b/tests/list-tests.html
index 3f378098f1..a56c678896 100644
--- a/tests/list-tests.html
+++ b/tests/list-tests.html
@@ -156,6 +156,7 @@