(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) { L.Proj = {}; L.Proj._isProj4Obj = function(a) { return (typeof a.inverse !== 'undefined' && typeof a.forward !== 'undefined'); }; L.Proj.ScaleDependantTransformation = function(scaleTransforms) { this.scaleTransforms = scaleTransforms; }; L.Proj.ScaleDependantTransformation.prototype.transform = function(point, scale) { return this.scaleTransforms[scale].transform(point, scale); }; L.Proj.ScaleDependantTransformation.prototype.untransform = function(point, scale) { return this.scaleTransforms[scale].untransform(point, scale); }; L.Proj.Projection = L.Class.extend({ initialize: function(a, def) { if (L.Proj._isProj4Obj(a)) { this._proj = a; } else { var code = a; 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; } } this._proj = proj4(code); } }, 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); } }); 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); } else { code = a; def = b; options = c || {}; this.projection = new L.Proj.Projection(code, def); } 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]; } } } }, scale: function(zoom) { var iZoom = Math.floor(zoom), baseScale, nextScale, scaleDiff, 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; } }, getSize: function(zoom) { var b = this.options.bounds, s, min, max; if (b) { s = this.scale(zoom); min = this.transformation.transform(b.min, s); max = this.transformation.transform(b.max, s); return L.point(Math.abs(max.x - min.x), Math.abs(max.y - min.y)); } else { // Backwards compatibility with Leaflet < 0.7 s = 256 * Math.pow(2, zoom); return L.point(s, s); } } }); L.Proj.CRS.TMS = L.Proj.CRS.extend({ options: { tileSize: 256 }, initialize: function(a, b, c, d) { var code, def, proj, projectedBounds, options; if (L.Proj._isProj4Obj(a)) { proj = a; projectedBounds = b; options = c || {}; options.origin = [projectedBounds[0], projectedBounds[3]]; L.Proj.CRS.prototype.initialize.call(this, proj, options); } else { code = a; def = b; projectedBounds = c; options = d || {}; options.origin = [projectedBounds[0], projectedBounds[3]]; L.Proj.CRS.prototype.initialize.call(this, code, def, options); } this.projectedBounds = projectedBounds; this._sizes = this._calculateSizes(); }, _calculateSizes: function() { var sizes = [], crsBounds = this.projectedBounds, projectedTileSize, i, x, y; for (i = this._scales.length - 1; i >= 0; i--) { if (this._scales[i]) { projectedTileSize = this.options.tileSize / this._scales[i]; // to prevent very small rounding errors from causing us to round up, // cut any decimals after 3rd before rounding up. x = Math.ceil(parseFloat((crsBounds[2] - crsBounds[0]) / projectedTileSize).toPrecision(3)) * projectedTileSize * this._scales[i]; y = Math.ceil(parseFloat((crsBounds[3] - crsBounds[1]) / projectedTileSize).toPrecision(3)) * projectedTileSize * this._scales[i]; sizes[i] = L.point(x, y); } } return sizes; }, getSize: function(zoom) { return this._sizes[zoom]; } }); L.Proj.TileLayer = {}; // Note: deprecated and not necessary since 0.7, will be removed L.Proj.TileLayer.TMS = L.TileLayer.extend({ options: { continuousWorld: true }, initialize: function(urlTemplate, crs, options) { var boundsMatchesGrid = true, scaleTransforms, upperY, crsBounds, i; if (!(crs instanceof L.Proj.CRS.TMS)) { throw 'CRS is not L.Proj.CRS.TMS.'; } L.TileLayer.prototype.initialize.call(this, urlTemplate, options); // Enabling tms will cause Leaflet to also try to do TMS, which will // break (at least prior to 0.7.0). Actively disable it, to prevent // well-meaning users from shooting themselves in the foot. this.options.tms = false; this.crs = crs; crsBounds = this.crs.projectedBounds; // Verify grid alignment for (i = this.options.minZoom; i < this.options.maxZoom && boundsMatchesGrid; i++) { var gridHeight = (crsBounds[3] - crsBounds[1]) / this._projectedTileSize(i); boundsMatchesGrid = Math.abs(gridHeight - Math.round(gridHeight)) > 1e-3; } if (!boundsMatchesGrid) { scaleTransforms = {}; for (i = this.options.minZoom; i < this.options.maxZoom; i++) { upperY = crsBounds[1] + Math.ceil((crsBounds[3] - crsBounds[1]) / this._projectedTileSize(i)) * this._projectedTileSize(i); scaleTransforms[this.crs.scale(i)] = new L.Transformation(1, -crsBounds[0], -1, upperY); } this.crs = new L.Proj.CRS.TMS(this.crs.projection._proj, crsBounds, this.crs.options); this.crs.transformation = new L.Proj.ScaleDependantTransformation(scaleTransforms); } }, getTileUrl: function(tilePoint) { var zoom = this._map.getZoom(), gridHeight = Math.ceil( (this.crs.projectedBounds[3] - this.crs.projectedBounds[1]) / this._projectedTileSize(zoom)); return L.Util.template(this._url, L.Util.extend({ s: this._getSubdomain(tilePoint), z: this._getZoomForUrl(), x: tilePoint.x, y: gridHeight - tilePoint.y - 1 }, this.options)); }, _projectedTileSize: function(zoom) { return (this.options.tileSize / this.crs.scale(zoom)); } }); L.Proj.GeoJSON = L.GeoJSON.extend({ initialize: function(geojson, options) { this._callLevel = 0; L.GeoJSON.prototype.initialize.call(this, null, options); if (geojson) { this.addData(geojson); } }, 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._projBounds = bounds; }, /* Danger ahead: overriding internal methods in Leaflet. I've decided to do this rather than making a copy of L.ImageOverlay and making very tiny modifications to it. Future will tell if this was wise or not. */ _animateZoom: function (e) { var northwest = L.point(this._projBounds.min.x, this._projBounds.max.y), southeast = L.point(this._projBounds.max.x, this._projBounds.min.y), topLeft = this._projectedToNewLayerPoint(northwest, e.zoom, e.center), size = this._projectedToNewLayerPoint(southeast, e.zoom, e.center).subtract(topLeft), origin = topLeft.add(size._multiplyBy((1 - 1 / e.scale) / 2)); this._image.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(origin) + ' scale(' + this._map.getZoomScale(e.zoom) + ') '; }, _reset: function() { var zoom = this._map.getZoom(), pixelOrigin = this._map.getPixelOrigin(), bounds = L.bounds(this._transform(this._projBounds.min, zoom)._subtract(pixelOrigin), this._transform(this._projBounds.max, zoom)._subtract(pixelOrigin)), size = bounds.getSize(), image = this._image; L.DomUtil.setPosition(image, bounds.min); image.style.width = size.x + 'px'; image.style.height = size.y + 'px'; }, _projectedToNewLayerPoint: function (point, newZoom, newCenter) { var topLeft = this._map._getNewTopLeftPoint(newCenter, newZoom).add(this._map._getMapPanePos()); return this._transform(point, newZoom)._subtract(topLeft); }, _transform: function(p, zoom) { var crs = this._map.options.crs, transformation = crs.transformation, scale = crs.scale(zoom); return transformation.transform(p, scale); } }); L.Proj.imageOverlay = function(url, bounds, options) { return new L.Proj.ImageOverlay(url, bounds, options); }; if (typeof L.CRS !== 'undefined') { // This is left here for backwards compatibility L.CRS.proj4js = (function () { return function (code, def, transformation, options) { options = options || {}; if (transformation) { options.transformation = transformation; } return new L.Proj.CRS(code, def, options); }; }()); } return L.Proj; }));