thematic-earth
Version:
HTML-based, whole-Earth thematic maps using locally hosted data layers
2 lines • 13.2 kB
JavaScript
/* Copyright (c) 2023 Read Write Tools. Legal use subject to the Thematic Earth Software License Agreement. */
import BaseFeature from'../features/base-feature.class.js';import PointFeature from'../features/point-feature.class.js';import LineFeature from'../features/line-feature.class.js';import ProjectedPoint from'../projection/projected-point.class.js';import*as st from'../spherical-earth/spherical-trigonometry.js';import expect from'../dev/expect.js';import terminal from'../dev/terminal.js';export default class GreatCircleFeature extends BaseFeature{constructor(t){expect(t,'GreatCircles'),super(),this.parentSpatialFile=t,this.embarkationName='',this.embarkationLatitude=null,this.embarkationLongitude=null,this.destinationName='',this.destinationLatitude=null,this.destinationLongitude=null,this.shortArc=new LineFeature,this.parentSpatialFile.addFeatureToLookupMap(this.shortArc),this.longArc=new LineFeature,this.parentSpatialFile.addFeatureToLookupMap(this.longArc),this.shortIntermediates=[],this.longIntermediates=[],this.distance12=0,this.distance14=0,this.distance43=0,this.distance32=0,Object.seal(this)}removeFeaturesFromMap(){this.parentSpatialFile.removeFeatureFromLookupMap(this.shortArc.featureId),this.parentSpatialFile.removeFeatureFromLookupMap(this.longArc.featureId);for(let t=0;t<this.shortIntermediates.length;t++)this.parentSpatialFile.removeFeatureFromLookupMap(this.shortIntermediates[t].featureId);for(let t=0;t<this.longIntermediates.length;t++)this.parentSpatialFile.removeFeatureFromLookupMap(this.longIntermediates[t].featureId)}setEmbarkationPoint(t,e,i){expect(t,'String'),expect(e,['Number','String']),expect(i,['Number','String']),this.embarkationName=t??'A',this.embarkationLatitude=Number(e),Number.isNaN(this.embarkationLatitude)&&(this.embarkationLatitude=0),this.embarkationLatitude>90&&(this.embarkationLatitude=90),this.embarkationLatitude<-90&&(this.embarkationLatitude=-90),this.embarkationLongitude=Number(i),Number.isNaN(this.embarkationLongitude)&&(this.embarkationLongitude=0),this.embarkationLongitude>180&&(this.embarkationLongitude-=360),this.embarkationLongitude<-180&&(this.embarkationLongitude+=360),this.resetArcsAndWaypoints();var a=new PointFeature;a.setPoint(new ProjectedPoint(this.embarkationLatitude,this.embarkationLongitude)),a.kvPairs.type='embarkation',a.kvPairs.label=this.embarkationName,this.shortIntermediates.push(a),this.parentSpatialFile.addFeatureToLookupMap(a)}setDestinationPoint(t,e,i){expect(t,'String'),expect(e,['Number','String']),expect(i,['Number','String']),this.destinationName=t??'B',this.destinationLatitude=Number(e),Number.isNaN(this.destinationLatitude)&&(this.destinationLatitude=0),this.destinationLatitude>90&&(this.destinationLatitude=90),this.destinationLatitude<-90&&(this.destinationLatitude=-90),this.destinationLongitude=Number(i),Number.isNaN(this.destinationLongitude)&&(this.destinationLongitude=0),this.destinationLongitude>180&&(this.destinationLongitude-=360),this.destinationLongitude<-180&&(this.destinationLongitude+=360),this.resetArcsAndWaypoints();var a=new PointFeature;a.setPoint(new ProjectedPoint(this.destinationLatitude,this.destinationLongitude)),a.kvPairs.type='destination',a.kvPairs.label=this.destinationName,this.shortIntermediates.push(a),this.parentSpatialFile.addFeatureToLookupMap(a)}resetArcsAndWaypoints(){this.removeFeaturesFromMap(),this.shortArc=new LineFeature,this.parentSpatialFile.addFeatureToLookupMap(this.shortArc),this.longArc=new LineFeature,this.parentSpatialFile.addFeatureToLookupMap(this.longArc),this.shortIntermediates=[],this.longIntermediates=[]}determineArcsAndWaypoints(){const t={latitude:this.embarkationLatitude,longitude:this.embarkationLongitude},e={latitude:this.destinationLatitude,longitude:this.destinationLongitude};var i=st.antipode(t.latitude,t.longitude),a=st.antipode(e.latitude,e.longitude);e.latitude==i.latitude&&e.longitude==i.longitude&&(terminal.abnormal('Great Circle starting and ending points are precise antipodes. Offseting longitude by 0.1 degree.'),e.longitude+=.1,a=st.antipode(e.latitude,e.longitude));const r=st.lengthOfArc(t.latitude,t.longitude,e.latitude,e.longitude)/Math.PI,n=Math.round(40*r),s=40-n;this.resetArcsAndWaypoints(),this.determineShortArc(t,e,n),this.determineLongArc(t,e,i,a,n,s),this.determineShortIntermediates(t,e),this.determineLongIntermediates(t,e,i,a)}determineShortArc(t,e,i){var a=st.greatCircleArc(t.latitude,t.longitude,e.latitude,e.longitude,i,!0);for(let t=0;t<a.length;t++){var r=new ProjectedPoint(a[t].latitude,a[t].longitude);this.shortArc.addPoint(r)}this.distance12=st.haversine(t.latitude,t.longitude,e.latitude,e.longitude);var n=this.distance12,s=`${Math.round(n)} km`;this.shortArc.kvPairs.type='short route',this.shortArc.kvPairs.label=s,this.shortArc.kvPairs.distance=s,this.shortArc.kvPairs.id=`Shortest route from ${this.embarkationName} to ${this.destinationName}`,this.shortArc.kvPairs.embarkation=this.formatLatLng(t.longitude,t.latitude),this.shortArc.kvPairs.destination=this.formatLatLng(e.longitude,e.latitude)}determineLongArc(t,e,i,a,r,n){var s=st.greatCircleArc(t.latitude,t.longitude,a.latitude,a.longitude,n,!1);for(let t=0;t<s.length;t++){var o=new ProjectedPoint(s[t].latitude,s[t].longitude);this.longArc.addPoint(o)}s=st.greatCircleArc(a.latitude,a.longitude,i.latitude,i.longitude,r,!1);for(let t=0;t<s.length;t++){o=new ProjectedPoint(s[t].latitude,s[t].longitude);this.longArc.addPoint(o)}s=st.greatCircleArc(i.latitude,i.longitude,e.latitude,e.longitude,n,!0);for(let t=0;t<s.length;t++){o=new ProjectedPoint(s[t].latitude,s[t].longitude);this.longArc.addPoint(o)}this.distance14=st.haversine(t.latitude,t.longitude,a.latitude,a.longitude),this.distance43=st.haversine(a.latitude,a.longitude,i.latitude,i.longitude),this.distance32=st.haversine(i.latitude,i.longitude,e.latitude,e.longitude);var d=this.distance14+this.distance43+this.distance32,l=`${Math.round(d)} km`;this.longArc.kvPairs.type='long route',this.longArc.kvPairs.label=l,this.longArc.kvPairs.distance=l,this.longArc.kvPairs.id=`Long way around from ${this.embarkationName} to ${this.destinationName}`,this.longArc.kvPairs.embarkation=this.formatLatLng(t.longitude,t.latitude),this.longArc.kvPairs.destination=this.formatLatLng(e.longitude,e.latitude)}determineShortIntermediates(t,e){var i=Math.floor(this.distance12/1e3),a=1/i,r=1e3*i/this.distance12,n=st.sphericalIntermediatePoint(t.latitude,t.longitude,e.latitude,e.longitude,r);if(null!=n)for(let e=1;e<=i;e++){var s=st.sphericalIntermediatePoint(t.latitude,t.longitude,n.latitude,n.longitude,a*e),o=1e3*e+' km',d=new PointFeature;d.setPoint(new ProjectedPoint(s.latitude,s.longitude)),d.kvPairs.type='short waypoint',d.kvPairs.label=o,d.kvPairs.id=`${o} from ${this.embarkationName}`,this.shortIntermediates.push(d),this.parentSpatialFile.addFeatureToLookupMap(d)}var l=new PointFeature;l.setPoint(new ProjectedPoint(t.latitude,t.longitude)),l.kvPairs.type='embarkation',l.kvPairs.label=this.embarkationName,l.kvPairs.id=`${this.embarkationName}<br /> ${this.formatLatLng(t.longitude,t.latitude)}`,l.kvPairs.distance=`${Math.round(this.distance12)} km to ${this.destinationName}`,this.shortIntermediates.push(l),this.parentSpatialFile.addFeatureToLookupMap(l);var h=new PointFeature;h.setPoint(new ProjectedPoint(e.latitude,e.longitude)),h.kvPairs.type='destination',h.kvPairs.label=this.destinationName,h.kvPairs.id=`${this.destinationName}<br />${this.formatLatLng(e.longitude,e.latitude)}`,h.kvPairs.distance=`${Math.round(this.distance12)} km from ${this.embarkationName}`,this.shortIntermediates.push(h),this.parentSpatialFile.addFeatureToLookupMap(h)}determineLongIntermediates(t,e,i,a){var r=null,n=null,s=null,o=Math.floor(this.distance14/1e3),d=1/o,l=1e3*o/this.distance14,h=this.distance14-1e3*o;if(null!=(r=st.sphericalIntermediatePoint(t.latitude,t.longitude,a.latitude,a.longitude,l)))for(let e=1;e<=o;e++){var u=st.sphericalIntermediatePoint(t.latitude,t.longitude,r.latitude,r.longitude,d*e),m=1e3*e+' km';(F=new PointFeature).setPoint(new ProjectedPoint(u.latitude,u.longitude)),F.kvPairs.type='long waypoint',F.kvPairs.label=m,F.kvPairs.id=`${m} from ${this.embarkationName}`,this.longIntermediates.push(F),this.parentSpatialFile.addFeatureToLookupMap(F)}var g=this.distance43+h,p=Math.floor(g/1e3),c=1/p,P=1e3*p/g,k=g-1e3*p;if(null!=r&&null!=(n=st.sphericalIntermediatePoint(r.latitude,r.longitude,i.latitude,i.longitude,P)))for(let t=1;t<p;t++){u=st.sphericalIntermediatePoint(r.latitude,r.longitude,n.latitude,n.longitude,c*t),m=1e3*(t+o)+' km';(F=new PointFeature).setPoint(new ProjectedPoint(u.latitude,u.longitude)),F.kvPairs.type='long waypoint',F.kvPairs.label=m,F.kvPairs.id=`${m} from ${this.embarkationName}`,this.longIntermediates.push(F),this.parentSpatialFile.addFeatureToLookupMap(F)}var L=this.distance32+k,v=Math.floor(L/1e3),b=1/v,f=1e3*v/L;if(null!=n&&null!=(s=st.sphericalIntermediatePoint(n.latitude,n.longitude,e.latitude,e.longitude,f)))for(let t=1;t<=v;t++){var F;u=st.sphericalIntermediatePoint(n.latitude,n.longitude,s.latitude,s.longitude,b*t),m=1e3*(t+o+p)+' km';(F=new PointFeature).setPoint(new ProjectedPoint(u.latitude,u.longitude)),F.kvPairs.type='long waypoint',F.kvPairs.label=m,F.kvPairs.id=`${m} from ${this.embarkationName}`,this.longIntermediates.push(F),this.parentSpatialFile.addFeatureToLookupMap(F)}var I=new PointFeature;I.setPoint(new ProjectedPoint(i.latitude,i.longitude)),I.kvPairs.type='embarkation antipode',I.kvPairs.label=`${this.embarkationName} antipode`,I.kvPairs.id=`${this.embarkationName} antipode<br />${this.formatLatLng(i.longitude,i.latitude)}`,I.kvPairs.distance=`${Math.round(this.distance32)} km from ${this.destinationName}<br />40030 km from ${this.embarkationName}`,this.longIntermediates.push(I),this.parentSpatialFile.addFeatureToLookupMap(I);var A=new PointFeature;A.setPoint(new ProjectedPoint(a.latitude,a.longitude)),A.kvPairs.type='destination antipode',A.kvPairs.label=`${this.destinationName} antipode`,A.kvPairs.id=`${this.destinationName} antipode<br />${this.formatLatLng(a.longitude,a.latitude)}`,A.kvPairs.distance=`${Math.round(this.distance14)} km from ${this.embarkationName}<br />40030 km from ${this.destinationName}`,this.longIntermediates.push(A),this.parentSpatialFile.addFeatureToLookupMap(A)}formatLatLng(t,e){var i=Math.abs(t.toFixed(2)),a=t<0?i+'° W':i+'° E',r=Math.abs(e.toFixed(2));return`longitude: ${a}<br />latitude: ${e<0?r+'° S':r+'° N'}`}computeFeatureStyle(t,e,i,a,r,n){expect(t,'RenderClock'),expect(e,'Visualizer'),expect(i,'String'),expect(a,'String'),expect(r,'Number'),expect(n,'Number'),this.shortArc.computeFeatureStyle(t,e,i,a,0,n),this.longArc.computeFeatureStyle(t,e,i,a,0,n);for(let r=0;r<this.shortIntermediates.length;r++)this.shortIntermediates[r].computeFeatureStyle(t,e,i,a,r,n);for(let r=0;r<this.longIntermediates.length;r++)this.longIntermediates[r].computeFeatureStyle(t,e,i,a,r,n)}runCourtesyValidator(t,e,i,a,r){this.shortArc.runCourtesyValidator(t,e,i,0,r),this.longArc.runCourtesyValidator(t,e,i,0,r);for(let a=0;a<this.shortIntermediates.length;a++)this.shortIntermediates[a].runCourtesyValidator(t,e,i,a,r);for(let a=0;a<this.longIntermediates.length;a++)this.longIntermediates[a].runCourtesyValidator(t,e,i,a,r)}toGeoCoords(t,e){this.shortArc.toGeoCoords(t,e),this.longArc.toGeoCoords(t,e);for(let i=0;i<this.shortIntermediates.length;i++)this.shortIntermediates[i].toGeoCoords(t,e);for(let i=0;i<this.longIntermediates.length;i++)this.longIntermediates[i].toGeoCoords(t,e)}toPlane(t,e){this.shortArc.toPlane(t,e),this.longArc.toPlane(t,e);for(let i=0;i<this.shortIntermediates.length;i++)this.shortIntermediates[i].toPlane(t,e);for(let i=0;i<this.longIntermediates.length;i++)this.longIntermediates[i].toPlane(t,e)}toPixels(t,e){this.shortArc.toPixels(t,e),this.longArc.toPixels(t,e);for(let i=0;i<this.shortIntermediates.length;i++)this.shortIntermediates[i].toPixels(t,e);for(let i=0;i<this.longIntermediates.length;i++)this.longIntermediates[i].toPixels(t,e)}toViewportCanvas(t,e){this.shortArc.toViewportCanvas(t,e),this.longArc.toViewportCanvas(t,e);for(let i=0;i<this.shortIntermediates.length;i++)this.shortIntermediates[i].toViewportCanvas(t,e);for(let i=0;i<this.longIntermediates.length;i++)this.longIntermediates[i].toViewportCanvas(t,e)}drawFeature(t,e,i){expect(t,'RenderClock'),expect(e,'Earth'),expect(i,'Layer'),this.shortArc.drawFeature(t,e,i),this.longArc.drawFeature(t,e,i);for(let a=0;a<this.shortIntermediates.length;a++)this.shortIntermediates[a].drawFeature(t,e,i);for(let a=0;a<this.longIntermediates.length;a++)this.longIntermediates[a].drawFeature(t,e,i)}discoverFeatures(t,e,i){var a=this.discoverPoint(t,e,i);return null==a&&(a=this.discoverLine(t,e,i)),a}discoverPoint(t,e,i){for(let r=0;r<this.shortIntermediates.length;r++){if((a=this.shortIntermediates[r]).featureIsVisible(i)&&a.isPointerAtPoint(t,e))return a}for(let r=0;r<this.longIntermediates.length;r++){var a;if((a=this.longIntermediates[r]).featureIsVisible(i)&&a.isPointerAtPoint(t,e))return a}return null}discoverLine(t,e,i){return this.shortArc.featureIsVisible(i)&&this.shortArc.isPointerOnLine(t,e)?this.shortArc:this.longArc.featureIsVisible(i)&&this.longArc.isPointerOnLine(t,e)?this.longArc:null}}