| | 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: numPoints |
|---|
| | 37 | * {Integer} The number of points to use in each graticule line. Higher |
|---|
| | 38 | * numbers result in a smoother curve for projected maps |
|---|
| | 39 | */ |
|---|
| | 40 | numPoints: 5, |
|---|
| | 41 | |
|---|
| | 42 | /** |
|---|
| | 43 | * APIProperty: targetSize |
|---|
| | 44 | * {Integer} The maximum size of the grid in pixels on the map |
|---|
| | 45 | */ |
|---|
| | 46 | targetSize: 200, |
|---|
| | 47 | |
|---|
| | 48 | /** |
|---|
| | 49 | * APIProperty: labelled |
|---|
| | 50 | * {Boolean} Should the graticule lines be labelled?. default=false |
|---|
| | 51 | */ |
|---|
| | 52 | labelled: true, |
|---|
| | 53 | |
|---|
| | 54 | /** |
|---|
| | 55 | * Property: gratLayer |
|---|
| | 56 | * {OpenLayers.Layer.Vector} |
|---|
| | 57 | */ |
|---|
| | 58 | gratLayer: null, |
|---|
| | 59 | |
|---|
| | 60 | /** |
|---|
| | 61 | * Constructor: OpenLayers.Control.ScaleLine |
|---|
| | 62 | * Create a new scale line control. |
|---|
| | 63 | * |
|---|
| | 64 | * Parameters: |
|---|
| | 65 | * options - {Object} An optional object whose properties will be used |
|---|
| | 66 | * to extend the control. |
|---|
| | 67 | */ |
|---|
| | 68 | initialize: function(options) { |
|---|
| | 69 | OpenLayers.Control.prototype.initialize.apply(this, [options]); |
|---|
| | 70 | }, |
|---|
| | 71 | |
|---|
| | 72 | /** |
|---|
| | 73 | * Method: draw |
|---|
| | 74 | * |
|---|
| | 75 | * Returns: |
|---|
| | 76 | * {DOMElement} |
|---|
| | 77 | */ |
|---|
| | 78 | draw: function() { |
|---|
| | 79 | OpenLayers.Control.prototype.draw.apply(this, arguments); |
|---|
| | 80 | if (!this.gratLayer) { |
|---|
| | 81 | var gratStyle = new OpenLayers.Style({ |
|---|
| | 82 | strokeColor: "black", |
|---|
| | 83 | strokeWidth: 1, |
|---|
| | 84 | strokeOpacity: 1, |
|---|
| | 85 | label: "${gridLabel}", |
|---|
| | 86 | align: "ct" |
|---|
| | 87 | }, { |
|---|
| | 88 | context: { |
|---|
| | 89 | gridLabel: function(feature) { |
|---|
| | 90 | return feature.attributes.labelled ? |
|---|
| | 91 | OpenLayers.Util.getFormattedLonLat(feature.attributes.value):''; |
|---|
| | 92 | } |
|---|
| | 93 | } |
|---|
| | 94 | }); |
|---|
| | 95 | this.gratLayer = new OpenLayers.Layer.Vector("Graticule", { |
|---|
| | 96 | styleMap: new OpenLayers.StyleMap(gratStyle), |
|---|
| | 97 | displayInLayerSwitcher: this.displayInLayerSwitcher |
|---|
| | 98 | }); |
|---|
| | 99 | this.map.addLayer(this.gratLayer); |
|---|
| | 100 | } |
|---|
| | 101 | this.map.events.register('moveend', this, this.update); |
|---|
| | 102 | this.update(); |
|---|
| | 103 | return this.div; |
|---|
| | 104 | }, |
|---|
| | 105 | |
|---|
| | 106 | update: function() { |
|---|
| | 107 | //wait for the map to be initialized before proceeding |
|---|
| | 108 | var mapBounds = this.map.getExtent(); |
|---|
| | 109 | if (!mapBounds) { |
|---|
| | 110 | return; |
|---|
| | 111 | } |
|---|
| | 112 | |
|---|
| | 113 | //clear out the old grid |
|---|
| | 114 | this.gratLayer.destroyFeatures(); |
|---|
| | 115 | |
|---|
| | 116 | //get the projection objects required |
|---|
| | 117 | var llProj = new OpenLayers.Projection("EPSG:4326"); |
|---|
| | 118 | var mapProj = this.map.getProjectionObject(); |
|---|
| | 119 | var mapRes = this.map.getResolution(); |
|---|
| | 120 | |
|---|
| | 121 | //get the map center in EPSG:4326 |
|---|
| | 122 | var mapCenter = this.map.getCenter(); //lon and lat here are really map x and y |
|---|
| | 123 | var mapCenterLL = new OpenLayers.Pixel(mapCenter.lon, mapCenter.lat); |
|---|
| | 124 | OpenLayers.Projection.transform(mapCenterLL, mapProj, llProj); |
|---|
| | 125 | |
|---|
| | 126 | /* This block of code determines the lon/lat interval to use for the |
|---|
| | 127 | * grid by calculating the diagonal size of one grid cell at the map |
|---|
| | 128 | * center. Iterates through the intervals array until the diagonal |
|---|
| | 129 | * length is less than the targetSize option. |
|---|
| | 130 | */ |
|---|
| | 131 | //find lat/lon interval that results in a grid of less than the target size |
|---|
| | 132 | var testSq = this.targetSize*mapRes; |
|---|
| | 133 | testSq *= testSq; //compare squares rather than doing a square root to save time |
|---|
| | 134 | var llInterval; |
|---|
| | 135 | for (var i=0; i<this.intervals.length; ++i) { |
|---|
| | 136 | llInterval = this.intervals[i]; //could do this for both x and y?? |
|---|
| | 137 | var delta = llInterval/2; |
|---|
| | 138 | var p1 = mapCenterLL.offset(new OpenLayers.Pixel(-delta, -delta)); //test coords in EPSG:4326 space |
|---|
| | 139 | var p2 = mapCenterLL.offset(new OpenLayers.Pixel( delta, delta)); |
|---|
| | 140 | OpenLayers.Projection.transform(p1, llProj, mapProj); // convert them back to map projection |
|---|
| | 141 | OpenLayers.Projection.transform(p2, llProj, mapProj); |
|---|
| | 142 | var distSq = (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y); |
|---|
| | 143 | if (distSq <= testSq) { |
|---|
| | 144 | break; |
|---|
| | 145 | } |
|---|
| | 146 | } |
|---|
| | 147 | //alert(llInterval); |
|---|
| | 148 | |
|---|
| | 149 | //round the LL center to an even number based on the interval |
|---|
| | 150 | mapCenterLL.x = Math.floor(mapCenterLL.x/llInterval)*llInterval; |
|---|
| | 151 | mapCenterLL.y = Math.floor(mapCenterLL.y/llInterval)*llInterval; |
|---|
| | 152 | //TODO adjust for minutses/seconds? |
|---|
| | 153 | |
|---|
| | 154 | /* The following 2 blocks calculate the nodes of the grid along a |
|---|
| | 155 | * line of constant longitude (then latitiude) running through the |
|---|
| | 156 | * center of the map until it reaches the map edge. The calculation |
|---|
| | 157 | * goes from the center in both directions to the edge. |
|---|
| | 158 | */ |
|---|
| | 159 | //get the central longitude line, increment the latitude |
|---|
| | 160 | var iter = 0 |
|---|
| | 161 | var centerLonPoints = [mapCenterLL.clone()]; |
|---|
| | 162 | var newPoint = mapCenterLL.clone(); |
|---|
| | 163 | var mapXY; |
|---|
| | 164 | do { |
|---|
| | 165 | newPoint = newPoint.offset(new OpenLayers.Pixel(0,llInterval)); |
|---|
| | 166 | mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); |
|---|
| | 167 | centerLonPoints.unshift(newPoint); |
|---|
| | 168 | } while (mapBounds.containsPixel(mapXY) && ++iter<1000); |
|---|
| | 169 | newPoint = mapCenterLL.clone(); |
|---|
| | 170 | do { |
|---|
| | 171 | newPoint = newPoint.offset(new OpenLayers.Pixel(0,-llInterval)); |
|---|
| | 172 | mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); |
|---|
| | 173 | centerLonPoints.push(newPoint); |
|---|
| | 174 | } while (mapBounds.containsPixel(mapXY) && ++iter<1000); |
|---|
| | 175 | |
|---|
| | 176 | //get the central latitude line, increment the longitude |
|---|
| | 177 | iter = 0 |
|---|
| | 178 | var centerLatPoints = [mapCenterLL.clone()]; |
|---|
| | 179 | newPoint = mapCenterLL.clone(); |
|---|
| | 180 | do { |
|---|
| | 181 | newPoint = newPoint.offset(new OpenLayers.Pixel(-llInterval, 0)); |
|---|
| | 182 | mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); |
|---|
| | 183 | centerLatPoints.unshift(newPoint); |
|---|
| | 184 | } while (mapBounds.containsPixel(mapXY) && ++iter<1000); |
|---|
| | 185 | newPoint = mapCenterLL.clone(); |
|---|
| | 186 | do { |
|---|
| | 187 | newPoint = newPoint.offset(new OpenLayers.Pixel(llInterval, 0)); |
|---|
| | 188 | mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); |
|---|
| | 189 | centerLatPoints.push(newPoint); |
|---|
| | 190 | } while (mapBounds.containsPixel(mapXY) && ++iter<1000); |
|---|
| | 191 | |
|---|
| | 192 | //now generate a line for each node in the central lat and lon lines |
|---|
| | 193 | var lines = []; |
|---|
| | 194 | for(var i=0; i < centerLatPoints.length; ++i) { |
|---|
| | 195 | var lon = centerLatPoints[i].x; |
|---|
| | 196 | var pointList = []; |
|---|
| | 197 | for (var j=0; j < centerLonPoints.length; ++j) { |
|---|
| | 198 | //TODO: increase the granularity for curved lines here |
|---|
| | 199 | lat = centerLonPoints[j].y; |
|---|
| | 200 | if (lat<-90 || lat>90) { //latitudes only valid between -90 and 90 |
|---|
| | 201 | continue; |
|---|
| | 202 | } |
|---|
| | 203 | pointList.push(new OpenLayers.Geometry.Point(lon,lat)); |
|---|
| | 204 | } |
|---|
| | 205 | var geom = new OpenLayers.Geometry.LineString(pointList); |
|---|
| | 206 | geom.transform(llProj, mapProj); |
|---|
| | 207 | lines.push(new OpenLayers.Feature.Vector(geom, {value: lon, labelled: this.labelled})); |
|---|
| | 208 | } |
|---|
| | 209 | for (var j=0; j < centerLonPoints.length; ++j) { |
|---|
| | 210 | lat = centerLonPoints[j].y; |
|---|
| | 211 | if (lat<-90 || lat>90) { //latitudes only valid between -90 and 90 |
|---|
| | 212 | continue; |
|---|
| | 213 | } |
|---|
| | 214 | var pointList = []; |
|---|
| | 215 | for(var i=0; i < centerLatPoints.length; ++i) { |
|---|
| | 216 | //TODO: increase the granularity for curved lines here |
|---|
| | 217 | var lon = centerLatPoints[i].x; |
|---|
| | 218 | pointList.push(new OpenLayers.Geometry.Point(lon,lat)); |
|---|
| | 219 | } |
|---|
| | 220 | var geom = new OpenLayers.Geometry.LineString(pointList); |
|---|
| | 221 | geom.transform(llProj, mapProj); |
|---|
| | 222 | lines.push(new OpenLayers.Feature.Vector(geom, {value: lat, labelled: this.labelled})); |
|---|
| | 223 | } |
|---|
| | 224 | this.gratLayer.addFeatures(lines); |
|---|
| | 225 | }, |
|---|
| | 226 | |
|---|
| | 227 | CLASS_NAME: "OpenLayers.Control.Graticule" |
|---|
| | 228 | }); |
|---|
| | 229 | |
|---|
| | 230 | OpenLayers.Util.getFormattedLonLat = function(coordinate) { |
|---|
| | 231 | var abscoordinate = Math.abs(coordinate) |
|---|
| | 232 | var coordinatedegrees = Math.floor(abscoordinate); |
|---|
| | 233 | |
|---|
| | 234 | var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60); |
|---|
| | 235 | var tempcoordinateminutes = coordinateminutes; |
|---|
| | 236 | coordinateminutes = Math.floor(coordinateminutes); |
|---|
| | 237 | var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60); |
|---|
| | 238 | coordinateseconds = Math.round(coordinateseconds*10); |
|---|
| | 239 | coordinateseconds /= 10; |
|---|
| | 240 | |
|---|
| | 241 | if( coordinatedegrees < 10 ) { |
|---|
| | 242 | coordinatedegrees = "0" + coordinatedegrees; |
|---|
| | 243 | } |
|---|
| | 244 | |
|---|
| | 245 | if( coordinateminutes < 10 ) { |
|---|
| | 246 | coordinateminutes = "0" + coordinateminutes; |
|---|
| | 247 | } |
|---|
| | 248 | |
|---|
| | 249 | if( coordinateseconds < 10 ) { |
|---|
| | 250 | coordinateseconds = "0" + coordinateseconds; |
|---|
| | 251 | } |
|---|
| | 252 | var str = coordinatedegrees + " "; //get degree symbol here somehow for SVG/VML labelling |
|---|
| | 253 | str += coordinateminutes + "'"; |
|---|
| | 254 | str += coordinateseconds + '"'; |
|---|
| | 255 | return str; |
|---|
| | 256 | }; |
|---|
| | 257 | |