Skip to content

Commit

Permalink
Merge pull request #2345 from Leaflet/geodesic
Browse files Browse the repository at this point in the history
Better geodesy handling
  • Loading branch information
mourner committed Jan 3, 2014
2 parents d1bc836 + ae8bc57 commit b01126d
Show file tree
Hide file tree
Showing 21 changed files with 148 additions and 134 deletions.
6 changes: 4 additions & 2 deletions build/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ exports.build = function (callback, version, compsBase32, buildName) {
});
};

exports.test = function(callback) {
exports.test = function(complete, fail) {
var karma = require('karma'),
testConfig = {configFile : __dirname + '/../spec/karma.conf.js'};

Expand Down Expand Up @@ -182,7 +182,9 @@ exports.test = function(callback) {
karma.server.start(testConfig, function(exitCode) {
if (!exitCode) {
console.log('\tTests ran successfully.\n');
complete();
} else {
process.exit(exitCode);
}
callback();
});
};
1 change: 1 addition & 0 deletions build/deps.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var deps = {
'geo/projection/Projection.SphericalMercator.js',
'geo/crs/CRS.js',
'geo/crs/CRS.Simple.js',
'geo/crs/CRS.Earth.js',
'geo/crs/CRS.EPSG3857.js',
'geo/crs/CRS.EPSG4326.js',
'map/Map.js',
Expand Down
2 changes: 2 additions & 0 deletions debug/map/simple-proj.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@

map.addLayer(grid);

L.circle([0, 0], 100, {color: 'red'}).addTo(map);

</script>
</body>
</html>
4 changes: 2 additions & 2 deletions spec/suites/geo/CRSSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ describe("CRS.EPSG3857", function () {
describe("project", function () {
it('projects geo coords into meter coords correctly', function () {
expect(crs.project(new L.LatLng(50, 30))).near(new L.Point(3339584.7238, 6446275.84102));
expect(crs.project(new L.LatLng(90, 180))).near(new L.Point(20037508.34279, 20037508.34278));
expect(crs.project(new L.LatLng(-90, -180))).near(new L.Point(-20037508.34279, -20037508.34278));
expect(crs.project(new L.LatLng(85.0511287798, 180))).near(new L.Point(20037508.34279, 20037508.34278));
expect(crs.project(new L.LatLng(-85.0511287798, -180))).near(new L.Point(-20037508.34279, -20037508.34278));
});
});

Expand Down
8 changes: 4 additions & 4 deletions spec/suites/geo/ProjectionSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ describe("Projection.Mercator", function () {
});

it("projects the northeast corner of the world", function () {
expect(p.project(new L.LatLng(90, 180))).near(new L.Point(20037508, 20037508));
expect(p.project(new L.LatLng(85.0840591556, 180))).near(new L.Point(20037508, 20037508));
});

it("projects the southwest corner of the world", function () {
expect(p.project(new L.LatLng(-90, -180))).near(new L.Point(-20037508, -20037508));
expect(p.project(new L.LatLng(-85.0840591556, -180))).near(new L.Point(-20037508, -20037508));
});

it("projects other points", function () {
Expand Down Expand Up @@ -56,11 +56,11 @@ describe("Projection.SphericalMercator", function () {
});

it("projects the northeast corner of the world", function () {
expect(p.project(new L.LatLng(90, 180))).near(new L.Point(20037508, 20037508));
expect(p.project(new L.LatLng(85.0511287798, 180))).near(new L.Point(20037508, 20037508));
});

it("projects the southwest corner of the world", function () {
expect(p.project(new L.LatLng(-90, -180))).near(new L.Point(-20037508, -20037508));
expect(p.project(new L.LatLng(-85.0511287798, -180))).near(new L.Point(-20037508, -20037508));
});

it("projects other points", function () {
Expand Down
9 changes: 5 additions & 4 deletions spec/suites/layer/vector/CircleSpec.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
describe('Circle', function () {
describe('#getBounds', function () {

var circle;
var map, circle;

beforeEach(function () {
circle = L.circle([50, 30], 200);
map = L.map(document.createElement('div')).setView([0, 0], 4);
circle = L.circle([50, 30], 200).addTo(map);
});

it('returns bounds', function () {
var bounds = circle.getBounds();

expect(bounds.getSouthWest().equals([49.998203369, 29.997204939])).to.be.ok();
expect(bounds.getNorthEast().equals([50.001796631, 30.002795061])).to.be.ok();
expect(bounds.getSouthWest()).nearLatLng(new L.LatLng(49.94347, 29.91211));
expect(bounds.getNorthEast()).nearLatLng(new L.LatLng(50.05646, 30.08789));
});
});
});
20 changes: 9 additions & 11 deletions src/control/Control.Scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,21 @@ L.Control.Scale = L.Control.extend({
},

_update: function () {
var bounds = this._map.getBounds(),
centerLat = bounds.getCenter().lat,
halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
dist = halfWorldMeters * (bounds.getEast() - bounds.getWest()) / 180,
var map = this._map,
y = map.getSize().y / 2;

size = this._map.getSize(),
options = this.options,
maxMeters = size.x > 0 ? dist * (options.maxWidth / size.x) : 0;
var maxMeters = L.CRS.Earth.distance(
map.containerPointToLatLng([0, y]),
map.containerPointToLatLng([this.options.maxWidth, y]));

this._updateScales(options, maxMeters);
this._updateScales(maxMeters);
},

_updateScales: function (options, maxMeters) {
if (options.metric && maxMeters) {
_updateScales: function (maxMeters) {
if (this.options.metric && maxMeters) {
this._updateMetric(maxMeters);
}
if (options.imperial && maxMeters) {
if (this.options.imperial && maxMeters) {
this._updateImperial(maxMeters);
}
},
Expand Down
17 changes: 1 addition & 16 deletions src/geo/LatLng.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,8 @@ L.LatLng.prototype = {
L.Util.formatNum(this.lng, precision) + ')';
},

// Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
// TODO move to projection code, LatLng shouldn't know about Earth
distanceTo: function (other) {
other = L.latLng(other);

var R = 6378137, // earth radius in meters
rad = Math.PI / 180,
dLat = (other.lat - this.lat) * rad,
dLon = (other.lng - this.lng) * rad,
lat1 = this.lat * rad,
lat2 = other.lat * rad,
sin1 = Math.sin(dLat / 2),
sin2 = Math.sin(dLon / 2);

var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);

return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return L.CRS.Earth.distance(this, L.latLng(other));
}
};

Expand Down
12 changes: 3 additions & 9 deletions src/geo/crs/CRS.EPSG3395.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@
* L.CRS.EPSG3857 (World Mercator) CRS implementation.
*/

L.CRS.EPSG3395 = L.extend({}, L.CRS, {
L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
code: 'EPSG:3395',

projection: L.Projection.Mercator,

transformation: (function () {
var m = L.Projection.Mercator,
r = m.R_MAJOR,
scale = 0.5 / (Math.PI * r);

var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
return new L.Transformation(scale, 0.5, -scale, 0.5);
}()),

wrapLng: [-180, 180]
}())
});
7 changes: 2 additions & 5 deletions src/geo/crs/CRS.EPSG3857.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
* L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping and is used by Leaflet by default.
*/

L.CRS.EPSG3857 = L.extend({}, L.CRS, {
L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
code: 'EPSG:3857',

projection: L.Projection.SphericalMercator,

transformation: (function () {
var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
return new L.Transformation(scale, 0.5, -scale, 0.5);
}()),

wrapLng: [-180, 180]
}())
});

L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
Expand Down
7 changes: 2 additions & 5 deletions src/geo/crs/CRS.EPSG4326.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
* L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
*/

L.CRS.EPSG4326 = L.extend({}, L.CRS, {
L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
code: 'EPSG:4326',

projection: L.Projection.LonLat,
transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5),

wrapLng: [-180, 180]
transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
});
19 changes: 19 additions & 0 deletions src/geo/crs/CRS.Earth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* L.CRS.Earth is the base class for all CRS representing Earth.
*/

L.CRS.Earth = L.extend({}, L.CRS, {
wrapLng: [-180, 180],

R: 6378137,

// distane between two geographical points using spherical law of cosines approximation
distance: function (latlng1, latlng2) {
var rad = Math.PI / 180,
lat1 = latlng1.lat * rad,
lat2 = latlng2.lat * rad;

return this.R * Math.acos(Math.sin(lat1) * Math.sin(lat2) +
Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad));
}
});
7 changes: 7 additions & 0 deletions src/geo/crs/CRS.Simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,12 @@ L.CRS.Simple = L.extend({}, L.CRS, {
return Math.pow(2, zoom);
},

distance: function (latlng1, latlng2) {
var dx = latlng2.lng - latlng1.lng,
dy = latlng2.lat - latlng1.lat;

return Math.sqrt(dx * dx + dy * dy);
},

infinite: true
});
59 changes: 22 additions & 37 deletions src/geo/projection/Projection.Mercator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,40 @@
*/

L.Projection.Mercator = {
MAX_LATITUDE: 85.0840591556,

R: 6378137,
R_MINOR: 6356752.314245179,
R_MAJOR: 6378137,

bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),

project: function (latlng) {
var d = Math.PI / 180,
max = this.MAX_LATITUDE,
lat = Math.max(Math.min(max, latlng.lat), -max),
r = this.R_MAJOR,
r2 = this.R_MINOR,
x = latlng.lng * d * r,
y = lat * d,
tmp = r2 / r,
eccent = Math.sqrt(1.0 - tmp * tmp),
con = eccent * Math.sin(y);

con = Math.pow((1 - con) / (1 + con), eccent * 0.5);

var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
y = -r * Math.log(ts);

return new L.Point(x, y);
r = this.R,
y = latlng.lat * d,
tmp = this.R_MINOR / r,
e = Math.sqrt(1 - tmp * tmp),
con = e * Math.sin(y);

var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
y = -r * Math.log(Math.max(ts, 1E-10));

return new L.Point(latlng.lng * d * r, y);
},

unproject: function (point) {
var d = 180 / Math.PI,
r = this.R_MAJOR,
r2 = this.R_MINOR,
lng = point.x * d / r,
tmp = r2 / r,
eccent = Math.sqrt(1 - (tmp * tmp)),
ts = Math.exp(- point.y / r),
phi = (Math.PI / 2) - 2 * Math.atan(ts),
numIter = 15,
tol = 1e-7,
i = numIter,
dphi = 0.1,
con;

while ((Math.abs(dphi) > tol) && (--i > 0)) {
con = eccent * Math.sin(phi);
dphi = (Math.PI / 2) - 2 * Math.atan(ts *
Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
r = this.R,
tmp = this.R_MINOR / r,
e = Math.sqrt(1 - tmp * tmp),
ts = Math.exp(-point.y / r),
phi = Math.PI / 2 - 2 * Math.atan(ts);

for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
con = e * Math.sin(phi);
con = Math.pow((1 - con) / (1 + con), e / 2);
dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
phi += dphi;
}

return new L.LatLng(phi * d, lng);
return new L.LatLng(phi * d, point.x * d / r);
}
};
21 changes: 9 additions & 12 deletions src/geo/projection/Projection.SphericalMercator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,24 @@

L.Projection.SphericalMercator = {

MAX_LATITUDE: 85.0511287798,

R: 6378137,

project: function (latlng) {
var d = Math.PI / 180,
max = this.MAX_LATITUDE,
x = this.R * latlng.lng * d,
y = Math.max(Math.min(max, latlng.lat), -max) * d;

y = this.R * Math.log(Math.tan((Math.PI / 4) + (y / 2)));
max = 1 - 1E-15,
sin = Math.max(Math.min(Math.sin(latlng.lat * d), max), -max);

return new L.Point(x, y);
return new L.Point(
this.R * latlng.lng * d,
this.R * Math.log((1 + sin) / (1 - sin)) / 2);
},

unproject: function (point) {
var d = 180 / Math.PI,
lng = point.x * d / this.R,
lat = (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d;
var d = 180 / Math.PI;

return new L.LatLng(lat, lng);
return new L.LatLng(
(2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
point.x * d / this.R);
},

bounds: (function () {
Expand Down
15 changes: 13 additions & 2 deletions src/layer/vector/Canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,21 @@ L.Canvas = L.Renderer.extend({
if (layer._empty()) { return; }

var p = layer._point,
ctx = this._ctx;
ctx = this._ctx,
r = layer._radius,
s = (layer._radiusY || r) / r;

if (s !== 1) {
ctx.save();
ctx.scale(1, s);
}

ctx.beginPath();
ctx.arc(p.x, p.y, layer._radius, 0, Math.PI * 2, false);
ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);

if (s !== 1) {
ctx.restore();
}

this._fillStroke(ctx, layer);
},
Expand Down
Loading

0 comments on commit b01126d

Please sign in to comment.