leaflet.smoothgeodesic
Version:
Smooth geodesic curves for Leaflet.
3 lines (2 loc) • 7.03 kB
JavaScript
/*! Leaflet.SmoothGeodesic 1.0.0 - created by Hunter Evanoff - https://github.com/hunter547/Leaflet.SmoothGeodesic */
const t=Math.PI/180,s=180/Math.PI;class i{constructor(s,i){this.lat=s,this.lon=i,this.x=t*i,this.y=t*s}view(){return String(this.lon).slice(0,4)+","+String(this.lat).slice(0,4)}antipode(){const t=-1*this.lat,s=this.lon<0?180+this.lon:-1*(180-this.lon);return new i(s,t)}}class o{constructor(){this.coords=[],this.length=0}moveTo(t){this.length++,this.coords.push(t)}}class e{constructor(t){this.properties=t||{},this.geometries=[]}}class n{constructor(t,s,o){if(!t||void 0===t[0]||void 0===t[1])throw new Error("GreatCircle constructor expects two args: start and end objects with x and y properties");if(!s||void 0===s[0]||void 0===s[1])throw new Error("GreatCircle constructor expects two args: start and end objects with x and y properties");this.start=new i(t[0],t[1]),this.end=new i(s[0],s[1]),this.properties=o||{};const e=this.start.x-this.end.x,n=this.start.y-this.end.y,h=Math.pow(Math.sin(n/2),2)+Math.cos(this.start.y)*Math.cos(this.end.y)*Math.pow(Math.sin(e/2),2);if(this.g=2*Math.asin(Math.sqrt(h)),this.g===Math.PI)throw new Error("it appears "+this.start.view()+" and "+this.end.view()+" are 'antipodal', e.g diametrically opposite, thus there is no single route but rather infinite");if(isNaN(this.g))throw new Error("could not calculate great circle between "+t+" and "+s)}interpolate(t){const i=Math.sin((1-t)*this.g)/Math.sin(this.g),o=Math.sin(t*this.g)/Math.sin(this.g),e=i*Math.cos(this.start.y)*Math.cos(this.start.x)+o*Math.cos(this.end.y)*Math.cos(this.end.x),n=i*Math.cos(this.start.y)*Math.sin(this.start.x)+o*Math.cos(this.end.y)*Math.sin(this.end.x),h=i*Math.sin(this.start.y)+o*Math.sin(this.end.y);return[s*Math.atan2(h,Math.sqrt(Math.pow(e,2)+Math.pow(n,2))),s*Math.atan2(n,e)]}Arc(t,s){const i=[];if(!t||t<=2)i.push([this.start.lat,this.start.lon]),i.push([this.end.lat,this.end.lon]);else{const s=1/(t-1);for(let o=0;o<t;++o){const t=s*o,e=this.interpolate(t);i.push(e)}}let n=!1,h=0;const a=s&&s.offset?s.offset:10,r=180-a,c=-180+a,p=360-a;for(let t=1;t<i.length;++t){const s=i[t-1][1],o=i[t][1],e=Math.abs(o-s);e>p&&(o>r&&s<c||s>r&&o<c)?n=!0:e>h&&(h=e)}const l=[];if(n&&h<a){let t=[];l.push(t);for(let s=0;s<i.length;++s){const o=i[s][1];if(s>0&&Math.abs(o-i[s-1][1])>p){let e=i[s-1][1],n=i[s-1][0],h=i[s][1],a=i[s][0];if(e>-180&&e<c&&180===h&&s+1<i.length&&i[s-1][1]>-180&&i[s-1][1]<c){t.push([i[s][0],-180]),s++,t.push([i[s][0],i[s][1]]);continue}if(e>r&&e<180&&-180===h&&s+1<i.length&&i[s-1][1]>r&&i[s-1][1]<180){t.push([i[s][0],180]),s++,t.push([i[s][0],i[s][1]]);continue}if(e<c&&h>r){const t=e;e=h,h=t;const s=n;n=a,a=s}if(e>r&&h<c&&(h+=360),e<=180&&h>=180&&e<h){const o=(180-e)/(h-e),c=o*a+(1-o)*n;t.push([c,i[s-1][1]>r?180:-180]),t=[],t.push([c,i[s-1][1]>r?-180:180]),l.push(t)}else t=[],l.push(t);t.push([i[s][0],o])}else t.push([i[s][0],i[s][1]])}}else{const t=[];l.push(t);for(let s=0;s<i.length;++s)t.push([i[s][0],i[s][1]])}const u=new e(this.properties);for(let t=0;t<l.length;++t){const s=new o;u.geometries.push(s);const i=l[t];for(let t=0;t<i.length;++t)s.moveTo(i[t])}return u}}const h=null===window||void 0===window?void 0:window.L;h.SmoothGeodesic=h.Path.extend({options:{},initialize:function(t,s,i,o){h.setOptions(this,o),this._coords=this._generateCoordinates(t,s,i),this._setPath(this._catmullRom2bezier())},_generateCoordinates:function(t,s,i){const o=new n(t instanceof h.LatLng?[t.lat,t.lng]:t,s instanceof h.LatLng?[s.lat,s.lng]:s),{geometries:e}=o.Arc(i);return e[0].coords},_catmullRom2bezier:function(){const t=["M",this._coords[0]],s=this._coords.flat();for(let i=0,o=s.length;o-2>i;i+=2){const e=[];0===i?(e.push({x:s[i],y:s[i+1]}),e.push({x:s[i],y:s[i+1]}),e.push({x:s[i+2],y:s[i+3]}),e.push({x:s[i+4],y:s[i+5]})):o-4===i?(e.push({x:s[i-2],y:s[i-1]}),e.push({x:s[i],y:s[i+1]}),e.push({x:s[i+2],y:s[i+3]}),e.push({x:s[i+2],y:s[i+3]})):(e.push({x:s[i-2],y:s[i-1]}),e.push({x:s[i],y:s[i+1]}),e.push({x:s[i+2],y:s[i+3]}),e.push({x:s[i+4],y:s[i+5]}));const n=[];n.push({x:e[1].x,y:e[1].y}),n.push({x:(-e[0].x+6*e[1].x+e[2].x)/6,y:(-e[0].y+6*e[1].y+e[2].y)/6}),n.push({x:(e[1].x+6*e[2].x-e[3].x)/6,y:(e[1].y+6*e[2].y-e[3].y)/6}),n.push({x:e[2].x,y:e[2].y}),t.push("C",[n[1].x,n[1].y],[n[2].x,n[2].y],[n[3].x,n[3].y])}return t},_setPath:function(t){this._pathData=t,this._bounds=this._computeBounds()},_isSmoothGeodesicSVGCommand:function(t){return-1!==["M","L","H","V","C","S","Q","T","Z"].indexOf(t)},_computeBounds:function(){const t=h.latLngBounds([]);let s,i="M";for(let o=0;o<this._pathData.length;o++)if(s=this._pathData[o],this._isSmoothGeodesicSVGCommand(s))i=s;else if("C"===i){const i=h.latLng(s[0],s[1]);s=this._pathData[++o];const e=h.latLng(s[0],s[1]);s=this._pathData[++o];const n=h.latLng(s[0],s[1]);t.extend(i),t.extend(e),t.extend(n),n.controlPoint1=i,n.controlPoint2=e}else t.extend(s);return t},getCenter:function(){return this._bounds.getCenter()},_update:function(){this._map&&this._updateCurveSvg()},_project:function(){let t,s=[0,0],i="M",o=h.point(0,0);const e=this._map;this._points=[];for(let n=0;n<this._pathData.length;n++)if(t=this._pathData[n],this._isSmoothGeodesicSVGCommand(t))this._points.push(t),i=t;else{switch(t.length){case 2:o=e.project(h.latLng(t[0],t[1])).subtract(e.getPixelOrigin()),s=t;break;case 1:if("H"===i){o=e.project(h.latLng(s[0],t[0])).subtract(e.getPixelOrigin()),s=[s[0],t[0]]}else{o=e.project(h.latLng(t[0],s[1])).subtract(e.getPixelOrigin()),s=[t[0],s[1]]}}this._points.push(o)}},_curvePointsToPath:function(t){let s,i="M",o="";for(let e=0;e<t.length;e++)if(s=t[e],"string"==typeof s&&this._isSmoothGeodesicSVGCommand(s))i=s,o+=i;else switch(i){case"H":o+=s.x+" ";break;case"V":o+=s.y+" ";break;default:o+=s.x+","+s.y+" "}return o||"M0 0"},beforeAdd:function(t){var s;null===(s=h.Path.prototype.beforeAdd)||void 0===s||s.call(this,t)},onAdd:function(t){var s;h.Path.prototype.onAdd.call(this,t);let i=1e3*(this.options.fitBounds?this.options.fitBounds.duration||.25:0);if(this.options.fitBounds&&this._map.fitBounds(this._bounds,this.options.fitBounds),this.options.animate&&this._path.animate){const t=this._svgSetDashArray();this.options.animate.delay&&this._path.animate([{strokeDashoffset:t},{strokeDashoffset:t}],{duration:this.options.animate.delay+i}),this._path.animate([{strokeDashoffset:t},{strokeDashoffset:0}],"number"==typeof this.options.animate?this.options.animate+i:Object.assign(Object.assign({},this.options.animate),{delay:(null===(s=this.options.animate)||void 0===s?void 0:s.delay)||0+i}))}},_updateCurveSvg:function(){this._renderer._setPath(this,this._curvePointsToPath(this._points)),this.options.animate&&this._svgSetDashArray()},_svgSetDashArray:function(){const t=this._path,s=t.getTotalLength();return this.options.dashArray||(t.style.strokeDasharray=s+" "+s),s}}),h.smoothGeodesic=function(t,s,i,o){return new h.SmoothGeodesic(t,s,i,o||{})},void 0!==window.L&&(window.L.SmoothGeodesic=h.SmoothGeodesic,window.L.smoothGeodesic=(...t)=>new h.SmoothGeodesic(...t));