From 6d43a28da68b0c416b86a2c741d6a824c947466a Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sat, 24 Oct 2009 05:36:34 +0000 Subject: [PATCH] added Graticule control and Util.getFormattedLonLat function. Thanks madair for this excellent patch. p=madair, r=me (closes #1083) git-svn-id: http://svn.openlayers.org/trunk/openlayers@9757 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/graticule.html | 102 +++++++++ lib/OpenLayers.js | 1 + lib/OpenLayers/Control/Graticule.js | 328 ++++++++++++++++++++++++++++ lib/OpenLayers/Lang/en.js | 6 + lib/OpenLayers/Util.js | 60 +++++ tests/Control/Graticule.html | 30 +++ tests/list-tests.html | 1 + 7 files changed, 528 insertions(+) create mode 100644 examples/graticule.html create mode 100644 lib/OpenLayers/Control/Graticule.js create mode 100644 tests/Control/Graticule.html diff --git a/examples/graticule.html b/examples/graticule.html new file mode 100644 index 0000000000..ed441cd6c0 --- /dev/null +++ b/examples/graticule.html @@ -0,0 +1,102 @@ + + + OpenLayers Graticule Example + + + + + + + + + +

Graticule Example

+ +
+
+ +

+ Adds a Graticule control to the map to display a grid of + latitude and longitude. +

+ +
+
+ +
+ + diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index ea7fb2477c..8239c423a1 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -168,6 +168,7 @@ "OpenLayers/Control/NavigationHistory.js", "OpenLayers/Control/Measure.js", "OpenLayers/Control/WMSGetFeatureInfo.js", + "OpenLayers/Control/Graticule.js", "OpenLayers/Geometry.js", "OpenLayers/Geometry/Rectangle.js", "OpenLayers/Geometry/Collection.js", diff --git a/lib/OpenLayers/Control/Graticule.js b/lib/OpenLayers/Control/Graticule.js new file mode 100644 index 0000000000..b36b2ec509 --- /dev/null +++ b/lib/OpenLayers/Control/Graticule.js @@ -0,0 +1,328 @@ +/* Copyright (c) 2006-2009 MetaCarta, Inc., published under a modified BSD license. + * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt + * for the full text of the license. */ + +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.Graticule + * The Graticule displays a grid of latitude/longitude lines reprojected on + * the map. + * + * Inherits from: + * - + * + */ +OpenLayers.Control.Graticule = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: intervals + * {Array(Float)} A list of possible graticule widths in degrees. + */ + intervals: [ 45, 30, 20, 10, 5, 2, 1, + 0.5, 0.2, 0.1, 0.05, 0.01, + 0.005, 0.002, 0.001 ], + + /** + * APIProperty: displayInLayerSwitcher + * {Boolean} Allows the Graticule control to be switched on and off. + * defaults to true. + */ + displayInLayerSwitcher: true, + + /** + * APIProperty: visible + * {Boolean} should the graticule be initially visible (default=true) + */ + visible: true, + + /** + * APIProperty: numPoints + * {Integer} The number of points to use in each graticule line. Higher + * numbers result in a smoother curve for projected maps + */ + numPoints: 50, + + /** + * APIProperty: targetSize + * {Integer} The maximum size of the grid in pixels on the map + */ + targetSize: 200, + + /** + * APIProperty: layerName + * {String} the name to be displayed in the layer switcher + */ + layerName: "Graticule", + + /** + * APIProperty: labelled + * {Boolean} Should the graticule lines be labelled?. default=true + */ + labelled: true, + + /** + * APIProperty: labelFormat + * {String} the format of the labels, default = 'dm'. See + * for other options. + */ + labelFormat: 'dm', + + /** + * APIProperty: lineSymbolizer + * {symbolizer} the symbolizer used to render lines + */ + lineSymbolizer: { + strokeColor: "#333", + strokeWidth: 1, + strokeOpacity: 0.5 + }, + + /** + * APIProperty: labelSymbolizer + * {symbolizer} the symbolizer used to render labels + */ + labelSymbolizer: {}, + + /** + * Property: gratLayer + * {OpenLayers.Layer.Vector} vector layer used to draw the graticule on + */ + gratLayer: null, + + /** + * Constructor: OpenLayers.Control.Graticule + * Create a new graticule control to display a grid of latitude longitude + * lines. + * + * Parameters: + * options - {Object} An optional object whose properties will be used + * to extend the control. + */ + initialize: function(options) { + OpenLayers.Control.prototype.initialize.apply(this, [options]); + + this.labelSymbolizer.stroke = false; + this.labelSymbolizer.fill = false; + this.labelSymbolizer.label = "${label}"; + this.labelSymbolizer.labelAlign = "${labelAlign}"; + this.labelSymbolizer.labelXOffset = "${xOffset}"; + this.labelSymbolizer.labelYOffset = "${yOffset}"; + }, + + /** + * Method: draw + * + * initializes the graticule layer and does the initial update + * + * Returns: + * {DOMElement} + */ + draw: function() { + OpenLayers.Control.prototype.draw.apply(this, arguments); + if (!this.gratLayer) { + var gratStyle = new OpenLayers.Style({},{ + rules: [new OpenLayers.Rule({'symbolizer': + {"Point":this.labelSymbolizer, + "Line":this.lineSymbolizer} + })] + }); + this.gratLayer = new OpenLayers.Layer.Vector(this.layerName, { + styleMap: new OpenLayers.StyleMap({'default':gratStyle}), + visibility: this.visible, + displayInLayerSwitcher: this.displayInLayerSwitcher + }); + this.map.addLayer(this.gratLayer); + } + this.map.events.register('moveend', this, this.update); + this.update(); + return this.div; + }, + + /** + * Method: update + * + * calculates the grid to be displayed and actually draws it + * + * Returns: + * {DOMElement} + */ + update: function() { + //wait for the map to be initialized before proceeding + var mapBounds = this.map.getExtent(); + if (!mapBounds) { + return; + } + var mapRect = mapBounds.toGeometry(); + + //clear out the old grid + this.gratLayer.destroyFeatures(); + + //get the projection objects required + var llProj = new OpenLayers.Projection("EPSG:4326"); + var mapProj = this.map.getProjectionObject(); + var mapRes = this.map.getResolution(); + + //if the map is in lon/lat, then the lines are straight and only one + //point is required + if (mapProj.proj && mapProj.proj.projName == "longlat") { + this.numPoints = 1; + } + + //get the map center in EPSG:4326 + var mapCenter = this.map.getCenter(); //lon and lat here are really map x and y + var mapCenterLL = new OpenLayers.Pixel(mapCenter.lon, mapCenter.lat); + OpenLayers.Projection.transform(mapCenterLL, mapProj, llProj); + + /* This block of code determines the lon/lat interval to use for the + * grid by calculating the diagonal size of one grid cell at the map + * center. Iterates through the intervals array until the diagonal + * length is less than the targetSize option. + */ + //find lat/lon interval that results in a grid of less than the target size + var testSq = this.targetSize*mapRes; + testSq *= testSq; //compare squares rather than doing a square root to save time + var llInterval; + for (var i=0; i= mapBounds.bottom && !labelPoint) { + labelPoint = gridPoint; + } + } + if (this.labelled) { + //keep track of when this grid line crosses the map bounds to set + //the label position + //labels along the bottom, add 10 pixel offset up into the map + //TODO add option for labels on top + var labelPos = new OpenLayers.Geometry.Point(labelPoint.x,mapBounds.bottom); + var labelAttrs = { + value: lon, + label: this.labelled?OpenLayers.Util.getFormattedLonLat(lon, "lon", this.labelFormat):"", + labelAlign: "cb", + xOffset: 0, + yOffset: 2 + }; + this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs)); + } + var geom = new OpenLayers.Geometry.LineString(pointList); + lines.push(new OpenLayers.Feature.Vector(geom)); + } + + //now draw the lines of constant latitude + for (var j=0; j < centerLonPoints.length; ++j) { + lat = centerLonPoints[j].y; + if (lat<-90 || lat>90) { //latitudes only valid between -90 and 90 + continue; + } + var pointList = []; + var lonStart = centerLatPoints[0].x; + var lonEnd = centerLatPoints[centerLatPoints.length - 1].x; + var lonDelta = (lonEnd - lonStart)/this.numPoints; + var lon = lonStart; + var labelPoint = null; + for(var i=0; i <= this.numPoints ; ++i) { + var gridPoint = new OpenLayers.Geometry.Point(lon,lat); + gridPoint.transform(llProj, mapProj); + pointList.push(gridPoint); + lon += lonDelta; + if (gridPoint.x < mapBounds.right) { + labelPoint = gridPoint; + } + } + if (this.labelled) { + //keep track of when this grid line crosses the map bounds to set + //the label position + //labels along the right, 30 pixel offset left into the map + //TODO add option for labels on left + var labelPos = new OpenLayers.Geometry.Point(mapBounds.right, labelPoint.y); + var labelAttrs = { + value: lat, + label: this.labelled?OpenLayers.Util.getFormattedLonLat(lat, "lat", this.labelFormat):"", + labelAlign: "rb", + xOffset: -2, + yOffset: 2 + }; + this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs)); + } + var geom = new OpenLayers.Geometry.LineString(pointList); + lines.push(new OpenLayers.Feature.Vector(geom)); + } + this.gratLayer.addFeatures(lines); + }, + + CLASS_NAME: "OpenLayers.Control.Graticule" +}); + diff --git a/lib/OpenLayers/Lang/en.js b/lib/OpenLayers/Lang/en.js index 9f41ca36e6..00c5bf156b 100644 --- a/lib/OpenLayers/Lang/en.js +++ b/lib/OpenLayers/Lang/en.js @@ -81,6 +81,12 @@ OpenLayers.Lang.en = { "target='_blank'>click here", 'scale': "Scale = 1 : ${scaleDenom}", + + //labels for the graticule control + 'W': 'W', + 'E': 'E', + 'N': 'N', + 'S': 'S', // console message 'layerAlreadyAdded': diff --git a/lib/OpenLayers/Util.js b/lib/OpenLayers/Util.js index a2440c5cdc..970926b3e7 100644 --- a/lib/OpenLayers/Util.js +++ b/lib/OpenLayers/Util.js @@ -958,6 +958,8 @@ OpenLayers.Util.getParameters = function(url) { var end = OpenLayers.String.contains(url, "#") ? url.indexOf('#') : url.length; paramsString = url.substring(start, end); + } else { + paramsString = url; } var parameters = {}; @@ -1642,3 +1644,61 @@ OpenLayers.Util.getScrollbarWidth = function() { return scrollbarWidth; }; + +/** + * APIFunction: getFormattedLonLat + * This function will return latitude or longitude value formatted as + * + * Parameters: + * coordinate - {Float} the coordinate value to be formatted + * axis - {String} value of either 'lat' or 'lon' to indicate which axis is to + * to be formatted (default = lat) + * dmsOption - {String} specify the precision of the output can be one of: + * 'dms' show degrees minutes and seconds + * 'dm' show only degrees and minutes + * 'd' show only degrees + * + * Returns: + * {String} the coordinate value formatted as a string + */ +OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) { + if (!dmsOption) { + dmsOption = 'dms'; //default to show degree, minutes, seconds + } + var abscoordinate = Math.abs(coordinate) + var coordinatedegrees = Math.floor(abscoordinate); + + var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60); + var tempcoordinateminutes = coordinateminutes; + coordinateminutes = Math.floor(coordinateminutes); + var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60); + coordinateseconds = Math.round(coordinateseconds*10); + coordinateseconds /= 10; + + if( coordinatedegrees < 10 ) { + coordinatedegrees = "0" + coordinatedegrees; + } + var str = coordinatedegrees + " "; //get degree symbol here somehow for SVG/VML labelling + + if (dmsOption.indexOf('dm') >= 0) { + if( coordinateminutes < 10 ) { + coordinateminutes = "0" + coordinateminutes; + } + str += coordinateminutes + "'"; + + if (dmsOption.indexOf('dms') >= 0) { + if( coordinateseconds < 10 ) { + coordinateseconds = "0" + coordinateseconds; + } + str += coordinateseconds + '"'; + } + } + + if (axis == "lon") { + str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E"); + } else { + str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N"); + } + return str; +}; + diff --git a/tests/Control/Graticule.html b/tests/Control/Graticule.html new file mode 100644 index 0000000000..f65e8aefc1 --- /dev/null +++ b/tests/Control/Graticule.html @@ -0,0 +1,30 @@ + + + + + + + +
+ + diff --git a/tests/list-tests.html b/tests/list-tests.html index 6977f8a0eb..61f4bddbfd 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -15,6 +15,7 @@
  • Control/DragPan.html
  • Control/DrawFeature.html
  • Control/GetFeature.html
  • +
  • Control/Graticule.html
  • Control/KeyboardDefaults.html
  • Control/LayerSwitcher.html
  • Control/Measure.html