proj4leaflet.js 7.3 KB

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