/* 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