proj4leaflet.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. (function (factory) {
  2. var L, proj4;
  3. if (typeof define === 'function' && define.amd) {
  4. // AMD
  5. define(['leaflet', 'proj4'], factory);
  6. } else if (typeof module === 'object' && typeof module.exports === "object") {
  7. // Node/CommonJS
  8. L = require('leaflet');
  9. proj4 = require('proj4');
  10. module.exports = factory(L, proj4);
  11. } else {
  12. // Browser globals
  13. if (typeof window.L === 'undefined' || typeof window.proj4 === 'undefined')
  14. throw 'Leaflet and proj4 must be loaded first';
  15. factory(window.L, window.proj4);
  16. }
  17. }(function (L, proj4) {
  18. L.Proj = {};
  19. L.Proj._isProj4Obj = function(a) {
  20. return (typeof a.inverse !== 'undefined' &&
  21. typeof a.forward !== 'undefined');
  22. };
  23. L.Proj.Projection = L.Class.extend({
  24. initialize: function(code, def, bounds) {
  25. var isP4 = L.Proj._isProj4Obj(code);
  26. this._proj = isP4 ? code : this._projFromCodeDef(code, def);
  27. this.bounds = isP4 ? def : bounds;
  28. },
  29. project: function (latlng) {
  30. var point = this._proj.forward([latlng.lng, latlng.lat]);
  31. return new L.Point(point[0], point[1]);
  32. },
  33. unproject: function (point, unbounded) {
  34. var point2 = this._proj.inverse([point.x, point.y]);
  35. return new L.LatLng(point2[1], point2[0], unbounded);
  36. },
  37. _projFromCodeDef: function(code, def) {
  38. if (def) {
  39. proj4.defs(code, def);
  40. } else if (proj4.defs[code] === undefined) {
  41. var urn = code.split(':');
  42. if (urn.length > 3) {
  43. code = urn[urn.length - 3] + ':' + urn[urn.length - 1];
  44. }
  45. if (proj4.defs[code] === undefined) {
  46. throw 'No projection definition for code ' + code;
  47. }
  48. }
  49. return proj4(code);
  50. }
  51. });
  52. L.Proj.CRS = L.Class.extend({
  53. includes: L.CRS,
  54. options: {
  55. transformation: new L.Transformation(1, 0, -1, 0)
  56. },
  57. initialize: function(a, b, c) {
  58. var code,
  59. proj,
  60. def,
  61. options;
  62. if (L.Proj._isProj4Obj(a)) {
  63. proj = a;
  64. code = proj.srsCode;
  65. options = b || {};
  66. this.projection = new L.Proj.Projection(proj, options.bounds);
  67. } else {
  68. code = a;
  69. def = b;
  70. options = c || {};
  71. this.projection = new L.Proj.Projection(code, def, options.bounds);
  72. }
  73. L.Util.setOptions(this, options);
  74. this.code = code;
  75. this.transformation = this.options.transformation;
  76. if (this.options.origin) {
  77. this.transformation =
  78. new L.Transformation(1, -this.options.origin[0],
  79. -1, this.options.origin[1]);
  80. }
  81. if (this.options.scales) {
  82. this._scales = this.options.scales;
  83. } else if (this.options.resolutions) {
  84. this._scales = [];
  85. for (var i = this.options.resolutions.length - 1; i >= 0; i--) {
  86. if (this.options.resolutions[i]) {
  87. this._scales[i] = 1 / this.options.resolutions[i];
  88. }
  89. }
  90. }
  91. this.infinite = !this.options.bounds;
  92. },
  93. scale: function(zoom) {
  94. var iZoom = Math.floor(zoom),
  95. baseScale,
  96. nextScale,
  97. scaleDiff,
  98. zDiff;
  99. if (zoom === iZoom) {
  100. return this._scales[zoom];
  101. } else {
  102. // Non-integer zoom, interpolate
  103. baseScale = this._scales[iZoom];
  104. nextScale = this._scales[iZoom + 1];
  105. scaleDiff = nextScale - baseScale;
  106. zDiff = (zoom - iZoom);
  107. return baseScale + scaleDiff * zDiff;
  108. }
  109. },
  110. zoom: function(scale) {
  111. // Find closest number in this._scales, down
  112. var downScale = this._closestElement(this._scales, scale),
  113. downZoom = this._scales.indexOf(downScale),
  114. nextScale,
  115. nextZoom,
  116. scaleDiff;
  117. // Check if scale is downScale => return array index
  118. if (scale === downScale) {
  119. return downZoom;
  120. }
  121. // Interpolate
  122. nextZoom = downZoom + 1;
  123. nextScale = this._scales[nextZoom];
  124. if (nextScale === undefined) {
  125. return Infinity;
  126. }
  127. scaleDiff = nextScale - downScale;
  128. return (scale - downScale) / scaleDiff + downZoom;
  129. },
  130. /* Get the closest lowest element in an array */
  131. _closestElement: function(array, element) {
  132. var low;
  133. for (var i = array.length; i--;) {
  134. if (array[i] <= element && (low === undefined || low < array[i])) {
  135. low = array[i];
  136. }
  137. }
  138. return low;
  139. }
  140. });
  141. L.Proj.GeoJSON = L.GeoJSON.extend({
  142. initialize: function(geojson, options) {
  143. this._callLevel = 0;
  144. L.GeoJSON.prototype.initialize.call(this, geojson, options);
  145. },
  146. addData: function(geojson) {
  147. var crs;
  148. if (geojson) {
  149. if (geojson.crs && geojson.crs.type === 'name') {
  150. crs = new L.Proj.CRS(geojson.crs.properties.name);
  151. } else if (geojson.crs && geojson.crs.type) {
  152. crs = new L.Proj.CRS(geojson.crs.type + ':' + geojson.crs.properties.code);
  153. }
  154. if (crs !== undefined) {
  155. this.options.coordsToLatLng = function(coords) {
  156. var point = L.point(coords[0], coords[1]);
  157. return crs.projection.unproject(point);
  158. };
  159. }
  160. }
  161. // Base class' addData might call us recursively, but
  162. // CRS shouldn't be cleared in that case, since CRS applies
  163. // to the whole GeoJSON, inluding sub-features.
  164. this._callLevel++;
  165. try {
  166. L.GeoJSON.prototype.addData.call(this, geojson);
  167. } finally {
  168. this._callLevel--;
  169. if (this._callLevel === 0) {
  170. delete this.options.coordsToLatLng;
  171. }
  172. }
  173. }
  174. });
  175. L.Proj.geoJson = function(geojson, options) {
  176. return new L.Proj.GeoJSON(geojson, options);
  177. };
  178. L.Proj.ImageOverlay = L.ImageOverlay.extend({
  179. initialize: function (url, bounds, options) {
  180. L.ImageOverlay.prototype.initialize.call(this, url, null, options);
  181. this._projectedBounds = bounds;
  182. },
  183. // Danger ahead: Overriding internal methods in Leaflet.
  184. // Decided to do this rather than making a copy of L.ImageOverlay
  185. // and doing very tiny modifications to it.
  186. // Future will tell if this was wise or not.
  187. _animateZoom: function (event) {
  188. var scale = this._map.getZoomScale(event.zoom);
  189. var northWest = L.point(this._projectedBounds.min.x, this._projectedBounds.max.y);
  190. var offset = this._projectedToNewLayerPoint(northWest, event.zoom, event.center);
  191. L.DomUtil.setTransform(this._image, offset, scale);
  192. },
  193. _reset: function () {
  194. var zoom = this._map.getZoom();
  195. var pixelOrigin = this._map.getPixelOrigin();
  196. var bounds = L.bounds(
  197. this._transform(this._projectedBounds.min, zoom)._subtract(pixelOrigin),
  198. this._transform(this._projectedBounds.max, zoom)._subtract(pixelOrigin)
  199. );
  200. var size = bounds.getSize();
  201. L.DomUtil.setPosition(this._image, bounds.min);
  202. this._image.style.width = size.x + 'px';
  203. this._image.style.height = size.y + 'px';
  204. },
  205. _projectedToNewLayerPoint: function (point, zoom, center) {
  206. var viewHalf = this._map.getSize()._divideBy(2);
  207. var newTopLeft = this._map.project(center, zoom)._subtract(viewHalf)._round();
  208. var topLeft = newTopLeft.add(this._map._getMapPanePos());
  209. return this._transform(point, zoom)._subtract(topLeft);
  210. },
  211. _transform: function (point, zoom) {
  212. var crs = this._map.options.crs;
  213. var transformation = crs.transformation;
  214. var scale = crs.scale(zoom);
  215. return transformation.transform(point, scale);
  216. }
  217. });
  218. L.Proj.imageOverlay = function (url, bounds, options) {
  219. return new L.Proj.ImageOverlay(url, bounds, options);
  220. };
  221. return L.Proj;
  222. }));