(function(factory) { var L, proj4; if (typeof define === 'function' && define.amd) { // AMD define(['leaflet', 'proj4'], factory); } else if (typeof module === 'object' && typeof module.exports === 'object') { // Node/CommonJS L = require('leaflet'); proj4 = require('proj4'); module.exports = factory(L, proj4); } else { // Browser globals if (typeof window.L === 'undefined' || typeof window.proj4 === 'undefined') { throw 'Leaflet and proj4 must be loaded first'; } factory(window.L, window.proj4); } }(function(L, proj4) { if (proj4.__esModule && proj4.default) { // If proj4 was bundled as an ES6 module, unwrap it to get // to the actual main proj4 object. // See discussion in https://github.com/kartena/Proj4Leaflet/pull/147 proj4 = proj4.default; } L.Proj = {}; L.Proj._isProj4Obj = function(a) { return (typeof a.inverse !== 'undefined' && typeof a.forward !== 'undefined'); }; L.Proj.Projection = L.Class.extend({ initialize: function(code, def, bounds) { var isP4 = L.Proj._isProj4Obj(code); this._proj = isP4 ? code : this._projFromCodeDef(code, def); this.bounds = isP4 ? def : bounds; }, project: function(latlng) { var point = this._proj.forward([latlng.lng, latlng.lat]); return new L.Point(point[0], point[1]); }, unproject: function(point, unbounded) { var point2 = this._proj.inverse([point.x, point.y]); return new L.LatLng(point2[1], point2[0], unbounded); }, _projFromCodeDef: function(code, def) { if (def) { proj4.defs(code, def); } else if (proj4.defs[code] === undefined) { var urn = code.split(':'); if (urn.length > 3) { code = urn[urn.length - 3] + ':' + urn[urn.length - 1]; } if (proj4.defs[code] === undefined) { throw 'No projection definition for code ' + code; } } return proj4(code); } }); L.Proj.CRS = L.Class.extend({ includes: L.CRS, options: { transformation: new L.Transformation(1, 0, -1, 0) }, initialize: function(a, b, c) { var code, proj, def, options; if (L.Proj._isProj4Obj(a)) { proj = a; code = proj.srsCode; options = b || {}; this.projection = new L.Proj.Projection(proj, options.bounds); } else { code = a; def = b; options = c || {}; this.projection = new L.Proj.Projection(code, def, options.bounds); } L.Util.setOptions(this, options); this.code = code; this.transformation = this.options.transformation; if (this.options.origin) { this.transformation = new L.Transformation(1, -this.options.origin[0], -1, this.options.origin[1]); } if (this.options.scales) { this._scales = this.options.scales; } else if (this.options.resolutions) { this._scales = []; for (var i = this.options.resolutions.length - 1; i >= 0; i--) { if (this.options.resolutions[i]) { this._scales[i] = 1 / this.options.resolutions[i]; } } } this.infinite = !this.options.bounds; }, scale: function(zoom) { var iZoom = Math.floor(zoom); var baseScale; var nextScale; var scaleDiff; var zDiff; if (zoom === iZoom) { return this._scales[zoom]; } else { // Non-integer zoom, interpolate baseScale = this._scales[iZoom]; nextScale = this._scales[iZoom + 1]; scaleDiff = nextScale - baseScale; zDiff = (zoom - iZoom); return baseScale + scaleDiff * zDiff; } }, zoom: function(scale) { // Find closest number in this._scales, down var downScale = this._closestElement(this._scales, scale); var downZoom = this._scales.indexOf(downScale); var nextScale; var nextZoom; var scaleDiff; // Check if scale is downScale => return array index if (scale === downScale) { return downZoom; } if (downScale === undefined) { return -Infinity; } // Interpolate nextZoom = downZoom + 1; nextScale = this._scales[nextZoom]; if (nextScale === undefined) { return Infinity; } scaleDiff = nextScale - downScale; return (scale - downScale) / scaleDiff + downZoom; }, distance: L.CRS.Earth.distance, R: L.CRS.Earth.R, /* Get the closest lowest element in an array */ _closestElement: function(array, element) { var low; for (var i = array.length; i--;) { if (array[i] <= element && (low === undefined || low < array[i])) { low = array[i]; } } return low; } }); L.Proj.GeoJSON = L.GeoJSON.extend({ initialize: function(geojson, options) { this._callLevel = 0; L.GeoJSON.prototype.initialize.call(this, geojson, options); }, addData: function(geojson) { var crs; if (geojson) { if (geojson.crs && geojson.crs.type === 'name') { crs = new L.Proj.CRS(geojson.crs.properties.name); } else if (geojson.crs && geojson.crs.type) { crs = new L.Proj.CRS(geojson.crs.type + ':' + geojson.crs.properties.code); } if (crs !== undefined) { this.options.coordsToLatLng = function(coords) { var point = L.point(coords[0], coords[1]); return crs.projection.unproject(point); }; } } // Base class' addData might call us recursively, but // CRS shouldn't be cleared in that case, since CRS applies // to the whole GeoJSON, inluding sub-features. this._callLevel++; try { L.GeoJSON.prototype.addData.call(this, geojson); } finally { this._callLevel--; if (this._callLevel === 0) { delete this.options.coordsToLatLng; } } } }); L.Proj.geoJson = function(geojson, options) { return new L.Proj.GeoJSON(geojson, options); }; L.Proj.ImageOverlay = L.ImageOverlay.extend({ initialize: function(url, bounds, options) { L.ImageOverlay.prototype.initialize.call(this, url, null, options); this._projectedBounds = bounds; }, // Danger ahead: Overriding internal methods in Leaflet. // Decided to do this rather than making a copy of L.ImageOverlay // and doing very tiny modifications to it. // Future will tell if this was wise or not. _animateZoom: function(event) { var scale = this._map.getZoomScale(event.zoom); var northWest = L.point(this._projectedBounds.min.x, this._projectedBounds.max.y); var offset = this._projectedToNewLayerPoint(northWest, event.zoom, event.center); L.DomUtil.setTransform(this._image, offset, scale); }, _reset: function() { var zoom = this._map.getZoom(); var pixelOrigin = this._map.getPixelOrigin(); var bounds = L.bounds( this._transform(this._projectedBounds.min, zoom)._subtract(pixelOrigin), this._transform(this._projectedBounds.max, zoom)._subtract(pixelOrigin) ); var size = bounds.getSize(); L.DomUtil.setPosition(this._image, bounds.min); this._image.style.width = size.x + 'px'; this._image.style.height = size.y + 'px'; }, _projectedToNewLayerPoint: function(point, zoom, center) { var viewHalf = this._map.getSize()._divideBy(2); var newTopLeft = this._map.project(center, zoom)._subtract(viewHalf)._round(); var topLeft = newTopLeft.add(this._map._getMapPanePos()); return this._transform(point, zoom)._subtract(topLeft); }, _transform: function(point, zoom) { var crs = this._map.options.crs; var transformation = crs.transformation; var scale = crs.scale(zoom); return transformation.transform(point, scale); } }); L.Proj.imageOverlay = function(url, bounds, options) { return new L.Proj.ImageOverlay(url, bounds, options); }; return L.Proj; }));