Graticule.js 14 KB

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