this library is used in the ingress backend, so distance calculation, etc are far closer if we use the value from that
250 lines
7.7 KiB
JavaScript
250 lines
7.7 KiB
JavaScript
/*
|
|
Geodesic extension to Leaflet library, by Fragger
|
|
https://github.com/Fragger/Leaflet.Geodesic
|
|
Version from master branch, dated Apr 26, 2013
|
|
Modified by qnstie 2013-07-17 to maintain compatibility with Leaflet.draw
|
|
*/
|
|
(function () {
|
|
function geodesicPoly(Klass, fill) {
|
|
return Klass.extend({
|
|
initialize: function (latlngs, options) {
|
|
Klass.prototype.initialize.call(this, L.geodesicConvertLines(latlngs, fill), options);
|
|
this._latlngsinit = this._convertLatLngs(latlngs);
|
|
},
|
|
getLatLngs: function () {
|
|
return this._latlngsinit;
|
|
},
|
|
setLatLngs: function (latlngs) {
|
|
this._latlngsinit = this._convertLatLngs(latlngs);
|
|
return this.redraw();
|
|
},
|
|
addLatLng: function (latlng) {
|
|
this._latlngsinit.push(L.latLng(latlng));
|
|
return this.redraw();
|
|
},
|
|
spliceLatLngs: function () { // (Number index, Number howMany)
|
|
var removed = [].splice.apply(this._latlngsinit, arguments);
|
|
this._convertLatLngs(this._latlngsinit);
|
|
this.redraw();
|
|
return removed;
|
|
},
|
|
redraw: function() {
|
|
this._latlngs = this._convertLatLngs(L.geodesicConvertLines(this._latlngsinit, fill));
|
|
return Klass.prototype.redraw.call(this);
|
|
}
|
|
});
|
|
}
|
|
|
|
// alternative geodesic line intermediate points function
|
|
// as north/south lines have very little curvature in the projection, we cam use longitude (east/west) seperation
|
|
// to calculate intermediate points. hopeefully this will avoid the rounding issues seen in the full intermediate
|
|
// points code that have been seen
|
|
function geodesicConvertLine(startLatLng, endLatLng, convertedPoints) {
|
|
var R = 6367000.0; // earth radius in meters (doesn't have to be exact)
|
|
var d2r = Math.PI/180.0;
|
|
var r2d = 180.0/Math.PI;
|
|
|
|
// maths based on http://williams.best.vwh.net/avform.htm#Int
|
|
|
|
var lat1 = startLatLng.lat * d2r;
|
|
var lat2 = endLatLng.lat * d2r;
|
|
var lng1 = startLatLng.lng * d2r;
|
|
var lng2 = endLatLng.lng * d2r;
|
|
|
|
var dLng = lng2-lng1;
|
|
|
|
var segments = Math.floor(Math.abs(dLng * R / 5000));
|
|
|
|
if (segments > 1) {
|
|
// pre-calculate some constant values for the loop
|
|
var sinLat1 = Math.sin(lat1);
|
|
var sinLat2 = Math.sin(lat2);
|
|
var cosLat1 = Math.cos(lat1);
|
|
var cosLat2 = Math.cos(lat2);
|
|
|
|
var sinLat1CosLat2 = sinLat1*cosLat2;
|
|
var sinLat2CosLat1 = sinLat2*cosLat1;
|
|
|
|
var cosLat1CosLat2SinDLng = cosLat1*cosLat2*Math.sin(dLng);
|
|
|
|
for (var i=1; i < segments; i++) {
|
|
var iLng = lng1+dLng*(i/segments);
|
|
var iLat = Math.atan( (sinLat1CosLat2*Math.sin(lng2-iLng) + sinLat2CosLat1*Math.sin(iLng-lng1))
|
|
/ cosLat1CosLat2SinDLng)
|
|
|
|
var point = L.latLng ( [iLat*r2d, iLng*r2d] );
|
|
convertedPoints.push(point);
|
|
}
|
|
}
|
|
|
|
convertedPoints.push(L.latLng(endLatLng));
|
|
}
|
|
|
|
|
|
|
|
L.geodesicConvertLines = function (latlngs, fill) {
|
|
if (latlngs.length == 0) {
|
|
return [];
|
|
}
|
|
|
|
for (var i = 0, len = latlngs.length; i < len; i++) {
|
|
if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
|
|
return;
|
|
}
|
|
latlngs[i] = L.latLng(latlngs[i]);
|
|
}
|
|
|
|
// geodesic calculations have issues when crossing the anti-meridian. so offset the points
|
|
// so this isn't an issue, then add back the offset afterwards
|
|
// a center longitude would be ideal - but the start point longitude will be 'good enough'
|
|
var lngOffset = latlngs[0].lng;
|
|
|
|
// points are wrapped after being offset relative to the first point coordinate, so they're
|
|
// within +-180 degrees
|
|
latlngs = latlngs.map(function(a){ return L.latLng(a.lat, a.lng-lngOffset).wrap(); });
|
|
|
|
var geodesiclatlngs = [];
|
|
|
|
if(!fill) {
|
|
geodesiclatlngs.push(latlngs[0]);
|
|
}
|
|
for (i = 0, len = latlngs.length - 1; i < len; i++) {
|
|
geodesicConvertLine(latlngs[i], latlngs[i+1], geodesiclatlngs);
|
|
}
|
|
if(fill) {
|
|
geodesicConvertLine(latlngs[len], latlngs[0], geodesiclatlngs);
|
|
}
|
|
|
|
// now add back the offset subtracted above. no wrapping here - the drawing code handles
|
|
// things better when there's no sudden jumps in coordinates. yes, lines will extend
|
|
// beyond +-180 degrees - but they won't be 'broken'
|
|
geodesiclatlngs = geodesiclatlngs.map(function(a){ return L.latLng(a.lat, a.lng+lngOffset); });
|
|
|
|
return geodesiclatlngs;
|
|
}
|
|
|
|
L.GeodesicPolyline = geodesicPoly(L.Polyline, 0);
|
|
L.GeodesicPolygon = geodesicPoly(L.Polygon, 1);
|
|
|
|
//L.GeodesicMultiPolyline = createMulti(L.GeodesicPolyline);
|
|
//L.GeodesicMultiPolygon = createMulti(L.GeodesicPolygon);
|
|
|
|
/*L.GeodesicMultiPolyline = L.MultiPolyline.extend({
|
|
initialize: function (latlngs, options) {
|
|
L.MultiPolyline.prototype.initialize.call(this, L.geodesicConvertLines(latlngs), options);
|
|
}
|
|
});*/
|
|
|
|
/*L.GeodesicMultiPolygon = L.MultiPolygon.extend({
|
|
initialize: function (latlngs, options) {
|
|
L.MultiPolygon.prototype.initialize.call(this, L.geodesicConvertLines(latlngs), options);
|
|
}
|
|
});*/
|
|
|
|
|
|
L.GeodesicCircle = L.Polygon.extend({
|
|
initialize: function (latlng, radius, options) {
|
|
this._latlng = L.latLng(latlng);
|
|
this._mRadius = radius;
|
|
|
|
points = this._calcPoints();
|
|
|
|
L.Polygon.prototype.initialize.call(this, points, options);
|
|
},
|
|
|
|
options: {
|
|
fill: true
|
|
},
|
|
|
|
setLatLng: function (latlng) {
|
|
this._latlng = L.latLng(latlng);
|
|
points = this._calcPoints();
|
|
this.setLatLngs(points);
|
|
},
|
|
|
|
setRadius: function (radius) {
|
|
this._mRadius = radius;
|
|
points = this._calcPoints();
|
|
this.setLatLngs(points);
|
|
|
|
},
|
|
|
|
getLatLng: function () {
|
|
return this._latlng;
|
|
},
|
|
|
|
getRadius: function() {
|
|
return this._mRadius;
|
|
},
|
|
|
|
|
|
_calcPoints: function() {
|
|
var R = 6367000.0; //earth radius in meters (approx - taken from leaflet source code)
|
|
var d2r = Math.PI/180.0;
|
|
var r2d = 180.0/Math.PI;
|
|
//console.log("geodesicCircle: radius = "+this._mRadius+"m, centre "+this._latlng.lat+","+this._latlng.lng);
|
|
|
|
// circle radius as an angle from the centre of the earth
|
|
var radRadius = this._mRadius / R;
|
|
|
|
//console.log(" (radius in radians "+radRadius);
|
|
|
|
// pre-calculate various values used for every point on the circle
|
|
var centreLat = this._latlng.lat * d2r;
|
|
var centreLng = this._latlng.lng * d2r;
|
|
|
|
var cosCentreLat = Math.cos(centreLat);
|
|
var sinCentreLat = Math.sin(centreLat);
|
|
|
|
var cosRadRadius = Math.cos(radRadius);
|
|
var sinRadRadius = Math.sin(radRadius);
|
|
|
|
var calcLatLngAtAngle = function(angle) {
|
|
var lat = Math.asin(sinCentreLat*cosRadRadius + cosCentreLat*sinRadRadius*Math.cos(angle));
|
|
var lng = centreLng + Math.atan2(Math.sin(angle)*sinRadRadius*cosCentreLat, cosRadRadius-sinCentreLat*Math.sin(lat));
|
|
|
|
return L.latLng(lat * r2d,lng * r2d);
|
|
}
|
|
|
|
|
|
var segments = Math.max(48,Math.floor(this._mRadius/1000));
|
|
//console.log(" (drawing circle as "+segments+" lines)");
|
|
var points = [];
|
|
for (var i=0; i<segments; i++) {
|
|
var angle = Math.PI*2/segments*i;
|
|
|
|
var point = calcLatLngAtAngle(angle)
|
|
points.push ( point );
|
|
}
|
|
|
|
return points;
|
|
},
|
|
|
|
});
|
|
|
|
|
|
L.geodesicPolyline = function (latlngs, options) {
|
|
return new L.GeodesicPolyline(latlngs, options);
|
|
};
|
|
|
|
L.geodesicPolygon = function (latlngs, options) {
|
|
return new L.GeodesicPolygon(latlngs, options);
|
|
};
|
|
|
|
/*
|
|
L.geodesicMultiPolyline = function (latlngs, options) {
|
|
return new L.GeodesicMultiPolyline(latlngs, options);
|
|
};
|
|
|
|
L.geodesicMultiPolygon = function (latlngs, options) {
|
|
return new L.GeodesicMultiPolygon(latlngs, options);
|
|
};
|
|
|
|
*/
|
|
|
|
L.geodesicCircle = function (latlng, radius, options) {
|
|
return new L.GeodesicCircle(latlng, radius, options);
|
|
}
|
|
|
|
}());
|