From e7410e6e032ad756ee6a8794b93f982034f987e8 Mon Sep 17 00:00:00 2001 From: Mourner Date: Tue, 31 May 2011 12:36:05 +0300 Subject: [PATCH] improve projections support, add EPSG:3395 (real mercator) --- CHANGELOG.md | 2 +- build/Makefile | 10 ++++++-- build/build.bat | 10 ++++++-- build/deps.js | 17 ++++++++++--- debug/include.js | 12 +++++++-- debug/map/wms-marble.html | 4 +-- dist/leaflet.js | 9 +++---- src/geo/CRS.js | 36 -------------------------- src/geo/Projection.js | 43 -------------------------------- src/geo/crs/CRS.EPSG3395.js | 13 ++++++++++ src/geo/crs/CRS.EPSG3857.js | 17 +++++++++++++ src/geo/crs/CRS.EPSG4326.js | 7 ++++++ src/geo/crs/CRS.js | 17 +++++++++++++ src/geo/projection/Projection.js | 5 ++++ src/map/Map.js | 2 +- 15 files changed, 106 insertions(+), 98 deletions(-) delete mode 100644 src/geo/CRS.js delete mode 100644 src/geo/Projection.js create mode 100644 src/geo/crs/CRS.EPSG3395.js create mode 100644 src/geo/crs/CRS.EPSG3857.js create mode 100644 src/geo/crs/CRS.EPSG4326.js create mode 100644 src/geo/crs/CRS.js create mode 100644 src/geo/projection/Projection.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 05a3a082..0f2f3315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Leaflet Changelog ## 0.2 (master) * Added **WMS support** (`TileLayer.WMS`). - * Improved different projections support (through `crs` option in `Map`). + * Improved **different projections support**, having `EPSG:3857`, `EPSG:4326` and `EPSG:3395` out of the box (through `crs` option in `Map`). * Added `TileLayer.Canvas` for easy creation of canvas-based tile layers. * `Circle` is now zoom-dependent (with radius in meters); circle of a permanent size is now called `L.CircleMarker`. * Added `mouseover` and `mouseout` events to map, markers and paths; added map `mousemove` event. diff --git a/build/Makefile b/build/Makefile index 8b3ddcd0..4c6b699a 100644 --- a/build/Makefile +++ b/build/Makefile @@ -19,8 +19,14 @@ --js ../src/dom/transition/Transition.Timer.js \ --js ../src/geo/LatLng.js \ --js ../src/geo/LatLngBounds.js \ - --js ../src/geo/Projection.js \ - --js ../src/geo/CRS.js \ + --js ../src/geo/projection/Projection.js \ + --js ../src/geo/projection/Projection.SphericalMercator.js \ + --js ../src/geo/projection/Projection.LonLat.js \ + --js ../src/geo/projection/Projection.Mercator.js \ + --js ../src/geo/crs/CRS.js \ + --js ../src/geo/crs/CRS.EPSG3857.js \ + --js ../src/geo/crs/CRS.EPSG4326.js \ + --js ../src/geo/crs/CRS.EPSG3395.js \ --js ../src/layer/tile/TileLayer.js \ --js ../src/layer/tile/TileLayer.WMS.js \ --js ../src/layer/tile/TileLayer.Canvas.js \ diff --git a/build/build.bat b/build/build.bat index e6cc66bd..84e60d9e 100644 --- a/build/build.bat +++ b/build/build.bat @@ -19,8 +19,14 @@ java -jar ../lib/closure-compiler/compiler.jar ^ --js ../src/dom/transition/Transition.Timer.js ^ --js ../src/geo/LatLng.js ^ --js ../src/geo/LatLngBounds.js ^ ---js ../src/geo/Projection.js ^ ---js ../src/geo/CRS.js ^ +--js ../src/geo/projection/Projection.js ^ +--js ../src/geo/projection/Projection.SphericalMercator.js ^ +--js ../src/geo/projection/Projection.LonLat.js ^ +--js ../src/geo/projection/Projection.Mercator.js ^ +--js ../src/geo/crs/CRS.js ^ +--js ../src/geo/crs/CRS.EPSG3857.js ^ +--js ../src/geo/crs/CRS.EPSG4326.js ^ +--js ../src/geo/crs/CRS.EPSG3395.js ^ --js ../src/layer/tile/TileLayer.js ^ --js ../src/layer/tile/TileLayer.WMS.js ^ --js ../src/layer/tile/TileLayer.Canvas.js ^ diff --git a/build/deps.js b/build/deps.js index a47788bf..8c7e3407 100644 --- a/build/deps.js +++ b/build/deps.js @@ -8,16 +8,27 @@ var deps = { 'dom/DomUtil.js', 'geo/LatLng.js', 'geo/LatLngBounds.js', - 'geo/Projection.js', - 'geo/CRS.js', + 'geo/projection/Projection.js', + 'geo/projection/Projection.SphericalMercator.js', + 'geo/projection/Projection.LonLat.js', + 'geo/crs/CRS.js', + 'geo/crs/CRS.EPSG3857.js', + 'geo/crs/CRS.EPSG4326.js', 'geometry/Bounds.js', 'geometry/Point.js', 'geometry/Transformation.js', 'map/Map.js'], - desc: 'The core of the library, including OOP, events, DOM facilities, basic units, projections and the base Map class.' + desc: 'The core of the library, including OOP, events, DOM facilities, basic units, projections (EPSG:3857 and EPSG:4326) and the base Map class.' }, + EPSG3395: { + src: ['geo/projection/Projection.Mercator.js', + 'geo/crs/CRS.EPSG3395.js'], + desc: 'EPSG:3395 projection (used by some map providers).', + heading: 'Additional projections' + }, + TileLayer: { src: ['layer/tile/TileLayer.js'], desc: 'The base class for displaying tile layers on the map.', diff --git a/debug/include.js b/debug/include.js index 34f1131f..a8a89ba4 100644 --- a/debug/include.js +++ b/debug/include.js @@ -25,8 +25,16 @@ 'geo/LatLng.js', 'geo/LatLngBounds.js', - 'geo/Projection.js', - 'geo/CRS.js', + + 'geo/projection/Projection.js', + 'geo/projection/Projection.SphericalMercator.js', + 'geo/projection/Projection.LonLat.js', + 'geo/projection/Projection.Mercator.js', + + 'geo/crs/CRS.js', + 'geo/crs/CRS.EPSG3857.js', + 'geo/crs/CRS.EPSG4326.js', + 'geo/crs/CRS.EPSG3395.js', 'layer/tile/TileLayer.js', 'layer/tile/TileLayer.WMS.js', diff --git a/debug/map/wms-marble.html b/debug/map/wms-marble.html index 6b6147ed..e1044081 100644 --- a/debug/map/wms-marble.html +++ b/debug/map/wms-marble.html @@ -15,7 +15,7 @@
diff --git a/dist/leaflet.js b/dist/leaflet.js index c71dc879..e8773c94 100644 --- a/dist/leaflet.js +++ b/dist/leaflet.js @@ -33,9 +33,8 @@ new L.Point(0,0);this._p2=new L.Point(c[0],c[1]);this._p3=new L.Point(c[2],c[3]) this._timer=setInterval(this._step,this._interval);this._startTime=L.Transition.getTime()},_step:function(){var a=L.Transition.getTime()-this._startTime,b=this.options.duration*1E3;a=b.lat&&a.lat<=c.lat&&d.lng>=b.lng&&a.lng<=c.lng}});L.Projection={};L.Projection.Mercator={MAX_LATITUDE:function(){var a=Math.exp(2*Math.PI);return Math.asin((a-1)/(a+1))*L.LatLng.RAD_TO_DEG}(),project:function(a){var b=L.LatLng.DEG_TO_RAD,c=L.Projection.Mercator.MAX_LATITUDE,d=a.lng*b,a=Math.max(Math.min(c,a.lat),-c)*b,a=Math.log(Math.tan(Math.PI/4+a/2));return new L.Point(d,a)},unproject:function(a,b){var c=L.LatLng.RAD_TO_DEG;return new L.LatLng((2*Math.atan(Math.exp(a.y))-Math.PI/2)*c,a.x*c,b)}}; -L.Projection.LonLat={project:function(a){return new L.Point(a.lng,a.lat)},unproject:function(a,b){return new L.LatLng(a.y,a.x,b)}};L.CRS=L.Class.extend({latLngToPoint:function(a,b){return this.transformation._transform(this.projection.project(a),b)},pointToLatLng:function(a,b,c){return this.projection.unproject(this.transformation.untransform(a,b),c)},project:L.Projection.LonLat.project});L.CRS.EPSG4326=L.CRS.extend({code:"EPSG:4326",projection:L.Projection.LonLat,transformation:new L.Transformation(1/360,0.5,-1/360,0.5)}); -L.CRS.EPSG3857=L.CRS.extend({code:"EPSG:3857",projection:L.Projection.Mercator,transformation:new L.Transformation(0.5/Math.PI,0.5,-0.5/Math.PI,0.5),project:function(a){return this.projection.project(a).multiplyBy(6378137)}});L.CRS.EPSG3395=L.CRS.EPSG3857.extend({code:"EPSG:3395"});L.TileLayer=L.Class.extend({includes:L.Mixin.Events,options:{minZoom:0,maxZoom:18,tileSize:256,subdomains:"abc",errorTileUrl:"",attribution:"",opacity:1,scheme:"xyz",unloadInvisibleTiles:L.Browser.mobileWebkit,updateWhenIdle:L.Browser.mobileWebkit},initialize:function(a,b){L.Util.setOptions(this,b);this._url=a;if(typeof this.options.subdomains=="string")this.options.subdomains=this.options.subdomains.split("")},onAdd:function(a){this._map=a;this._initContainer();this._createTileProto();a.on("viewreset", +a=a.getNorthEast()):d=a;return d.lat>=b.lat&&a.lat<=c.lat&&d.lng>=b.lng&&a.lng<=c.lng}});L.Projection={};L.Projection.SphericalMercator={MAX_LATITUDE:85.0511287798,project:function(a){var b=L.LatLng.DEG_TO_RAD,c=this.MAX_LATITUDE,d=a.lng*b,a=Math.max(Math.min(c,a.lat),-c)*b,a=Math.log(Math.tan(Math.PI/4+a/2));return new L.Point(d,a)},unproject:function(a,b){var c=L.LatLng.RAD_TO_DEG;return new L.LatLng((2*Math.atan(Math.exp(a.y))-Math.PI/2)*c,a.x*c,b)}};L.Projection.LonLat={project:function(a){return new L.Point(a.lng,a.lat)},unproject:function(a,b){return new L.LatLng(a.y,a.x,b)}};L.Projection.Mercator={MAX_LATITUDE:85.0840591556,R_MINOR:6356752.3142,R_MAJOR:6378137,project:function(a){var b=L.LatLng.DEG_TO_RAD,c=this.MAX_LATITUDE,d=this.R_MAJOR,e=a.lng*b*d,a=Math.max(Math.min(c,a.lat),-c)*b,b=this.R_MINOR/d,b=Math.sqrt(1-b*b),c=b*Math.sin(a),c=Math.pow((1-c)/(1+c),b*0.5),a=-d*Math.log(Math.tan(0.5*(Math.PI*0.5-a))/c);return new L.Point(e,a)},unproject:function(a,b){for(var c=L.LatLng.RAD_TO_DEG,d=this.R_MAJOR,e=a.x*c/d,f=this.R_MINOR/d,f=Math.sqrt(1-f*f),d=Math.exp(-a.y/d), +g=Math.PI/2-2*Math.atan(d),h=15,i=0.1;Math.abs(i)>1.0E-7&&--h>0;)i=f*Math.sin(g),i=Math.PI/2-2*Math.atan(d*Math.pow((1-i)/(1+i),0.5*f))-g,g+=i;return new L.LatLng(g*c,e,b)}};L.CRS={latLngToPoint:function(a,b){return this.transformation._transform(this.projection.project(a),b)},pointToLatLng:function(a,b,c){return this.projection.unproject(this.transformation.untransform(a,b),c)},project:function(a){return this.projection.project(a)}};L.CRS.EPSG3857=L.Util.extend({},L.CRS,{code:"EPSG:3857",projection:L.Projection.SphericalMercator,transformation:new L.Transformation(0.5/Math.PI,0.5,-0.5/Math.PI,0.5),project:function(a){return this.projection.project(a).multiplyBy(6378137)}});L.CRS.EPSG900913=L.Util.extend({},L.CRS.EPSG3857,{code:"EPSG:900913"});L.CRS.EPSG4326=L.Util.extend({},L.CRS,{code:"EPSG:4326",projection:L.Projection.LonLat,transformation:new L.Transformation(1/360,0.5,-1/360,0.5)});L.CRS.EPSG3395=L.Util.extend({},L.CRS,{code:"EPSG:3395",projection:L.Projection.Mercator,transformation:function(){var a=L.Projection.Mercator;return new L.Transformation(0.5/(Math.PI*a.R_MAJOR),0.5,-0.5/(Math.PI*a.R_MINOR),0.5)}()});L.TileLayer=L.Class.extend({includes:L.Mixin.Events,options:{minZoom:0,maxZoom:18,tileSize:256,subdomains:"abc",errorTileUrl:"",attribution:"",opacity:1,scheme:"xyz",unloadInvisibleTiles:L.Browser.mobileWebkit,updateWhenIdle:L.Browser.mobileWebkit},initialize:function(a,b){L.Util.setOptions(this,b);this._url=a;if(typeof this.options.subdomains=="string")this.options.subdomains=this.options.subdomains.split("")},onAdd:function(a){this._map=a;this._initContainer();this._createTileProto();a.on("viewreset", this._reset,this);if(this.options.updateWhenIdle)a.on("moveend",this._update,this);else this._limitedUpdate=L.Util.limitExecByInterval(this._update,100,this),a.on("move",this._limitedUpdate,this);this._reset();this._update()},onRemove:function(){this._map.getPanes().tilePane.removeChild(this._container);this._map.off("viewreset",this._reset);this.options.updateWhenIdle?this._map.off("moveend",this._update):this._map.off("move",this._limitedUpdate)},getAttribution:function(){return this.options.attribution}, _initContainer:function(){var a=this._map.getPanes().tilePane;if(!this._container||a.empty)this._container=L.DomUtil.create("div","leaflet-layer",a),this.options.opacity<1&&L.DomUtil.setOpacity(this._container,this.options.opacity)},_reset:function(){this._tiles={};this._initContainer();this._container.innerHTML=""},_update:function(){var a=this._map.getPixelBounds(),b=this.options.tileSize,c=new L.Point(Math.floor(a.min.x/b),Math.floor(a.min.y/b)),a=new L.Point(Math.floor(a.max.x/b),Math.floor(a.max.y/ b)),c=new L.Bounds(c,a);this._addTilesFromCenterOut(c);this.options.unloadInvisibleTiles&&this._removeOtherTiles(c)},_addTilesFromCenterOut:function(a){for(var b=[],c=a.getCenter(),d=a.min.y;d<=a.max.y;d++)for(var e=a.min.x;e<=a.max.x;e++)e+":"+d in this._tiles||b.push(new L.Point(e,d));b.sort(function(a,b){return a.distanceTo(c)-b.distanceTo(c)});this._tilesToLoad=b.length;a=0;for(d=this._tilesToLoad;aLeaflet';this._attributions={};this._update()},getPosition:function(){return L.Control.Position.BOTTOM_RIGHT},getContainer:function(){return this._container},setPrefix:function(a){this._prefix=a},addAttribution:function(a){a&&(this._attributions[a]=!0,this._update())},removeAttribution:function(a){a&& -(delete this._attributions[a],this._update())},_update:function(){if(this._map){var a=[],b;for(b in this._attributions)this._attributions.hasOwnProperty(b)&&a.push(b);this._container.innerHTML=[this._prefix,a.join(", ")].join(" — ")}}});L.Map=L.Class.extend({includes:L.Mixin.Events,options:{crs:new L.CRS.EPSG3857,scale:function(a){return 256*(1< Point*/ { - var projectedPoint = this.projection.project(latlng); - return this.transformation._transform(projectedPoint, scale); - }, - - pointToLatLng: function(/*Point*/ point, /*Number*/ scale, /*(optional) Boolean*/ unbounded)/*-> LatLng*/ { - var untransformedPoint = this.transformation.untransform(point, scale); - return this.projection.unproject(untransformedPoint, unbounded); - //TODO get rid of 'unbounded' everywhere - }, - - project: L.Projection.LonLat.project -}); - -L.CRS.EPSG4326 = L.CRS.extend({ - code: 'EPSG:4326', - projection: L.Projection.LonLat, - transformation: new L.Transformation(1/360, 0.5, -1/360, 0.5) -}); - -L.CRS.EPSG3857 = L.CRS.extend({ - code: 'EPSG:3857', - projection: L.Projection.Mercator, - transformation: new L.Transformation(0.5/Math.PI, 0.5, -0.5/Math.PI, 0.5), - - project: function(/*LatLng*/ latlng)/*-> Point*/ { - var projectedPoint = this.projection.project(latlng), - earthRadius = 6378137; - return projectedPoint.multiplyBy(earthRadius); - } -}); - -L.CRS.EPSG3395 = L.CRS.EPSG3857.extend({ - code: 'EPSG:3395' -}); \ No newline at end of file diff --git a/src/geo/Projection.js b/src/geo/Projection.js deleted file mode 100644 index af3cbfe1..00000000 --- a/src/geo/Projection.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * L.Projection contains various geographical projections. - */ - -L.Projection = {}; - -//Mercator Projection - see http://en.wikipedia.org/wiki/Mercator_projection -L.Projection.Mercator = { - MAX_LATITUDE: (function() { - var a = Math.exp(2 * Math.PI); - return Math.asin((a - 1)/(a + 1)) * L.LatLng.RAD_TO_DEG; - })(), - - project: function(/*LatLng*/ latlng) /*-> Point*/ { - var d = L.LatLng.DEG_TO_RAD, - max = L.Projection.Mercator.MAX_LATITUDE, - lat = Math.max(Math.min(max, latlng.lat), -max), - x = latlng.lng * d, - y = lat * d; - y = Math.log(Math.tan(Math.PI/4 + y/2)); - - return new L.Point(x, y); - }, - - unproject: function(/*Point*/ point, /*Boolean*/ unbounded) /*-> LatLng*/ { - var d = L.LatLng.RAD_TO_DEG, - lng = point.x * d, - lat = (2 * Math.atan(Math.exp(point.y)) - Math.PI/2) * d; - - return new L.LatLng(lat, lng, unbounded); - } -}; - - -L.Projection.LonLat = { - project: function(latlng) { - return new L.Point(latlng.lng, latlng.lat); - }, - - unproject: function(point, unbounded) { - return new L.LatLng(point.y, point.x, unbounded); - } -}; \ No newline at end of file diff --git a/src/geo/crs/CRS.EPSG3395.js b/src/geo/crs/CRS.EPSG3395.js new file mode 100644 index 00000000..232a040b --- /dev/null +++ b/src/geo/crs/CRS.EPSG3395.js @@ -0,0 +1,13 @@ + +L.CRS.EPSG3395 = L.Util.extend({}, L.CRS, { + code: 'EPSG:3395', + + projection: L.Projection.Mercator, + transformation: (function() { + var m = L.Projection.Mercator, + r = m.R_MAJOR, + r2 = m.R_MINOR; + + return new L.Transformation(0.5/(Math.PI * r), 0.5, -0.5/(Math.PI * r2), 0.5); + })() +}); \ No newline at end of file diff --git a/src/geo/crs/CRS.EPSG3857.js b/src/geo/crs/CRS.EPSG3857.js new file mode 100644 index 00000000..cbdbd03a --- /dev/null +++ b/src/geo/crs/CRS.EPSG3857.js @@ -0,0 +1,17 @@ + +L.CRS.EPSG3857 = L.Util.extend({}, L.CRS, { + code: 'EPSG:3857', + + projection: L.Projection.SphericalMercator, + transformation: new L.Transformation(0.5/Math.PI, 0.5, -0.5/Math.PI, 0.5), + + project: function(/*LatLng*/ latlng)/*-> Point*/ { + var projectedPoint = this.projection.project(latlng), + earthRadius = 6378137; + return projectedPoint.multiplyBy(earthRadius); + } +}); + +L.CRS.EPSG900913 = L.Util.extend({}, L.CRS.EPSG3857, { + code: 'EPSG:900913' +}); \ No newline at end of file diff --git a/src/geo/crs/CRS.EPSG4326.js b/src/geo/crs/CRS.EPSG4326.js new file mode 100644 index 00000000..1550718d --- /dev/null +++ b/src/geo/crs/CRS.EPSG4326.js @@ -0,0 +1,7 @@ + +L.CRS.EPSG4326 = L.Util.extend({}, L.CRS, { + code: 'EPSG:4326', + + projection: L.Projection.LonLat, + transformation: new L.Transformation(1/360, 0.5, -1/360, 0.5) +}); \ No newline at end of file diff --git a/src/geo/crs/CRS.js b/src/geo/crs/CRS.js new file mode 100644 index 00000000..220ad42f --- /dev/null +++ b/src/geo/crs/CRS.js @@ -0,0 +1,17 @@ + +L.CRS = { + latLngToPoint: function(/*LatLng*/ latlng, /*Number*/ scale)/*-> Point*/ { + var projectedPoint = this.projection.project(latlng); + return this.transformation._transform(projectedPoint, scale); + }, + + pointToLatLng: function(/*Point*/ point, /*Number*/ scale, /*(optional) Boolean*/ unbounded)/*-> LatLng*/ { + var untransformedPoint = this.transformation.untransform(point, scale); + return this.projection.unproject(untransformedPoint, unbounded); + //TODO get rid of 'unbounded' everywhere + }, + + project: function(latlng) { + return this.projection.project(latlng); + } +}; \ No newline at end of file diff --git a/src/geo/projection/Projection.js b/src/geo/projection/Projection.js new file mode 100644 index 00000000..84316b30 --- /dev/null +++ b/src/geo/projection/Projection.js @@ -0,0 +1,5 @@ +/* + * L.Projection contains various geographical projections used by CRS classes. + */ + +L.Projection = {}; diff --git a/src/map/Map.js b/src/map/Map.js index 1892e315..127cba80 100644 --- a/src/map/Map.js +++ b/src/map/Map.js @@ -7,7 +7,7 @@ L.Map = L.Class.extend({ options: { // projection - crs: new L.CRS.EPSG3857(), + crs: L.CRS.EPSG3857 || L.CRS.EPSG4326, scale: function(zoom) { return 256 * (1 << zoom); }, // state