Graticule.js 14 KB

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