| | 1 | /* Copyright (c) 2006-2007 MetaCarta, Inc., published under a modified BSD license. |
|---|
| | 2 | * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt |
|---|
| | 3 | * for the full text of the license. */ |
|---|
| | 4 | |
|---|
| | 5 | /** |
|---|
| | 6 | * @requires OpenLayers/Control.js |
|---|
| | 7 | */ |
|---|
| | 8 | |
|---|
| | 9 | /** |
|---|
| | 10 | * Class: OpenLayers.Control.Graticule |
|---|
| | 11 | * The Graticule displays a grid of latitude/longitude lines reprojected on |
|---|
| | 12 | * the map. |
|---|
| | 13 | * |
|---|
| | 14 | * Inherits from: |
|---|
| | 15 | * - <OpenLayers.Control> |
|---|
| | 16 | * |
|---|
| | 17 | */ |
|---|
| | 18 | OpenLayers.Control.Graticule = OpenLayers.Class(OpenLayers.Control, { |
|---|
| | 19 | |
|---|
| | 20 | /** |
|---|
| | 21 | * APIProperty: intervals |
|---|
| | 22 | * {Array(Float)} A list of possible graticule widths in degrees. |
|---|
| | 23 | */ |
|---|
| | 24 | intervals: [ 45, 30, 20, 10, 5, 2, 1, |
|---|
| | 25 | 0.5, 0.2, 0.1, 0.05, 0.01, |
|---|
| | 26 | 0.005, 0.002, 0.001 ], |
|---|
| | 27 | |
|---|
| | 28 | /** |
|---|
| | 29 | * APIProperty: displayInLayerSwitcher |
|---|
| | 30 | * {Boolean} Allows the Graticule control to be switched on and off. |
|---|
| | 31 | * defaults to true. |
|---|
| | 32 | */ |
|---|
| | 33 | displayInLayerSwitcher: true, |
|---|
| | 34 | |
|---|
| | 35 | /** |
|---|
| | 36 | * APIProperty: visible |
|---|
| | 37 | * {Boolean} should the graticule be initially visible (default=true) |
|---|
| | 38 | */ |
|---|
| | 39 | visible: true, |
|---|
| | 40 | |
|---|
| | 41 | /** |
|---|
| | 42 | * APIProperty: numPoints |
|---|
| | 43 | * {Integer} The number of points to use in each graticule line. Higher |
|---|
| | 44 | * numbers result in a smoother curve for projected maps |
|---|
| | 45 | */ |
|---|
| | 46 | numPoints: 50, |
|---|
| | 47 | |
|---|
| | 48 | /** |
|---|
| | 49 | * APIProperty: targetSize |
|---|
| | 50 | * {Integer} The maximum size of the grid in pixels on the map |
|---|
| | 51 | */ |
|---|
| | 52 | targetSize: 200, |
|---|
| | 53 | |
|---|
| | 54 | /** |
|---|
| | 55 | * APIProperty: labelled |
|---|
| | 56 | * {Boolean} Should the graticule lines be labelled?. default=false |
|---|
| | 57 | */ |
|---|
| | 58 | labelled: true, |
|---|
| | 59 | |
|---|
| | 60 | /** |
|---|
| | 61 | * Property: gratLayer |
|---|
| | 62 | * {OpenLayers.Layer.Vector} |
|---|
| | 63 | */ |
|---|
| | 64 | gratLayer: null, |
|---|
| | 65 | |
|---|
| | 66 | /** |
|---|
| | 67 | * Constructor: OpenLayers.Control.ScaleLine |
|---|
| | 68 | * Create a new scale line control. |
|---|
| | 69 | * |
|---|
| | 70 | * Parameters: |
|---|
| | 71 | * options - {Object} An optional object whose properties will be used |
|---|
| | 72 | * to extend the control. |
|---|
| | 73 | */ |
|---|
| | 74 | initialize: function(options) { |
|---|
| | 75 | OpenLayers.Control.prototype.initialize.apply(this, [options]); |
|---|
| | 76 | }, |
|---|
| | 77 | |
|---|
| | 78 | /** |
|---|
| | 79 | * Method: draw |
|---|
| | 80 | * |
|---|
| | 81 | * Returns: |
|---|
| | 82 | * {DOMElement} |
|---|
| | 83 | */ |
|---|
| | 84 | draw: function() { |
|---|
| | 85 | OpenLayers.Control.prototype.draw.apply(this, arguments); |
|---|
| | 86 | if (!this.gratLayer) { |
|---|
| | 87 | var gratStyle = new OpenLayers.Style({ |
|---|
| | 88 | strokeColor: "#333", |
|---|
| | 89 | strokeWidth: 1, |
|---|
| | 90 | strokeOpacity: 0.5, |
|---|
| | 91 | label: "${label}", |
|---|
| | 92 | align: "ct" |
|---|
| | 93 | }); |
|---|
| | 94 | this.gratLayer = new OpenLayers.Layer.Vector("Graticule", { |
|---|
| | 95 | styleMap: new OpenLayers.StyleMap(gratStyle), |
|---|
| | 96 | visibility: this.visible, |
|---|
| | 97 | labelOffset: new OpenLayers.Pixel(0,0), |
|---|
| | 98 | displayInLayerSwitcher: this.displayInLayerSwitcher |
|---|
| | 99 | }); |
|---|
| | 100 | this.map.addLayer(this.gratLayer); |
|---|
| | 101 | } |
|---|
| | 102 | this.map.events.register('moveend', this, this.update); |
|---|
| | 103 | this.update(); |
|---|
| | 104 | return this.div; |
|---|
| | 105 | }, |
|---|
| | 106 | |
|---|
| | 107 | update: function() { |
|---|
| | 108 | //wait for the map to be initialized before proceeding |
|---|
| | 109 | var mapBounds = this.map.getExtent(); |
|---|
| | 110 | if (!mapBounds) { |
|---|
| | 111 | return; |
|---|
| | 112 | } |
|---|
| | 113 | var mapRect = mapBounds.toGeometry(); |
|---|
| | 114 | |
|---|
| | 115 | //clear out the old grid |
|---|
| | 116 | this.gratLayer.destroyFeatures(); |
|---|
| | 117 | |
|---|
| | 118 | //get the projection objects required |
|---|
| | 119 | var llProj = new OpenLayers.Projection("EPSG:4326"); |
|---|
| | 120 | var mapProj = this.map.getProjectionObject(); |
|---|
| | 121 | var mapRes = this.map.getResolution(); |
|---|
| | 122 | if (mapProj.proj.projName == "longlat") { |
|---|
| | 123 | this.numPoints = 1; |
|---|
| | 124 | } |
|---|
| | 125 | |
|---|
| | 126 | //get the map center in EPSG:4326 |
|---|
| | 127 | var mapCenter = this.map.getCenter(); //lon and lat here are really map x and y |
|---|
| | 128 | var mapCenterLL = new OpenLayers.Pixel(mapCenter.lon, mapCenter.lat); |
|---|
| | 129 | OpenLayers.Projection.transform(mapCenterLL, mapProj, llProj); |
|---|
| | 130 | |
|---|
| | 131 | /* This block of code determines the lon/lat interval to use for the |
|---|
| | 132 | * grid by calculating the diagonal size of one grid cell at the map |
|---|
| | 133 | * center. Iterates through the intervals array until the diagonal |
|---|
| | 134 | * length is less than the targetSize option. |
|---|
| | 135 | */ |
|---|
| | 136 | //find lat/lon interval that results in a grid of less than the target size |
|---|
| | 137 | var testSq = this.targetSize*mapRes; |
|---|
| | 138 | testSq *= testSq; //compare squares rather than doing a square root to save time |
|---|
| | 139 | var llInterval; |
|---|
| | 140 | for (var i=0; i<this.intervals.length; ++i) { |
|---|
| | 141 | llInterval = this.intervals[i]; //could do this for both x and y?? |
|---|
| | 142 | var delta = llInterval/2; |
|---|
| | 143 | var p1 = mapCenterLL.offset(new OpenLayers.Pixel(-delta, -delta)); //test coords in EPSG:4326 space |
|---|
| | 144 | var p2 = mapCenterLL.offset(new OpenLayers.Pixel( delta, delta)); |
|---|
| | 145 | OpenLayers.Projection.transform(p1, llProj, mapProj); // convert them back to map projection |
|---|
| | 146 | OpenLayers.Projection.transform(p2, llProj, mapProj); |
|---|
| | 147 | var distSq = (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y); |
|---|
| | 148 | if (distSq <= testSq) { |
|---|
| | 149 | break; |
|---|
| | 150 | } |
|---|
| | 151 | } |
|---|
| | 152 | //alert(llInterval); |
|---|
| | 153 | |
|---|
| | 154 | //round the LL center to an even number based on the interval |
|---|
| | 155 | mapCenterLL.x = Math.floor(mapCenterLL.x/llInterval)*llInterval; |
|---|
| | 156 | mapCenterLL.y = Math.floor(mapCenterLL.y/llInterval)*llInterval; |
|---|
| | 157 | //TODO adjust for minutses/seconds? |
|---|
| | 158 | |
|---|
| | 159 | /* The following 2 blocks calculate the nodes of the grid along a |
|---|
| | 160 | * line of constant longitude (then latitiude) running through the |
|---|
| | 161 | * center of the map until it reaches the map edge. The calculation |
|---|
| | 162 | * goes from the center in both directions to the edge. |
|---|
| | 163 | */ |
|---|
| | 164 | //get the central longitude line, increment the latitude |
|---|
| | 165 | var iter = 0 |
|---|
| | 166 | var centerLonPoints = [mapCenterLL.clone()]; |
|---|
| | 167 | var newPoint = mapCenterLL.clone(); |
|---|
| | 168 | var mapXY; |
|---|
| | 169 | do { |
|---|
| | 170 | newPoint = newPoint.offset(new OpenLayers.Pixel(0,llInterval)); |
|---|
| | 171 | mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); |
|---|
| | 172 | centerLonPoints.unshift(newPoint); |
|---|
| | 173 | } while (mapBounds.containsPixel(mapXY) && ++iter<1000); |
|---|
| | 174 | newPoint = mapCenterLL.clone(); |
|---|
| | 175 | do { |
|---|
| | 176 | newPoint = newPoint.offset(new OpenLayers.Pixel(0,-llInterval)); |
|---|
| | 177 | mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); |
|---|
| | 178 | centerLonPoints.push(newPoint); |
|---|
| | 179 | } while (mapBounds.containsPixel(mapXY) && ++iter<1000); |
|---|
| | 180 | |
|---|
| | 181 | //get the central latitude line, increment the longitude |
|---|
| | 182 | iter = 0 |
|---|
| | 183 | var centerLatPoints = [mapCenterLL.clone()]; |
|---|
| | 184 | newPoint = mapCenterLL.clone(); |
|---|
| | 185 | do { |
|---|
| | 186 | newPoint = newPoint.offset(new OpenLayers.Pixel(-llInterval, 0)); |
|---|
| | 187 | mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); |
|---|
| | 188 | centerLatPoints.unshift(newPoint); |
|---|
| | 189 | } while (mapBounds.containsPixel(mapXY) && ++iter<1000); |
|---|
| | 190 | newPoint = mapCenterLL.clone(); |
|---|
| | 191 | do { |
|---|
| | 192 | newPoint = newPoint.offset(new OpenLayers.Pixel(llInterval, 0)); |
|---|
| | 193 | mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); |
|---|
| | 194 | centerLatPoints.push(newPoint); |
|---|
| | 195 | } while (mapBounds.containsPixel(mapXY) && ++iter<1000); |
|---|
| | 196 | |
|---|
| | 197 | //now generate a line for each node in the central lat and lon lines |
|---|
| | 198 | //first loop over constant longitude |
|---|
| | 199 | var lines = []; |
|---|
| | 200 | for(var i=0; i < centerLatPoints.length; ++i) { |
|---|
| | 201 | var lon = centerLatPoints[i].x; |
|---|
| | 202 | var pointList = []; |
|---|
| | 203 | var latEnd = Math.min(centerLonPoints[0].y, 90); |
|---|
| | 204 | var latStart = Math.max(centerLonPoints[centerLonPoints.length - 1].y, -90); |
|---|
| | 205 | var latDelta = (latEnd - latStart)/this.numPoints; |
|---|
| | 206 | var lat = latStart; |
|---|
| | 207 | var gridAttrs = { |
|---|
| | 208 | value: lon, |
|---|
| | 209 | label: this.labelled?OpenLayers.Util.getFormattedLonLat(lon, "lon"):"", |
|---|
| | 210 | labelPos: null |
|---|
| | 211 | }; |
|---|
| | 212 | for(var j=0; j<= this.numPoints; ++j) { |
|---|
| | 213 | var gridPoint = new OpenLayers.Geometry.Point(lon,lat); |
|---|
| | 214 | gridPoint.transform(llProj, mapProj); |
|---|
| | 215 | pointList.push(gridPoint); |
|---|
| | 216 | if (this.labelled) { |
|---|
| | 217 | //keep track of when this grid line crosses the map bounds to set |
|---|
| | 218 | //the label position |
|---|
| | 219 | //labels along the bottom, add 10 pixel offset up into the map |
|---|
| | 220 | //TODO add option for labels on top |
|---|
| | 221 | if (gridPoint.y >= mapBounds.bottom && !gridAttrs.labelPos) { |
|---|
| | 222 | gridAttrs.labelPos = new OpenLayers.Geometry.Point(gridPoint.x,mapBounds.bottom+10*mapRes); |
|---|
| | 223 | } |
|---|
| | 224 | } |
|---|
| | 225 | lat += latDelta; |
|---|
| | 226 | } |
|---|
| | 227 | var geom = new OpenLayers.Geometry.LineString(pointList); |
|---|
| | 228 | lines.push(new OpenLayers.Feature.Vector(geom, gridAttrs)); |
|---|
| | 229 | } |
|---|
| | 230 | |
|---|
| | 231 | //now draw the lines of constant latitude |
|---|
| | 232 | for (var j=0; j < centerLonPoints.length; ++j) { |
|---|
| | 233 | lat = centerLonPoints[j].y; |
|---|
| | 234 | if (lat<-90 || lat>90) { //latitudes only valid between -90 and 90 |
|---|
| | 235 | continue; |
|---|
| | 236 | } |
|---|
| | 237 | var pointList = []; |
|---|
| | 238 | var lonStart = centerLatPoints[0].x; |
|---|
| | 239 | var lonEnd = centerLatPoints[centerLatPoints.length - 1].x; |
|---|
| | 240 | var lonDelta = (lonEnd - lonStart)/this.numPoints; |
|---|
| | 241 | var lon = lonStart; |
|---|
| | 242 | var gridAttrs = { |
|---|
| | 243 | value: lat, |
|---|
| | 244 | label: this.labelled?OpenLayers.Util.getFormattedLonLat(lat, "lat"):"", |
|---|
| | 245 | labelPos: null |
|---|
| | 246 | }; |
|---|
| | 247 | for(var i=0; i <= this.numPoints ; ++i) { |
|---|
| | 248 | var gridPoint = new OpenLayers.Geometry.Point(lon,lat); |
|---|
| | 249 | gridPoint.transform(llProj, mapProj); |
|---|
| | 250 | pointList.push(gridPoint); |
|---|
| | 251 | if (this.labelled) { |
|---|
| | 252 | //keep track of when this grid line crosses the map bounds to set |
|---|
| | 253 | //the label position |
|---|
| | 254 | //labels along the right, 30 pixel offset left into the map |
|---|
| | 255 | //TODO add option for labels on left |
|---|
| | 256 | if (gridPoint.x < mapBounds.right) { |
|---|
| | 257 | gridAttrs.labelPos = new OpenLayers.Geometry.Point(mapBounds.right-30*mapRes, gridPoint.y+6*mapRes); |
|---|
| | 258 | } |
|---|
| | 259 | } |
|---|
| | 260 | lon += lonDelta; |
|---|
| | 261 | } |
|---|
| | 262 | var geom = new OpenLayers.Geometry.LineString(pointList); |
|---|
| | 263 | lines.push(new OpenLayers.Feature.Vector(geom, gridAttrs)); |
|---|
| | 264 | } |
|---|
| | 265 | this.gratLayer.addFeatures(lines); |
|---|
| | 266 | }, |
|---|
| | 267 | |
|---|
| | 268 | CLASS_NAME: "OpenLayers.Control.Graticule" |
|---|
| | 269 | }); |
|---|
| | 270 | |
|---|
| | 271 | OpenLayers.Util.getFormattedLonLat = function(coordinate, axis) { |
|---|
| | 272 | var abscoordinate = Math.abs(coordinate) |
|---|
| | 273 | var coordinatedegrees = Math.floor(abscoordinate); |
|---|
| | 274 | |
|---|
| | 275 | var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60); |
|---|
| | 276 | var tempcoordinateminutes = coordinateminutes; |
|---|
| | 277 | coordinateminutes = Math.floor(coordinateminutes); |
|---|
| | 278 | var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60); |
|---|
| | 279 | coordinateseconds = Math.round(coordinateseconds*10); |
|---|
| | 280 | coordinateseconds /= 10; |
|---|
| | 281 | |
|---|
| | 282 | if( coordinatedegrees < 10 ) { |
|---|
| | 283 | coordinatedegrees = "0" + coordinatedegrees; |
|---|
| | 284 | } |
|---|
| | 285 | |
|---|
| | 286 | if( coordinateminutes < 10 ) { |
|---|
| | 287 | coordinateminutes = "0" + coordinateminutes; |
|---|
| | 288 | } |
|---|
| | 289 | |
|---|
| | 290 | if( coordinateseconds < 10 ) { |
|---|
| | 291 | coordinateseconds = "0" + coordinateseconds; |
|---|
| | 292 | } |
|---|
| | 293 | var str = coordinatedegrees + " "; //get degree symbol here somehow for SVG/VML labelling |
|---|
| | 294 | str += coordinateminutes + "'"; |
|---|
| | 295 | str += coordinateseconds + '"'; |
|---|
| | 296 | if (axis == "lon") { |
|---|
| | 297 | str += coordinate < 0 ? "W" : "E"; |
|---|
| | 298 | } else { |
|---|
| | 299 | str += coordinate < 0 ? "S" : "N"; |
|---|
| | 300 | } |
|---|
| | 301 | return str; |
|---|
| | 302 | }; |
|---|
| | 303 | |