Phi: Partial Fix #1769 route manager path
Use geodesic plugin for leaflet do correctly display great circle paths between waypoints and handle wrapping across -180deg of latitude. The entire leaflet library seems to have an issue wrapping around -180/+180 deg of latitude :-/
This commit is contained in:
parent
6a327df616
commit
e0b415ad74
3 changed files with 71 additions and 13 deletions
49
Phi/3rdparty/leaflet-0.7.3/Leaflet.Geodesic.min.js
vendored
Normal file
49
Phi/3rdparty/leaflet-0.7.3/Leaflet.Geodesic.min.js
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*! Leaflet.Geodesic https://github.com/henrythasler/Leaflet.Geodesic*/
|
||||
void 0===Number.prototype.toRadians&&(Number.prototype.toRadians=function(){return this*Math.PI/180}),void 0===Number.prototype.toDegrees&&(Number.prototype.toDegrees=function(){return 180*this/Math.PI})
|
||||
var INTERSECT_LNG=179.999
|
||||
L.Geodesic=L.MultiPolyline.extend({options:{color:"blue",steps:10,dash:1,wrap:!0},initialize:function(t,a){this.options=this._merge_options(this.options,a),this.datum={},this.datum.ellipsoid={a:6378137,b:6356752.3142,f:1/298.257223563},L.MultiPolyline.prototype.initialize.call(this,t,this.options)},setLatLngs:function(t){this._latlngs=this.options.dash<1?this._generate_GeodesicDashed(t):this._generate_Geodesic(t),L.MultiPolyline.prototype.setLatLngs.call(this,this._latlngs)},getStats:function(){var t,a,n={distance:0,points:0,polygons:this._latlngs.length}
|
||||
for(t=0;t<this._latlngs.length;t++)for(n.points+=this._latlngs[t].length,a=0;a<this._latlngs[t].length-1;a++)n.distance+=this._vincenty_inverse(this._latlngs[t][a],this._latlngs[t][a+1]).distance
|
||||
return n},createCircle:function(t,a){var n,i=[],s=0,e={lat:0,lng:0,brg:0}
|
||||
for(i[s]=[],n=0;n<=this.options.steps;){var h=this._vincenty_direct(L.latLng(t),360/this.options.steps*n,a,!0),l=L.latLng(h.lat,h.lng)
|
||||
if(Math.abs(l.lng-e.lng)>180){var o=this._vincenty_inverse(e,l),r=this._intersection(e,o.initialBearing,{lat:-89,lng:l.lng-e.lng>0?-INTERSECT_LNG:INTERSECT_LNG},0)
|
||||
r?(i[s].push(L.latLng(r.lat,r.lng)),s++,i[s]=[],e=L.latLng(r.lat,-r.lng),i[s].push(e)):(s++,i[s]=[],i[s].push(l),e=l,n++)}else i[s].push(l),e=l,n++}this._latlngs=i,L.MultiPolyline.prototype.setLatLngs.call(this,this._latlngs)},_generate_Geodesic:function(t){var a,n,i,s=[],e=0
|
||||
for(n=0;n<t.length;n++){for(s[e]=[],i=0;i<t[n].length-1;i++){var h=this._vincenty_inverse(L.latLng(t[n][i]),L.latLng(t[n][i+1])),l=L.latLng(t[n][i])
|
||||
for(s[e].push(l),a=1;a<=this.options.steps;){var o=this._vincenty_direct(L.latLng(t[n][i]),h.initialBearing,h.distance/this.options.steps*a,this.options.wrap),r=L.latLng(o.lat,o.lng)
|
||||
if(Math.abs(r.lng-l.lng)>180){var g=this._intersection(L.latLng(t[n][i]),h.initialBearing,{lat:-89,lng:r.lng-l.lng>0?-INTERSECT_LNG:INTERSECT_LNG},0)
|
||||
g?(s[e].push(L.latLng(g.lat,g.lng)),e++,s[e]=[],l=L.latLng(g.lat,-g.lng),s[e].push(l)):(e++,s[e]=[],s[e].push(r),l=r,a++)}else s[e].push(r),l=r,a++}}e++}return s},_generate_GeodesicDashed:function(t){var a,n,i,s=[],e=0
|
||||
for(n=0;n<t.length;n++){for(s[e]=[],i=0;i<t[n].length-1;i++){var h=this._vincenty_inverse(L.latLng(t[n][i]),L.latLng(t[n][i+1])),l=L.latLng(t[n][i])
|
||||
for(s[e].push(l),a=1;a<=this.options.steps;){var o=this._vincenty_direct(L.latLng(t[n][i]),h.initialBearing,h.distance/this.options.steps*a-h.distance/this.options.steps*(1-this.options.dash),this.options.wrap),r=L.latLng(o.lat,o.lng)
|
||||
if(Math.abs(r.lng-l.lng)>180){var g=this._intersection(L.latLng(t[n][i]),h.initialBearing,{lat:-89,lng:r.lng-l.lng>0?-INTERSECT_LNG:INTERSECT_LNG},0)
|
||||
g?(s[e].push(L.latLng(g.lat,g.lng)),e++,s[e]=[],l=L.latLng(g.lat,-g.lng),s[e].push(l)):(e++,s[e]=[],s[e].push(r),l=r,a++)}else{s[e].push(r),e++
|
||||
var M=this._vincenty_direct(L.latLng(t[n][i]),h.initialBearing,h.distance/this.options.steps*a,this.options.wrap)
|
||||
s[e]=[],s[e].push(L.latLng(M.lat,M.lng)),a++}}}e++}return s},_vincenty_direct:function(t,a,n,i){var s,e=t.lat.toRadians(),h=t.lng.toRadians(),l=a.toRadians(),o=n,r=this.datum.ellipsoid.a,g=this.datum.ellipsoid.b,M=this.datum.ellipsoid.f,c=Math.sin(l),p=Math.cos(l),u=(1-M)*Math.tan(e),L=1/Math.sqrt(1+u*u),d=u*L,v=Math.atan2(u,p),_=L*c,f=1-_*_,y=f*(r*r-g*g)/(g*g),I=1+y/16384*(4096+y*(-768+y*(320-175*y))),N=y/1024*(256+y*(-128+y*(74-47*y))),R=o/(g*I),P=0
|
||||
do{var m=Math.cos(2*v+R),b=Math.sin(R),E=Math.cos(R),T=N*b*(m+N/4*(E*(-1+2*m*m)-N/6*m*(-3+4*b*b)*(-3+4*m*m)))
|
||||
s=R,R=o/(g*I)+T}while(Math.abs(R-s)>1e-12&&++P)
|
||||
var G=d*b-L*E*p,D=Math.atan2(d*E+L*b*p,(1-M)*Math.sqrt(_*_+G*G)),B=Math.atan2(b*c,L*E-d*b*p),C=M/16*f*(4+M*(4-3*f)),S=B-(1-C)*M*_*(R+C*b*(m+C*E*(-1+2*m*m)))
|
||||
if(i)var w=(h+S+3*Math.PI)%(2*Math.PI)-Math.PI
|
||||
else var w=h+S
|
||||
var q=Math.atan2(_,-G)
|
||||
return{lat:D.toDegrees(),lng:w.toDegrees(),finalBearing:q.toDegrees()}},_vincenty_inverse:function(t,a){var n,i=t.lat.toRadians(),s=t.lng.toRadians(),e=a.lat.toRadians(),h=a.lng.toRadians(),l=this.datum.ellipsoid.a,o=this.datum.ellipsoid.b,r=this.datum.ellipsoid.f,g=h-s,M=(1-r)*Math.tan(i),c=1/Math.sqrt(1+M*M),p=M*c,u=(1-r)*Math.tan(e),L=1/Math.sqrt(1+u*u),d=u*L,v=g,_=0
|
||||
do{var f=Math.sin(v),y=Math.cos(v),I=L*f*L*f+(c*d-p*L*y)*(c*d-p*L*y),N=Math.sqrt(I)
|
||||
if(0==N)return 0
|
||||
var R=p*d+c*L*y,P=Math.atan2(N,R),m=c*L*f/N,b=1-m*m,E=R-2*p*d/b
|
||||
isNaN(E)&&(E=0)
|
||||
var T=r/16*b*(4+r*(4-3*b))
|
||||
n=v,v=g+(1-T)*r*m*(P+T*N*(E+T*R*(-1+2*E*E)))}while(Math.abs(v-n)>1e-12&&++_<100)
|
||||
if(_>=100)return console.log("Formula failed to converge. Altering target position."),this._vincenty_inverse(t,{lat:a.lat,lng:a.lng-.01})
|
||||
var G=b*(l*l-o*o)/(o*o),D=1+G/16384*(4096+G*(-768+G*(320-175*G))),B=G/1024*(256+G*(-128+G*(74-47*G))),C=B*N*(E+B/4*(R*(-1+2*E*E)-B/6*E*(-3+4*N*N)*(-3+4*E*E))),S=o*D*(P-C),w=Math.atan2(L*f,c*d-p*L*y),q=Math.atan2(c*f,-p*L+c*d*y)
|
||||
return S=+S.toFixed(3),{distance:S,initialBearing:w.toDegrees(),finalBearing:q.toDegrees()}},_intersection:function(t,a,n,i){var s=t.lat.toRadians(),e=t.lng.toRadians(),h=n.lat.toRadians(),l=n.lng.toRadians(),o=(+a).toRadians(),r=(+i).toRadians(),g=h-s,M=l-e,c=2*Math.asin(Math.sqrt(Math.sin(g/2)*Math.sin(g/2)+Math.cos(s)*Math.cos(h)*Math.sin(M/2)*Math.sin(M/2)))
|
||||
if(0==c)return null
|
||||
var p=Math.acos((Math.sin(h)-Math.sin(s)*Math.cos(c))/(Math.sin(c)*Math.cos(s)))
|
||||
isNaN(p)&&(p=0)
|
||||
var u=Math.acos((Math.sin(s)-Math.sin(h)*Math.cos(c))/(Math.sin(c)*Math.cos(h)))
|
||||
if(Math.sin(l-e)>0)var L=p,d=2*Math.PI-u
|
||||
else var L=2*Math.PI-p,d=u
|
||||
var v=(o-L+Math.PI)%(2*Math.PI)-Math.PI,_=(d-r+Math.PI)%(2*Math.PI)-Math.PI
|
||||
if(0==Math.sin(v)&&0==Math.sin(_))return null
|
||||
if(Math.sin(v)*Math.sin(_)<0)return null
|
||||
var f=Math.acos(-Math.cos(v)*Math.cos(_)+Math.sin(v)*Math.sin(_)*Math.cos(c)),y=Math.atan2(Math.sin(c)*Math.sin(v)*Math.sin(_),Math.cos(_)+Math.cos(v)*Math.cos(f)),I=Math.asin(Math.sin(s)*Math.cos(y)+Math.cos(s)*Math.sin(y)*Math.cos(o)),N=Math.atan2(Math.sin(o)*Math.sin(y)*Math.cos(s),Math.cos(y)-Math.sin(s)*Math.sin(I)),R=e+N
|
||||
return R=(R+3*Math.PI)%(2*Math.PI)-Math.PI,{lat:I.toDegrees(),lng:R.toDegrees()}},_merge_options:function(t,a){var n={}
|
||||
for(var i in t)n[i]=t[i]
|
||||
for(var i in a)n[i]=a[i]
|
||||
return n}}),L.geodesic=function(t,a){return new L.Geodesic(t,a)}
|
|
@ -7,6 +7,7 @@ require.config({
|
|||
kojqui : '3rdparty/knockout-jqueryui',
|
||||
sprintf : '3rdparty/sprintf/sprintf.min',
|
||||
leaflet : '3rdparty/leaflet-0.7.3/leaflet',
|
||||
geodesic : '3rdparty/leaflet-0.7.3/Leaflet.Geodesic.min',
|
||||
text : '3rdparty/require/text',
|
||||
flot : '3rdparty/flot/jquery.flot',
|
||||
flotresize : '3rdparty/flot/jquery.flot.resize',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
if (typeof define === "function" && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define([
|
||||
'leaflet', 'props', './MapIcons', 'knockout'
|
||||
'leaflet', 'props', './MapIcons', 'knockout', 'geodesic'
|
||||
], factory);
|
||||
} else {
|
||||
// Browser globals
|
||||
|
@ -31,13 +31,21 @@
|
|||
});
|
||||
self.waypointCountSubscription = self.waypointCount.subscribe(function() {
|
||||
self.update();
|
||||
});
|
||||
|
||||
})
|
||||
self.geodesic = L.geodesic([], {
|
||||
weight: 5,
|
||||
opacity: 0.5,
|
||||
color: 'blue',
|
||||
steps: 20,
|
||||
}).addTo(map);
|
||||
},
|
||||
|
||||
onRemove : function(map) {
|
||||
this.waypointCountSubscription.dispose();
|
||||
this.waypointCount.dispose();
|
||||
var self = this;
|
||||
self.waypointCountSubscription.dispose();
|
||||
self.waypointCount.dispose();
|
||||
map.removeLayer(self.geodesic);
|
||||
leaflet.GeoJSON.prototype.onRemove.call(this, map);
|
||||
},
|
||||
|
||||
|
@ -55,8 +63,15 @@
|
|||
var jqxhr = $.get(url).done(function(data) {
|
||||
self.clearLayers();
|
||||
var geoJSON = self.routePropsToGeoJson(data);
|
||||
if (geoJSON)
|
||||
if (geoJSON) {
|
||||
self.addData(geoJSON);
|
||||
var latlngs = [];
|
||||
geoJSON.features.forEach( function(f) {
|
||||
if( f.geometry && f.geometry.coordinates )
|
||||
latlngs.push( new L.LatLng(f.geometry.coordinates[1], f.geometry.coordinates[0] ) );
|
||||
});
|
||||
self.geodesic.setLatLngs([latlngs]);
|
||||
}
|
||||
}).fail(function(a, b) {
|
||||
// self.stop(); // TODO: Should we?
|
||||
alert('failed to load RouteManager data');
|
||||
|
@ -69,7 +84,6 @@
|
|||
type : "FeatureCollection",
|
||||
features : [],
|
||||
};
|
||||
var lineString = [];
|
||||
|
||||
var root = new SGPropertyNode(props);
|
||||
root.getChildren("wp").forEach(function(wp) {
|
||||
|
@ -80,7 +94,6 @@
|
|||
var position = [
|
||||
lon, lat
|
||||
];
|
||||
lineString.push(position);
|
||||
|
||||
geoJSON.features.push({
|
||||
"type" : "Feature",
|
||||
|
@ -93,12 +106,7 @@
|
|||
});
|
||||
});
|
||||
|
||||
geoJSON.features.push({
|
||||
"type" : "LineString",
|
||||
"coordinates" : lineString,
|
||||
});
|
||||
|
||||
if (lineString.length >= 2)
|
||||
if (geoJSON.features.length >= 2)
|
||||
return geoJSON;
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in a new issue