terra-route 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- var t=function(t,s){var e=function(t){return t*Math.PI/180},r=e(t[1]),i=e(t[0]),n=e(s[1]),a=n-r,h=e(s[0])-i,c=Math.sin(a/2)*Math.sin(a/2)+Math.cos(r)*Math.cos(n)*Math.sin(h/2)*Math.sin(h/2);return 2*Math.atan2(Math.sqrt(c),Math.sqrt(1-c))*6371e3/1e3},s=/*#__PURE__*/function(){function t(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}var s=t.prototype;return s.insert=function(t,s){var e=this.length;this.length=e+1;for(var r=t,i=s,n=this.insertCounter++;e>0;){var a=e-1>>>2,h=this.keys[a],c=this.idxs[a];if(r>h||r===h&&n>c)break;this.keys[e]=h,this.values[e]=this.values[a],this.idxs[e]=c,e=a}this.keys[e]=r,this.values[e]=i,this.idxs[e]=n},s.extractMin=function(){var t=this.length;if(0===t)return null;var s=this.values[0],e=t-1;return this.length=e,e>0&&(this.keys[0]=this.keys[e],this.values[0]=this.values[e],this.idxs[0]=this.idxs[e],this.bubbleDown(0)),s},s.size=function(){return this.length},s.bubbleDown=function(t){for(var s=this.length,e=this.keys,r=this.values,i=this.idxs,n=e[t],a=r[t],h=i[t];;){var c=1+(t<<2);if(c>=s)break;var o=c,u=e[c],l=i[c],v=r[c],d=c+1;if(d<s){var f=e[d],g=i[d];(f<u||f===u&&g<l)&&(o=d,u=f,l=g,v=r[d])}var p=c+2;if(p<s){var y=e[p],M=i[p];(y<u||y===u&&M<l)&&(o=p,u=y,l=M,v=r[p])}var I=c+3;if(I<s){var w=e[I],S=i[I];(w<u||w===u&&S<l)&&(o=I,u=w,l=S,v=r[I])}if(!(u<n||u===n&&l<h))break;e[t]=u,r[t]=v,i[t]=l,t=o}e[t]=n,r[t]=a,i[t]=h},t}();exports.TerraRoute=/*#__PURE__*/function(){function e(e){var r,i;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(r=null==e?void 0:e.distanceMeasurement)?r:t,this.heapConstructor=null!=(i=null==e?void 0:e.heap)?i:s}var r=e.prototype;return r.buildRouteGraph=function(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;for(var s=this.coordinateIndexMap,e=this.coordinates,r=this.distanceMeasurement,i=t.features,n=[],a=0,h=i.length;a<h;a++)for(var c=i[a].geometry.coordinates,o=0,u=c.length-1;o<u;o++){var l,v,d=c[o],f=c[o+1],g=d[0],p=d[1],y=f[0],M=f[1],I=s.get(g);void 0===I&&(I=new Map,s.set(g,I));var w=I.get(p);void 0===w&&(w=e.length,e.push(d),I.set(p,w));var S=s.get(y);void 0===S&&(S=new Map,s.set(y,S));var x=S.get(M);void 0===x&&(x=e.length,e.push(f),S.set(M,x)),n[w]=(null!=(l=n[w])?l:0)+1,n[x]=(null!=(v=n[x])?v:0)+1}var m=this.coordinates.length;this.csrNodeCount=m;for(var C=new Int32Array(m+1),k=0;k<m;k++){var b,O=null!=(b=n[k])?b:0;C[k+1]=C[k]+O}for(var N=C[m],A=new Int32Array(N),F=new Float64Array(N),D=C.slice(),L=0,j=i.length;L<j;L++)for(var R=i[L].geometry.coordinates,q=0,P=R.length-1;q<P;q++){var T=R[q],z=R[q+1],E=T[1],G=z[0],U=z[1],V=this.coordinateIndexMap.get(T[0]).get(E),Y=this.coordinateIndexMap.get(G).get(U),_=r(T,z),B=D[V]++;A[B]=Y,F[B]=_,A[B=D[Y]++]=V,F[B]=_}this.csrOffsets=C,this.csrIndices=A,this.csrDistances=F,this.adjacencyList=new Array(m)},r.getRoute=function(t,s){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var e=this.getOrCreateIndex(t.geometry.coordinates),r=this.getOrCreateIndex(s.geometry.coordinates);if(e===r)return null;var i=this.coordinates,n=this.adjacencyList,a=this.distanceMeasurement,h=i.length;this.ensureScratch(h);var c=this.gScoreScratch,o=this.cameFromScratch,u=this.visitedScratch,l=this.hScratch;c.fill(Number.POSITIVE_INFINITY,0,h),o.fill(-1,0,h),u.fill(0,0,h),l.fill(-1,0,h);var v=new this.heapConstructor,d=i[r],f=l[e];for(f<0&&(f=a(i[e],d),l[e]=f),v.insert(f,e),c[e]=0;v.size()>0;){var g=v.extractMin();if(0===u[g]){if(g===r)break;if(u[g]=1,this.csrOffsets&&g<this.csrNodeCount)for(var p=this.csrOffsets,y=this.csrIndices,M=this.csrDistances,I=p[g+1],w=p[g];w<I;w++){var S=y[w],x=c[g]+M[w];if(x<c[S]){c[S]=x,o[S]=g;var m=l[S];m<0&&(m=a(i[S],d),l[S]=m),v.insert(x+m,S)}}else{var C=n[g];if(!C||0===C.length)continue;for(var k=0,b=C.length;k<b;k++){var O=C[k],N=O.node,A=c[g]+O.distance;if(A<c[N]){c[N]=A,o[N]=g;var F=l[N];F<0&&(F=a(i[N],d),l[N]=F),v.insert(A+F,N)}}}}}if(o[r]<0)return null;for(var D=[],L=r;L!==e;)D.push(i[L]),L=o[L];return D.push(i[e]),D.reverse(),{type:"Feature",geometry:{type:"LineString",coordinates:D},properties:{}}},r.getOrCreateIndex=function(t){var s=t[0],e=t[1],r=this.coordinateIndexMap.get(s);void 0===r&&(r=new Map,this.coordinateIndexMap.set(s,r));var i=r.get(e);if(void 0===i&&(i=this.coordinates.length,this.coordinates.push(t),r.set(e,i),this.adjacencyList[i]=[],this.csrOffsets)){var n=this.csrNodeCount;if(i===n){var a=new Int32Array(n+2);a.set(this.csrOffsets,0),a[n+1]=a[n],this.csrOffsets=a,this.csrNodeCount=n+1}}return i},r.ensureScratch=function(t){if(!(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch)){var s=0|t;this.gScoreScratch=new Float64Array(s),this.cameFromScratch=new Int32Array(s),this.visitedScratch=new Uint8Array(s),this.hScratch=new Float64Array(s),this.scratchCapacity=s}},e}(),exports.createCheapRuler=function(t){var s=1/298.257223563,e=s*(2-s),r=Math.PI/180,i=Math.cos(t*r),n=1/(1-e*(1-i*i)),a=Math.sqrt(n),h=6378.137*r,c=h*a*i,o=h*a*n*(1-e);return function(t,s){for(var e=t[0]-s[0];e<-180;)e+=360;for(;e>180;)e-=360;var r=e*c,i=(t[1]-s[1])*o;return Math.sqrt(r*r+i*i)}},exports.haversineDistance=t;
1
+ var t=function(t,r){var e=function(t){return t*Math.PI/180},s=e(t[1]),i=e(t[0]),n=e(r[1]),a=n-s,h=e(r[0])-i,c=Math.sin(a/2)*Math.sin(a/2)+Math.cos(s)*Math.cos(n)*Math.sin(h/2)*Math.sin(h/2);return 2*Math.atan2(Math.sqrt(c),Math.sqrt(1-c))*6371e3/1e3},r=/*#__PURE__*/function(){function t(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}var r=t.prototype;return r.insert=function(t,r){var e=this.length;this.length=e+1;for(var s=t,i=r,n=this.insertCounter++;e>0;){var a=e-1>>>2,h=this.keys[a],c=this.idxs[a];if(s>h||s===h&&n>c)break;this.keys[e]=h,this.values[e]=this.values[a],this.idxs[e]=c,e=a}this.keys[e]=s,this.values[e]=i,this.idxs[e]=n},r.extractMin=function(){var t=this.length;if(0===t)return null;var r=this.values[0],e=t-1;return this.length=e,e>0&&(this.keys[0]=this.keys[e],this.values[0]=this.values[e],this.idxs[0]=this.idxs[e],this.bubbleDown(0)),r},r.size=function(){return this.length},r.bubbleDown=function(t){for(var r=this.length,e=this.keys,s=this.values,i=this.idxs,n=e[t],a=s[t],h=i[t];;){var c=1+(t<<2);if(c>=r)break;var o=c,u=e[c],l=i[c],f=s[c],v=c+1;if(v<r){var d=e[v],I=i[v];(d<u||d===u&&I<l)&&(o=v,u=d,l=I,f=s[v])}var S=c+2;if(S<r){var g=e[S],y=i[S];(g<u||g===u&&y<l)&&(o=S,u=g,l=y,f=s[S])}var p=c+3;if(p<r){var m=e[p],w=i[p];(m<u||m===u&&w<l)&&(o=p,u=m,l=w,f=s[p])}if(!(u<n||u===n&&l<h))break;e[t]=u,s[t]=f,i[t]=l,t=o}e[t]=n,s[t]=a,i[t]=h},t}();exports.TerraRoute=/*#__PURE__*/function(){function e(e){var s,i;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.gScoreRevScratch=null,this.cameFromRevScratch=null,this.visitedRevScratch=null,this.hRevScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(s=null==e?void 0:e.distanceMeasurement)?s:t,this.heapConstructor=null!=(i=null==e?void 0:e.heap)?i:r}var s=e.prototype;return s.buildRouteGraph=function(t){var r=this;this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;var e=this.coordinateIndexMap,s=this.coordinates,i=this.distanceMeasurement,n=[];this.forEachSegment(t,function(t,i){var a,h,c=r.indexCoordinate(t,e,s),o=r.indexCoordinate(i,e,s);n[c]=(null!=(a=n[c])?a:0)+1,n[o]=(null!=(h=n[o])?h:0)+1});var a=this.coordinates.length;this.csrNodeCount=a;for(var h=new Int32Array(a+1),c=0;c<a;c++){var o,u=null!=(o=n[c])?o:0;h[c+1]=h[c]+u}var l=h[a],f=new Int32Array(l),v=new Float64Array(l),d=h.slice();this.forEachSegment(t,function(t,e){var s=r.coordinateIndexMap.get(t[0]).get(t[1]),n=r.coordinateIndexMap.get(e[0]).get(e[1]),a=i(t,e),h=d[s]++;f[h]=n,v[h]=a,h=d[n]++,f[h]=s,v[h]=a}),this.csrOffsets=h,this.csrIndices=f,this.csrDistances=v,this.adjacencyList=new Array(a)},s.expandRouteGraph=function(t){var r=this;if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");this.network={type:"FeatureCollection",features:[].concat(this.network.features,t.features)};for(var e=this.coordinateIndexMap,s=this.coordinates,i=this.distanceMeasurement,n=this.adjacencyList,a=0;a<n.length;a++)void 0===n[a]&&(n[a]=[]);this.forEachSegment(t,function(t,a){var h=r.indexCoordinate(t,e,s,function(t){n[t]=[]}),c=r.indexCoordinate(a,e,s,function(t){n[t]=[]}),o=i(t,a);n[h].push({node:c,distance:o}),n[c].push({node:h,distance:o})}),this.rebuildCsrFromAdjacency()},s.rebuildCsrFromAdjacency=function(){var t=this.coordinates.length,r=this.adjacencyList,e=new Int32Array(t);if(this.csrOffsets&&this.csrIndices&&this.csrDistances)for(var s=this.csrOffsets,i=Math.min(this.csrNodeCount,t),n=0;n<i;n++)e[n]+=s[n+1]-s[n];for(var a=0;a<t;a++){var h=r[a];h&&h.length&&(e[a]+=h.length)}for(var c=new Int32Array(t+1),o=0;o<t;o++)c[o+1]=c[o]+e[o];var u=c[t],l=new Int32Array(u),f=new Float64Array(u),v=c.slice();if(this.csrOffsets&&this.csrIndices&&this.csrDistances)for(var d=this.csrOffsets,I=this.csrIndices,S=this.csrDistances,g=Math.min(this.csrNodeCount,t),y=0;y<g;y++){for(var p=d[y+1],m=v[y],w=d[y];w<p;w++)l[m]=I[w],f[m]=S[w],m++;v[y]=m}for(var M=0;M<t;M++){var N=r[M];if(N&&0!==N.length){for(var C=v[M],x=0,F=N.length;x<F;x++){var O=N[x];l[C]=O.node,f[C]=O.distance,C++}v[M]=C}}this.csrOffsets=c,this.csrIndices=l,this.csrDistances=f,this.csrNodeCount=t,this.adjacencyList=new Array(t)},s.getRoute=function(t,r){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var e=this.getOrCreateIndex(t.geometry.coordinates),s=this.getOrCreateIndex(r.geometry.coordinates);if(e===s)return null;var i=this.coordinates,n=this.adjacencyList,a=i.length;this.ensureScratch(a);var h=this.gScoreScratch,c=this.gScoreRevScratch,o=this.cameFromScratch,u=this.cameFromRevScratch,l=this.visitedScratch,f=this.visitedRevScratch,v=this.hScratch,d=this.hRevScratch;h.fill(Number.POSITIVE_INFINITY,0,a),c.fill(Number.POSITIVE_INFINITY,0,a),o.fill(-1,0,a),u.fill(-1,0,a),l.fill(0,0,a),f.fill(0,0,a),v.fill(-1,0,a),d.fill(-1,0,a);var I=new this.heapConstructor,S=new this.heapConstructor;h[e]=0,c[s]=0,I.insert(0,e),S.insert(0,s);for(var g=Number.POSITIVE_INFINITY,y=-1,p=0,m=0;I.size()>0&&S.size()>0&&!(y>=0&&p+m>=g);)if(I.size()<=S.size()){var w=I.extractMin();if(0!==l[w])continue;if(p=h[w],l[w]=1,0!==f[w]){var M=h[w]+c[w];M<g&&(g=M,y=w)}if(this.csrOffsets&&w<this.csrNodeCount)for(var N=this.csrOffsets,C=this.csrIndices,x=this.csrDistances,F=N[w],O=N[w+1];F<O;F++){var b=C[F],R=h[w]+x[F];if(R<h[b]){h[b]=R,o[b]=w;var k=c[b];if(k!==Number.POSITIVE_INFINITY){var A=R+k;A<g&&(g=A,y=b)}I.insert(R,b)}}else{var T=n[w];if(T&&T.length)for(var E=0,D=T.length;E<D;E++){var P=T[E],j=P.node,L=h[w]+P.distance;if(L<h[j]){h[j]=L,o[j]=w;var V=c[j];if(V!==Number.POSITIVE_INFINITY){var Y=L+V;Y<g&&(g=Y,y=j)}I.insert(L,j)}}}}else{var _=S.extractMin();if(0!==f[_])continue;if(m=c[_],f[_]=1,0!==l[_]){var z=h[_]+c[_];z<g&&(g=z,y=_)}if(this.csrOffsets&&_<this.csrNodeCount)for(var q=this.csrOffsets,G=this.csrIndices,U=this.csrDistances,B=q[_],H=q[_+1];B<H;B++){var J=G[B],K=c[_]+U[B];if(K<c[J]){c[J]=K,u[J]=_;var Q=h[J];if(Q!==Number.POSITIVE_INFINITY){var W=K+Q;W<g&&(g=W,y=J)}S.insert(K,J)}}else{var X=n[_];if(X&&X.length)for(var Z=0,$=X.length;Z<$;Z++){var tt=X[Z],rt=tt.node,et=c[_]+tt.distance;if(et<c[rt]){c[rt]=et,u[rt]=_;var st=h[rt];if(st!==Number.POSITIVE_INFINITY){var it=et+st;it<g&&(g=it,y=rt)}S.insert(et,rt)}}}}if(y<0)return null;for(var nt=[],at=y;at!==e&&at>=0;)nt.push(i[at]),at=o[at];if(at!==e)return null;for(nt.push(i[e]),nt.reverse(),at=y;at!==s;){if((at=u[at])<0)return null;nt.push(i[at])}return{type:"Feature",geometry:{type:"LineString",coordinates:nt},properties:{}}},s.getOrCreateIndex=function(t){var r=t[0],e=t[1],s=this.coordinateIndexMap.get(r);void 0===s&&(s=new Map,this.coordinateIndexMap.set(r,s));var i=s.get(e);if(void 0===i&&(i=this.coordinates.length,this.coordinates.push(t),s.set(e,i),this.adjacencyList[i]=[],this.csrOffsets)){var n=this.csrNodeCount;if(i===n){var a=new Int32Array(n+2);a.set(this.csrOffsets,0),a[n+1]=a[n],this.csrOffsets=a,this.csrNodeCount=n+1}}return i},s.ensureScratch=function(t){if(!(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch&&this.gScoreRevScratch&&this.cameFromRevScratch&&this.visitedRevScratch&&this.hRevScratch)){var r=0|t;this.gScoreScratch=new Float64Array(r),this.cameFromScratch=new Int32Array(r),this.visitedScratch=new Uint8Array(r),this.hScratch=new Float64Array(r),this.gScoreRevScratch=new Float64Array(r),this.cameFromRevScratch=new Int32Array(r),this.visitedRevScratch=new Uint8Array(r),this.hRevScratch=new Float64Array(r),this.scratchCapacity=r}},s.forEachSegment=function(t,r){for(var e=t.features,s=0,i=e.length;s<i;s++)for(var n=e[s].geometry.coordinates,a=0,h=n.length-1;a<h;a++)r(n[a],n[a+1])},s.indexCoordinate=function(t,r,e,s){var i=t[0],n=t[1],a=r.get(i);void 0===a&&(a=new Map,r.set(i,a));var h=a.get(n);return void 0===h&&(h=e.length,e.push(t),a.set(n,h),s&&s(h)),h},e}(),exports.createCheapRuler=function(t){var r=1/298.257223563,e=r*(2-r),s=Math.PI/180,i=Math.cos(t*s),n=1/(1-e*(1-i*i)),a=Math.sqrt(n),h=6378.137*s,c=h*a*i,o=h*a*n*(1-e);return function(t,r){for(var e=t[0]-r[0];e<-180;)e+=360;for(;e>180;)e-=360;var s=e*c,i=(t[1]-r[1])*o;return Math.sqrt(s*s+i*i)}},exports.haversineDistance=t;
2
2
  //# sourceMappingURL=terra-route.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.cjs","sources":["../src/distance/haversine.ts","../src/heap/four-ary-heap.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n\n // Convert distance from meters to kilometers\n return distance / 1000;\n}\n","import { Heap } from \"./heap\";\n\ninterface Node {\n key: number;\n value: number;\n index: number; // insertion order for stable tie-breaking\n}\n\n/**\n * A 4-ary min-heap with stable tie-breaking on insertion order.\n * Parent(i) = floor((i - 1) / 4)\n * Children(i) = 4*i + 1 .. 4*i + 4\n */\nexport class FourAryHeap implements Heap {\n // Parallel arrays for fewer object allocations and faster property access\n private keys: number[] = [];\n private values: number[] = [];\n private idxs: number[] = [];\n private length = 0; // number of valid elements\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n // Bubble-up using local variables (avoid temporary node object)\n let i = this.length;\n this.length = i + 1;\n\n let ck = key;\n let cv = value;\n let ci = this.insertCounter++;\n\n while (i > 0) {\n const p = (i - 1) >>> 2; // divide by 4\n const pk = this.keys[p];\n const pi = this.idxs[p];\n if (ck > pk || (ck === pk && ci > pi)) break;\n\n // move parent down\n this.keys[i] = pk;\n this.values[i] = this.values[p];\n this.idxs[i] = pi;\n i = p;\n }\n\n // place the new node\n this.keys[i] = ck;\n this.values[i] = cv;\n this.idxs[i] = ci;\n }\n\n extractMin(): number | null {\n const n = this.length;\n if (n === 0) return null;\n\n const minValue = this.values[0];\n const last = n - 1;\n this.length = last;\n\n if (last > 0) {\n // Move last element to root then bubble down\n this.keys[0] = this.keys[last];\n this.values[0] = this.values[last];\n this.idxs[0] = this.idxs[last];\n this.bubbleDown(0);\n }\n\n return minValue;\n }\n\n size(): number {\n return this.length;\n }\n\n private bubbleDown(i: number): void {\n const n = this.length;\n const k = this.keys;\n const v = this.values;\n const idx = this.idxs;\n\n const nodeK = k[i];\n const nodeV = v[i];\n const nodeI = idx[i];\n\n while (true) {\n const c1 = (i << 2) + 1; // 4*i + 1\n if (c1 >= n) break; // no children\n\n // find smallest among up to 4 children\n let smallest = c1;\n let sK = k[c1];\n let sI = idx[c1];\n let sV = v[c1];\n\n const c2 = c1 + 1;\n if (c2 < n) {\n const k2 = k[c2];\n const i2 = idx[c2];\n if (k2 < sK || (k2 === sK && i2 < sI)) {\n smallest = c2;\n sK = k2;\n sI = i2;\n sV = v[c2];\n }\n }\n\n const c3 = c1 + 2;\n if (c3 < n) {\n const k3 = k[c3];\n const i3 = idx[c3];\n if (k3 < sK || (k3 === sK && i3 < sI)) {\n smallest = c3;\n sK = k3;\n sI = i3;\n sV = v[c3];\n }\n }\n\n const c4 = c1 + 3;\n if (c4 < n) {\n const k4 = k[c4];\n const i4 = idx[c4];\n if (k4 < sK || (k4 === sK && i4 < sI)) {\n smallest = c4;\n sK = k4;\n sI = i4;\n sV = v[c4];\n }\n }\n\n if (sK < nodeK || (sK === nodeK && sI < nodeI)) {\n k[i] = sK;\n v[i] = sV;\n idx[i] = sI;\n i = smallest;\n } else {\n break;\n }\n }\n\n k[i] = nodeK;\n v[i] = nodeV;\n idx[i] = nodeI;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\"; // Import GeoJSON types\nimport { haversineDistance } from \"./distance/haversine\"; // Great-circle distance function (default heuristic/edge weight)\nimport { createCheapRuler } from \"./distance/cheap-ruler\"; // Factory for faster planar distance (exported for consumers)\nimport { HeapConstructor } from \"./heap/heap\"; // Heap interface so users can plug custom heaps\nimport { FourAryHeap } from \"./heap/four-ary-heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null; // The last network used to build the graph\n private distanceMeasurement: (a: Position, b: Position) => number; // Distance function used for edges and heuristic\n private heapConstructor: HeapConstructor; // Heap class used by A*\n\n // Map from longitude → (map from latitude → index) to deduplicate coordinates and get node indices quickly\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map(); // Nested map for exact coord lookup\n private coordinates: Position[] = []; // Array of all unique coordinates by index\n // Sparse adjacency list used during build and for any nodes added dynamically later\n private adjacencyList: Array<Array<{ node: number, distance: number }>> = []; // Per-node neighbor arrays used only pre-CSR or for dynamic nodes\n\n // Compressed Sparse Row adjacency representation for fast neighbor iteration in getRoute\n private csrOffsets: Int32Array | null = null; // Row pointer: length = nodeCount + 1, offsets into indices/distances\n private csrIndices: Int32Array | null = null; // Column indices: neighbor node IDs, length = totalEdges\n private csrDistances: Float64Array | null = null; // Edge weights aligned to csrIndices, length = totalEdges\n private csrNodeCount = 0; // Number of nodes captured in the CSR arrays\n\n // Reusable typed scratch buffers for A*\n private gScoreScratch: Float64Array | null = null; // gScore per node (cost from start)\n private cameFromScratch: Int32Array | null = null; // Predecessor per node for path reconstruction\n private visitedScratch: Uint8Array | null = null; // Visited set to avoid reprocessing\n private hScratch: Float64Array | null = null; // Per-node heuristic cache for the current query (lazy compute)\n private scratchCapacity = 0; // Current capacity of scratch arrays\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number; // Optional distance function override\n heap?: HeapConstructor; // Optional heap implementation override\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance; // Default to haversine\n this.heapConstructor = options?.heap ?? FourAryHeap; // Default to MinHeap\n }\n\n /**\n * Builds a graph (CSR) from a LineString FeatureCollection.\n * Two-pass build: pass 1 assigns node indices and counts degrees; pass 2 fills CSR arrays.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network; // Keep a reference to the network\n\n // Reset internal structures for a fresh build\n this.coordinateIndexMap = new Map(); // Clear coordinate index map\n this.coordinates = []; // Clear coordinates array\n this.adjacencyList = []; // Will not be populated during build; reserved for dynamic nodes post-build\n // Reset CSR structures (will rebuild below)\n this.csrOffsets = null;\n this.csrIndices = null;\n this.csrDistances = null;\n this.csrNodeCount = 0;\n\n // Hoist to locals for speed (avoid repeated property lookups in hot loops)\n const coordIndexMapLocal = this.coordinateIndexMap; // Local alias for coord map\n const coordsLocal = this.coordinates; // Local alias for coordinates array\n const measureDistance = this.distanceMeasurement; // Local alias for distance function\n\n const features = network.features; // All LineString features\n\n // Pass 1: assign indices and count degrees per node\n const degree: number[] = []; // Dynamic degree array; grows as nodes are discovered\n for (let f = 0, fLen = features.length; f < fLen; f++) { // Iterate features\n const feature = features[f]; // Current feature\n const lineCoords = feature.geometry.coordinates; // Coordinates for this LineString\n\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) { // Iterate segment pairs\n const a = lineCoords[i]; // Segment start coord\n const b = lineCoords[i + 1]; // Segment end coord\n\n const lngA = a[0], latA = a[1]; // Keys for A\n const lngB = b[0], latB = b[1]; // Keys for B\n\n // Index A\n let latMapA = coordIndexMapLocal.get(lngA);\n if (latMapA === undefined) {\n latMapA = new Map<number, number>();\n coordIndexMapLocal.set(lngA, latMapA);\n }\n let indexA = latMapA.get(latA);\n if (indexA === undefined) {\n indexA = coordsLocal.length;\n coordsLocal.push(a);\n latMapA.set(latA, indexA);\n }\n\n // Index B\n let latMapB = coordIndexMapLocal.get(lngB);\n if (latMapB === undefined) {\n latMapB = new Map<number, number>();\n coordIndexMapLocal.set(lngB, latMapB);\n }\n let indexB = latMapB.get(latB);\n if (indexB === undefined) {\n indexB = coordsLocal.length;\n coordsLocal.push(b);\n latMapB.set(latB, indexB);\n }\n\n // Count degree for both directions\n degree[indexA] = (degree[indexA] ?? 0) + 1;\n degree[indexB] = (degree[indexB] ?? 0) + 1;\n }\n }\n\n // Build CSR arrays from degree counts\n const nodeCount = this.coordinates.length; // Total nodes discovered\n this.csrNodeCount = nodeCount; // CSR covers all built nodes\n const offsets = new Int32Array(nodeCount + 1); // Row pointer array\n for (let i = 0; i < nodeCount; i++) {\n const deg = degree[i] ?? 0; // Degree of node i\n offsets[i + 1] = offsets[i] + deg; // Prefix sum\n }\n const totalEdges = offsets[nodeCount]; // Total adjacency entries\n const indices = new Int32Array(totalEdges); // Neighbor indices array\n const distances = new Float64Array(totalEdges); // Distances array aligned to indices\n\n // Pass 2: fill CSR arrays using a write cursor per node\n const cursor = offsets.slice(); // Current write positions per node\n for (let f = 0, fLen = features.length; f < fLen; f++) {\n const feature = features[f];\n const lineCoords = feature.geometry.coordinates;\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) {\n const a = lineCoords[i];\n const b = lineCoords[i + 1];\n\n const lngA = a[0], latA = a[1];\n const lngB = b[0], latB = b[1];\n\n // Read back indices (guaranteed to exist from pass 1)\n const indexA = this.coordinateIndexMap.get(lngA)!.get(latA)!;\n const indexB = this.coordinateIndexMap.get(lngB)!.get(latB)!;\n\n const segmentDistance = measureDistance(a, b); // Edge weight once\n\n // Write A → B\n let pos = cursor[indexA]++;\n indices[pos] = indexB;\n distances[pos] = segmentDistance;\n // Write B → A\n pos = cursor[indexB]++;\n indices[pos] = indexA;\n distances[pos] = segmentDistance;\n }\n }\n\n // Commit CSR to instance\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n\n // Prepare sparse shell only for dynamically added nodes later (no prefilled neighbor arrays)\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>, // Start point feature\n end: Feature<Point> // End point feature\n ): Feature<LineString> | null {\n if (this.network === null) { // Guard: graph must be built first\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Ensure start/end exist in index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates); // Get or insert start node index\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates); // Get or insert end node index\n\n // Trivial case: same node\n if (startIndex === endIndex) {\n return null; // No path needed\n }\n\n // Local aliases\n const coords = this.coordinates; // Alias to coordinates array\n const adj = this.adjacencyList; // Alias to sparse adjacency list (for dynamic nodes)\n const measureDistance = this.distanceMeasurement; // Alias to distance function\n\n // Ensure and init scratch buffers\n const nodeCount = coords.length; // Current number of nodes (may be >= csrNodeCount if new nodes added)\n this.ensureScratch(nodeCount); // Allocate scratch arrays if needed\n\n // Non-null after ensure\n const gScore = this.gScoreScratch!; // gScore pointer\n const cameFrom = this.cameFromScratch!; // cameFrom pointer\n const visited = this.visitedScratch!; // visited pointer\n const hCache = this.hScratch!; // heuristic cache pointer\n\n // Reset only the used range for speed\n gScore.fill(Number.POSITIVE_INFINITY, 0, nodeCount); // Init gScore to +∞\n cameFrom.fill(-1, 0, nodeCount); // Init predecessors to -1 (unknown)\n visited.fill(0, 0, nodeCount); // Init visited flags to 0\n hCache.fill(-1, 0, nodeCount); // Init heuristic cache with sentinel (-1 means unknown)\n\n // Create min-heap (priority queue)\n const openSet = new this.heapConstructor();\n\n // Precompute heuristic for start to prime the queue cost\n const endCoord = coords[endIndex]; // Cache end coordinate for heuristic\n let hStart = hCache[startIndex];\n\n if (hStart < 0) {\n hStart = measureDistance(coords[startIndex], endCoord);\n hCache[startIndex] = hStart;\n }\n\n openSet.insert(hStart, startIndex); // Insert start with f = g(0) + h(start)\n gScore[startIndex] = 0; // g(start) = 0\n\n while (openSet.size() > 0) { // Main A* loop until queue empty\n const current = openSet.extractMin()!; // Pop node with smallest f\n if (visited[current] !== 0) { // Skip if already finalized\n continue;\n }\n if (current === endIndex) { // Early exit if reached goal\n break;\n }\n visited[current] = 1; // Mark as visited\n\n // Prefer CSR neighbors if available for this node, fall back to sparse list for dynamically added nodes\n\n if (this.csrOffsets && current < this.csrNodeCount) { // Use CSR fast path\n const csrOffsets = this.csrOffsets!; // Local CSR offsets (non-null here)\n const csrIndices = this.csrIndices!; // Local CSR neighbors\n const csrDistances = this.csrDistances!; // Local CSR weights\n const startOff = csrOffsets[current]; // Row start for current\n const endOff = csrOffsets[current + 1]; // Row end for current\n\n for (let i = startOff; i < endOff; i++) { // Iterate neighbors in CSR slice\n const nbNode = csrIndices[i]; // Neighbor node id\n const tentativeG = gScore[current] + csrDistances[i]; // g' = g(current) + w(current, nb)\n if (tentativeG < gScore[nbNode]) { // Relaxation check\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n // A* priority = g + h (cache h per node for this query)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Push/update neighbor into open set\n }\n }\n }\n // Fallback: use sparse adjacency (only for nodes added after CSR build)\n else {\n\n const neighbors = adj[current]; // Neighbor list for current\n if (!neighbors || neighbors.length === 0) continue; // No neighbors\n for (let i = 0, n = neighbors.length; i < n; i++) { // Iterate neighbors\n const nb = neighbors[i]; // Neighbor entry\n const nbNode = nb.node; // Neighbor id\n\n const tentativeG = gScore[current] + nb.distance; // g' via current\n if (tentativeG < gScore[nbNode]) { // Relax if better\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n\n // A* priority = g + h (cached)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Enqueue neighbor\n }\n }\n }\n }\n\n // If goal was never reached/relaxed, return null\n if (cameFrom[endIndex] < 0) {\n return null;\n }\n\n // Reconstruct path (push + reverse to avoid O(n^2) unshift)\n const path: Position[] = [];\n let cur = endIndex;\n while (cur !== startIndex) {\n path.push(coords[cur]);\n cur = cameFrom[cur];\n }\n // Include start coordinate\n path.push(coords[startIndex]);\n\n // Reverse to get start→end order\n path.reverse();\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number { // Ensure a coordinate has a node index, creating if absent\n const lng = coord[0]; // Extract longitude\n const lat = coord[1]; // Extract latitude\n\n let latMap = this.coordinateIndexMap.get(lng); // Get lat→index map for this longitude\n if (latMap === undefined) { // Create if missing\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n\n let index = latMap.get(lat); // Lookup index by latitude\n if (index === undefined) { // If not found, append new node\n\n index = this.coordinates.length; // New index at end\n this.coordinates.push(coord); // Store coordinate\n latMap.set(lat, index); // Record mapping\n\n // Ensure sparse adjacency slot for dynamically added nodes\n this.adjacencyList[index] = []; // Init empty neighbor array\n\n // Extend CSR offsets to keep indices consistent (no neighbors for new node)\n if (this.csrOffsets) { // Only adjust if CSR already built\n\n // Only need to expand offsets by one; indices/distances remain unchanged\n const oldCount = this.csrNodeCount; // Nodes currently covered by CSR\n\n // Appending exactly one new node at the end\n if (index === oldCount) {\n const newOffsets = new Int32Array(oldCount + 2); // Allocate offsets for +1 node\n newOffsets.set(this.csrOffsets, 0); // Copy previous offsets\n\n // Last offset repeats to indicate zero neighbors\n newOffsets[oldCount + 1] = newOffsets[oldCount]; // Replicate last pointer\n this.csrOffsets = newOffsets; // Swap in new offsets\n this.csrNodeCount = oldCount + 1; // Increment CSR node count\n }\n }\n }\n\n return index;\n }\n\n // Ensure scratch arrays are allocated with at least `size` capacity.\n private ensureScratch(size: number): void {\n const ifAlreadyBigEnough = this.scratchCapacity >= size\n && this.gScoreScratch\n && this.cameFromScratch\n && this.visitedScratch\n && this.hScratch;\n\n if (ifAlreadyBigEnough) {\n return; // Nothing to do\n }\n const capacity = size | 0; // Ensure integer\n this.gScoreScratch = new Float64Array(capacity);\n this.cameFromScratch = new Int32Array(capacity);\n this.visitedScratch = new Uint8Array(capacity);\n this.hScratch = new Float64Array(capacity);\n this.scratchCapacity = capacity;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance } ","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}"],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","FourAryHeap","keys","this","values","idxs","length","insertCounter","_proto","prototype","insert","key","value","i","ck","cv","ci","p","pk","pi","extractMin","n","minValue","last","bubbleDown","size","k","v","idx","nodeK","nodeV","nodeI","c1","smallest","sK","sI","sV","c2","k2","i2","c3","k3","i3","c4","k4","i4","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","csrOffsets","csrIndices","csrDistances","csrNodeCount","gScoreScratch","cameFromScratch","visitedScratch","hScratch","scratchCapacity","heap","buildRouteGraph","coordIndexMapLocal","coordsLocal","measureDistance","features","degree","f","fLen","lineCoords","geometry","len","_degree$indexA","_degree$indexB","b","lngA","latA","lngB","latB","latMapA","get","undefined","set","indexA","push","latMapB","indexB","nodeCount","offsets","Int32Array","_degree$_i","deg","totalEdges","indices","distances","Float64Array","cursor","slice","segmentDistance","pos","Array","getRoute","start","end","Error","startIndex","getOrCreateIndex","endIndex","coords","adj","ensureScratch","gScore","cameFrom","visited","hCache","fill","Number","POSITIVE_INFINITY","openSet","endCoord","hStart","current","endOff","nbNode","tentativeG","hVal","neighbors","nb","node","distance","path","cur","reverse","type","properties","coord","lng","lat","latMap","index","oldCount","newOffsets","capacity","Uint8Array","FE","E2","RAD","cosLat","w2","w","m","kx","ky","deltaLng","dx","dy"],"mappings":"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"}
1
+ {"version":3,"file":"terra-route.cjs","sources":["../src/distance/haversine.ts","../src/heap/four-ary-heap.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n\n // Convert distance from meters to kilometers\n return distance / 1000;\n}\n","import { Heap } from \"./heap\";\n\ninterface Node {\n key: number;\n value: number;\n index: number; // insertion order for stable tie-breaking\n}\n\n/**\n * A 4-ary min-heap with stable tie-breaking on insertion order.\n * Parent(i) = floor((i - 1) / 4)\n * Children(i) = 4*i + 1 .. 4*i + 4\n */\nexport class FourAryHeap implements Heap {\n // Parallel arrays for fewer object allocations and faster property access\n private keys: number[] = [];\n private values: number[] = [];\n private idxs: number[] = [];\n private length = 0; // number of valid elements\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n // Bubble-up using local variables (avoid temporary node object)\n let i = this.length;\n this.length = i + 1;\n\n let ck = key;\n let cv = value;\n let ci = this.insertCounter++;\n\n while (i > 0) {\n const p = (i - 1) >>> 2; // divide by 4\n const pk = this.keys[p];\n const pi = this.idxs[p];\n if (ck > pk || (ck === pk && ci > pi)) break;\n\n // move parent down\n this.keys[i] = pk;\n this.values[i] = this.values[p];\n this.idxs[i] = pi;\n i = p;\n }\n\n // place the new node\n this.keys[i] = ck;\n this.values[i] = cv;\n this.idxs[i] = ci;\n }\n\n extractMin(): number | null {\n const n = this.length;\n if (n === 0) return null;\n\n const minValue = this.values[0];\n const last = n - 1;\n this.length = last;\n\n if (last > 0) {\n // Move last element to root then bubble down\n this.keys[0] = this.keys[last];\n this.values[0] = this.values[last];\n this.idxs[0] = this.idxs[last];\n this.bubbleDown(0);\n }\n\n return minValue;\n }\n\n size(): number {\n return this.length;\n }\n\n private bubbleDown(i: number): void {\n const n = this.length;\n const k = this.keys;\n const v = this.values;\n const idx = this.idxs;\n\n const nodeK = k[i];\n const nodeV = v[i];\n const nodeI = idx[i];\n\n while (true) {\n const c1 = (i << 2) + 1; // 4*i + 1\n if (c1 >= n) break; // no children\n\n // find smallest among up to 4 children\n let smallest = c1;\n let sK = k[c1];\n let sI = idx[c1];\n let sV = v[c1];\n\n const c2 = c1 + 1;\n if (c2 < n) {\n const k2 = k[c2];\n const i2 = idx[c2];\n if (k2 < sK || (k2 === sK && i2 < sI)) {\n smallest = c2;\n sK = k2;\n sI = i2;\n sV = v[c2];\n }\n }\n\n const c3 = c1 + 2;\n if (c3 < n) {\n const k3 = k[c3];\n const i3 = idx[c3];\n if (k3 < sK || (k3 === sK && i3 < sI)) {\n smallest = c3;\n sK = k3;\n sI = i3;\n sV = v[c3];\n }\n }\n\n const c4 = c1 + 3;\n if (c4 < n) {\n const k4 = k[c4];\n const i4 = idx[c4];\n if (k4 < sK || (k4 === sK && i4 < sI)) {\n smallest = c4;\n sK = k4;\n sI = i4;\n sV = v[c4];\n }\n }\n\n if (sK < nodeK || (sK === nodeK && sI < nodeI)) {\n k[i] = sK;\n v[i] = sV;\n idx[i] = sI;\n i = smallest;\n } else {\n break;\n }\n }\n\n k[i] = nodeK;\n v[i] = nodeV;\n idx[i] = nodeI;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\"; // Import GeoJSON types\nimport { haversineDistance } from \"./distance/haversine\"; // Great-circle distance function (default heuristic/edge weight)\nimport { createCheapRuler } from \"./distance/cheap-ruler\"; // Factory for faster planar distance (exported for consumers)\nimport { HeapConstructor } from \"./heap/heap\"; // Heap interface so users can plug custom heaps\nimport { FourAryHeap } from \"./heap/four-ary-heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n expandRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null; // The last network used to build the graph\n private distanceMeasurement: (a: Position, b: Position) => number; // Distance function used for edges and heuristic\n private heapConstructor: HeapConstructor; // Heap class used by A*\n\n // Map from longitude → (map from latitude → index) to deduplicate coordinates and get node indices quickly\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map(); // Nested map for exact coord lookup\n private coordinates: Position[] = []; // Array of all unique coordinates by index\n // Sparse adjacency list used during build and for any nodes added dynamically later\n private adjacencyList: Array<Array<{ node: number, distance: number }>> = []; // Per-node neighbor arrays used only pre-CSR or for dynamic nodes\n\n // Compressed Sparse Row adjacency representation for fast neighbor iteration in getRoute\n private csrOffsets: Int32Array | null = null; // Row pointer: length = nodeCount + 1, offsets into indices/distances\n private csrIndices: Int32Array | null = null; // Column indices: neighbor node IDs, length = totalEdges\n private csrDistances: Float64Array | null = null; // Edge weights aligned to csrIndices, length = totalEdges\n private csrNodeCount = 0; // Number of nodes captured in the CSR arrays\n\n // Reusable typed scratch buffers for A*\n private gScoreScratch: Float64Array | null = null; // gScore per node (cost from start)\n private cameFromScratch: Int32Array | null = null; // Predecessor per node for path reconstruction\n private visitedScratch: Uint8Array | null = null; // Visited set to avoid reprocessing\n private hScratch: Float64Array | null = null; // Per-node heuristic cache for the current query (lazy compute)\n // Reverse-direction scratch for bidirectional search\n private gScoreRevScratch: Float64Array | null = null; // gScore per node from the end\n private cameFromRevScratch: Int32Array | null = null; // Successor per node (next step toward the end)\n private visitedRevScratch: Uint8Array | null = null; // Visited set for reverse search\n private hRevScratch: Float64Array | null = null; // Heuristic cache for reverse direction per query\n private scratchCapacity = 0; // Current capacity of scratch arrays\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number; // Optional distance function override\n heap?: HeapConstructor; // Optional heap implementation override\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance; // Default to haversine\n this.heapConstructor = options?.heap ?? FourAryHeap; // Default to MinHeap\n }\n\n /**\n * Builds a graph (CSR) from a LineString FeatureCollection.\n * Two-pass build: pass 1 assigns node indices and counts degrees; pass 2 fills CSR arrays.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network; // Keep a reference to the network\n\n // Reset internal structures for a fresh build\n this.coordinateIndexMap = new Map(); // Clear coordinate index map\n this.coordinates = []; // Clear coordinates array\n this.adjacencyList = []; // Will not be populated during build; reserved for dynamic nodes post-build\n // Reset CSR structures (will rebuild below)\n this.csrOffsets = null;\n this.csrIndices = null;\n this.csrDistances = null;\n this.csrNodeCount = 0;\n\n // Hoist to locals for speed (avoid repeated property lookups in hot loops)\n const coordIndexMapLocal = this.coordinateIndexMap; // Local alias for coord map\n const coordsLocal = this.coordinates; // Local alias for coordinates array\n const measureDistance = this.distanceMeasurement; // Local alias for distance function\n\n // Pass 1: assign indices and count degrees per node\n const degree: number[] = []; // Dynamic degree array; grows as nodes are discovered\n this.forEachSegment(network, (a, b) => {\n const indexA = this.indexCoordinate(a, coordIndexMapLocal, coordsLocal);\n const indexB = this.indexCoordinate(b, coordIndexMapLocal, coordsLocal);\n\n degree[indexA] = (degree[indexA] ?? 0) + 1;\n degree[indexB] = (degree[indexB] ?? 0) + 1;\n });\n\n // Build CSR arrays from degree counts\n const nodeCount = this.coordinates.length; // Total nodes discovered\n this.csrNodeCount = nodeCount; // CSR covers all built nodes\n const offsets = new Int32Array(nodeCount + 1); // Row pointer array\n for (let i = 0; i < nodeCount; i++) {\n const deg = degree[i] ?? 0; // Degree of node i\n offsets[i + 1] = offsets[i] + deg; // Prefix sum\n }\n const totalEdges = offsets[nodeCount]; // Total adjacency entries\n const indices = new Int32Array(totalEdges); // Neighbor indices array\n const distances = new Float64Array(totalEdges); // Distances array aligned to indices\n\n // Pass 2: fill CSR arrays using a write cursor per node\n const cursor = offsets.slice(); // Current write positions per node\n this.forEachSegment(network, (a, b) => {\n // Read back indices (guaranteed to exist from pass 1)\n const indexA = this.coordinateIndexMap.get(a[0])!.get(a[1])!;\n const indexB = this.coordinateIndexMap.get(b[0])!.get(b[1])!;\n\n const segmentDistance = measureDistance(a, b); // Edge weight once\n\n let pos = cursor[indexA]++;\n indices[pos] = indexB;\n distances[pos] = segmentDistance;\n pos = cursor[indexB]++;\n indices[pos] = indexA;\n distances[pos] = segmentDistance;\n });\n\n // Commit CSR to instance\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n\n // Prepare sparse shell only for dynamically added nodes later (no prefilled neighbor arrays)\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Expands (merges) the existing graph with an additional LineString FeatureCollection.\n */\n public expandRouteGraph(network: FeatureCollection<LineString>): void {\n if (this.network === null) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Merge the feature arrays for reference/debugging. We avoid copying properties deeply.\n this.network = {\n type: \"FeatureCollection\",\n features: [...this.network.features, ...network.features],\n };\n\n const coordIndexMapLocal = this.coordinateIndexMap;\n const coordsLocal = this.coordinates;\n const measureDistance = this.distanceMeasurement;\n const adj = this.adjacencyList;\n\n // Ensure we have adjacency arrays for any existing CSR-only nodes.\n // (During buildRouteGraph, adjacencyList is sized but entries are undefined.)\n for (let i = 0; i < adj.length; i++) {\n if (adj[i] === undefined) adj[i] = [];\n }\n\n // Add new edges into the adjacency list (sparse), then rebuild CSR from adjacency.\n this.forEachSegment(network, (a, b) => {\n const indexA = this.indexCoordinate(a, coordIndexMapLocal, coordsLocal, (idx) => { adj[idx] = []; });\n const indexB = this.indexCoordinate(b, coordIndexMapLocal, coordsLocal, (idx) => { adj[idx] = []; });\n\n const segmentDistance = measureDistance(a, b);\n\n adj[indexA].push({ node: indexB, distance: segmentDistance });\n adj[indexB].push({ node: indexA, distance: segmentDistance });\n });\n\n this.rebuildCsrFromAdjacency();\n }\n\n /**\n * Rebuild CSR arrays for the full node set, using:\n * - Existing CSR edges (from the last build/expand)\n * - Any additional edges stored in `adjacencyList`\n */\n private rebuildCsrFromAdjacency(): void {\n const nodeCount = this.coordinates.length;\n const adj = this.adjacencyList;\n\n // Compute degree using CSR degree + adjacency degree\n const degree = new Int32Array(nodeCount);\n\n if (this.csrOffsets && this.csrIndices && this.csrDistances) {\n const csrOffsets = this.csrOffsets;\n const covered = Math.min(this.csrNodeCount, nodeCount);\n for (let i = 0; i < covered; i++) {\n degree[i] += (csrOffsets[i + 1] - csrOffsets[i]);\n }\n }\n\n for (let i = 0; i < nodeCount; i++) {\n const neighbors = adj[i];\n if (neighbors && neighbors.length) degree[i] += neighbors.length;\n }\n\n const offsets = new Int32Array(nodeCount + 1);\n for (let i = 0; i < nodeCount; i++) {\n offsets[i + 1] = offsets[i] + degree[i];\n }\n const totalEdges = offsets[nodeCount];\n const indices = new Int32Array(totalEdges);\n const distances = new Float64Array(totalEdges);\n const cursor = offsets.slice();\n\n // Copy existing CSR edges first\n if (this.csrOffsets && this.csrIndices && this.csrDistances) {\n const csrOffsets = this.csrOffsets;\n const csrIndices = this.csrIndices;\n const csrDistances = this.csrDistances;\n const covered = Math.min(this.csrNodeCount, nodeCount);\n for (let n = 0; n < covered; n++) {\n const startOff = csrOffsets[n];\n const endOff = csrOffsets[n + 1];\n let pos = cursor[n];\n for (let i = startOff; i < endOff; i++) {\n indices[pos] = csrIndices[i];\n distances[pos] = csrDistances[i];\n pos++;\n }\n cursor[n] = pos;\n }\n }\n\n // Append adjacency edges\n for (let n = 0; n < nodeCount; n++) {\n const neighbors = adj[n];\n if (!neighbors || neighbors.length === 0) continue;\n let pos = cursor[n];\n for (let i = 0, len = neighbors.length; i < len; i++) {\n const nb = neighbors[i];\n indices[pos] = nb.node;\n distances[pos] = nb.distance;\n pos++;\n }\n cursor[n] = pos;\n }\n\n // Commit and reset adjacency (we've absorbed edges into CSR)\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n this.csrNodeCount = nodeCount;\n\n // Keep adjacency list for *future* dynamic additions, but clear existing edges to avoid duplication.\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>, // Start point feature\n end: Feature<Point> // End point feature\n ): Feature<LineString> | null {\n if (this.network === null) { // Guard: graph must be built first\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Ensure start/end exist in index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates); // Get or insert start node index\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates); // Get or insert end node index\n\n // Trivial case: same node\n if (startIndex === endIndex) {\n return null; // No path needed\n }\n\n // Local aliases\n const coords = this.coordinates; // Alias to coordinates array\n const adj = this.adjacencyList; // Alias to sparse adjacency list (for dynamic nodes)\n\n // Ensure and init scratch buffers\n const nodeCount = coords.length; // Current number of nodes (may be >= csrNodeCount if new nodes added)\n this.ensureScratch(nodeCount); // Allocate scratch arrays if needed\n\n // Non-null after ensure\n const gF = this.gScoreScratch!; // forward gScore (from start)\n const gR = this.gScoreRevScratch!; // reverse gScore (from end)\n const prevF = this.cameFromScratch!; // predecessor in forward search\n const nextR = this.cameFromRevScratch!; // successor in reverse search (toward end)\n const visF = this.visitedScratch!;\n const visR = this.visitedRevScratch!;\n const hF = this.hScratch!;\n const hR = this.hRevScratch!;\n\n gF.fill(Number.POSITIVE_INFINITY, 0, nodeCount);\n gR.fill(Number.POSITIVE_INFINITY, 0, nodeCount);\n prevF.fill(-1, 0, nodeCount);\n nextR.fill(-1, 0, nodeCount);\n visF.fill(0, 0, nodeCount);\n visR.fill(0, 0, nodeCount);\n hF.fill(-1, 0, nodeCount);\n hR.fill(-1, 0, nodeCount);\n\n const openF = new this.heapConstructor();\n const openR = new this.heapConstructor();\n\n const startCoord = coords[startIndex];\n const endCoord = coords[endIndex];\n\n gF[startIndex] = 0;\n gR[endIndex] = 0;\n\n // Bidirectional Dijkstra (A* with zero heuristic). This keeps correctness simple and matches the\n // reference pathfinder while still saving work by meeting in the middle.\n openF.insert(0, startIndex);\n openR.insert(0, endIndex);\n\n // Best meeting point found so far\n let bestPathCost = Number.POSITIVE_INFINITY;\n let meetingNode = -1;\n\n // Main bidirectional loop: expand alternately.\n // Without a heap peek, a safe and effective stopping rule is based on the last extracted keys:\n // once min_g_forward + min_g_reverse >= bestPathCost, no shorter path can still be found.\n let lastExtractedGForward = 0;\n let lastExtractedGReverse = 0;\n while (openF.size() > 0 && openR.size() > 0) {\n if (meetingNode >= 0 && (lastExtractedGForward + lastExtractedGReverse) >= bestPathCost) {\n break;\n }\n\n // Expand one step from each side, prioritizing the smaller frontier.\n const expandForward = openF.size() <= openR.size();\n\n if (expandForward) {\n const current = openF.extractMin()!;\n if (visF[current] !== 0) continue;\n lastExtractedGForward = gF[current];\n visF[current] = 1;\n\n // If reverse has finalized this node, we have a candidate meeting.\n if (visR[current] !== 0) {\n const total = gF[current] + gR[current];\n if (total < bestPathCost) {\n bestPathCost = total;\n meetingNode = current;\n }\n }\n\n // Relax neighbors and push newly improved ones\n if (this.csrOffsets && current < this.csrNodeCount) {\n const csrOffsets = this.csrOffsets!;\n const csrIndices = this.csrIndices!;\n const csrDistances = this.csrDistances!;\n for (let i = csrOffsets[current], endOff = csrOffsets[current + 1]; i < endOff; i++) {\n const nbNode = csrIndices[i];\n const tentativeG = gF[current] + csrDistances[i];\n if (tentativeG < gF[nbNode]) {\n gF[nbNode] = tentativeG;\n prevF[nbNode] = current;\n const otherG = gR[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openF.insert(tentativeG, nbNode);\n }\n }\n } else {\n const neighbors = adj[current];\n if (neighbors && neighbors.length) {\n for (let i = 0, n = neighbors.length; i < n; i++) {\n const nb = neighbors[i];\n const nbNode = nb.node;\n const tentativeG = gF[current] + nb.distance;\n if (tentativeG < gF[nbNode]) {\n gF[nbNode] = tentativeG;\n prevF[nbNode] = current;\n const otherG = gR[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openF.insert(tentativeG, nbNode);\n }\n }\n }\n }\n } else {\n const current = openR.extractMin()!;\n if (visR[current] !== 0) continue;\n lastExtractedGReverse = gR[current];\n visR[current] = 1;\n\n if (visF[current] !== 0) {\n const total = gF[current] + gR[current];\n if (total < bestPathCost) {\n bestPathCost = total;\n meetingNode = current;\n }\n }\n\n // Reverse direction: same neighbor iteration because graph is undirected.\n // Store successor pointer (next step toward end) i.e. nextR[neighbor] = current.\n if (this.csrOffsets && current < this.csrNodeCount) {\n const csrOffsets = this.csrOffsets!;\n const csrIndices = this.csrIndices!;\n const csrDistances = this.csrDistances!;\n for (let i = csrOffsets[current], endOff = csrOffsets[current + 1]; i < endOff; i++) {\n const nbNode = csrIndices[i];\n const tentativeG = gR[current] + csrDistances[i];\n if (tentativeG < gR[nbNode]) {\n gR[nbNode] = tentativeG;\n nextR[nbNode] = current;\n const otherG = gF[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openR.insert(tentativeG, nbNode);\n }\n }\n } else {\n const neighbors = adj[current];\n if (neighbors && neighbors.length) {\n for (let i = 0, n = neighbors.length; i < n; i++) {\n const nb = neighbors[i];\n const nbNode = nb.node;\n const tentativeG = gR[current] + nb.distance;\n if (tentativeG < gR[nbNode]) {\n gR[nbNode] = tentativeG;\n nextR[nbNode] = current;\n const otherG = gF[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openR.insert(tentativeG, nbNode);\n }\n }\n }\n }\n }\n }\n\n if (meetingNode < 0) {\n return null;\n }\n\n // Reconstruct path: start -> meeting using prevF, then meeting -> end using nextR\n const path: Position[] = [];\n\n // Walk back from meeting to start, collecting nodes\n let cur = meetingNode;\n while (cur !== startIndex && cur >= 0) {\n path.push(coords[cur]);\n cur = prevF[cur];\n }\n if (cur !== startIndex) {\n // Forward tree doesn't connect start to meeting (shouldn't happen if meeting is valid)\n return null;\n }\n path.push(coords[startIndex]);\n path.reverse();\n\n // Walk from meeting to end (skip meeting node because it's already included)\n cur = meetingNode;\n while (cur !== endIndex) {\n cur = nextR[cur];\n if (cur < 0) {\n return null;\n }\n path.push(coords[cur]);\n }\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number { // Ensure a coordinate has a node index, creating if absent\n const lng = coord[0]; // Extract longitude\n const lat = coord[1]; // Extract latitude\n\n let latMap = this.coordinateIndexMap.get(lng); // Get lat→index map for this longitude\n if (latMap === undefined) { // Create if missing\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n\n let index = latMap.get(lat); // Lookup index by latitude\n if (index === undefined) { // If not found, append new node\n\n index = this.coordinates.length; // New index at end\n this.coordinates.push(coord); // Store coordinate\n latMap.set(lat, index); // Record mapping\n\n // Ensure sparse adjacency slot for dynamically added nodes\n this.adjacencyList[index] = []; // Init empty neighbor array\n\n // Extend CSR offsets to keep indices consistent (no neighbors for new node)\n if (this.csrOffsets) { // Only adjust if CSR already built\n\n // Only need to expand offsets by one; indices/distances remain unchanged\n const oldCount = this.csrNodeCount; // Nodes currently covered by CSR\n\n // Appending exactly one new node at the end\n if (index === oldCount) {\n const newOffsets = new Int32Array(oldCount + 2); // Allocate offsets for +1 node\n newOffsets.set(this.csrOffsets, 0); // Copy previous offsets\n\n // Last offset repeats to indicate zero neighbors\n newOffsets[oldCount + 1] = newOffsets[oldCount]; // Replicate last pointer\n this.csrOffsets = newOffsets; // Swap in new offsets\n this.csrNodeCount = oldCount + 1; // Increment CSR node count\n }\n }\n }\n\n return index;\n }\n\n // Ensure scratch arrays are allocated with at least `size` capacity.\n private ensureScratch(size: number): void {\n const ifAlreadyBigEnough = this.scratchCapacity >= size\n && this.gScoreScratch\n && this.cameFromScratch\n && this.visitedScratch\n && this.hScratch\n && this.gScoreRevScratch\n && this.cameFromRevScratch\n && this.visitedRevScratch\n && this.hRevScratch;\n\n if (ifAlreadyBigEnough) {\n return; // Nothing to do\n }\n const capacity = size | 0; // Ensure integer\n this.gScoreScratch = new Float64Array(capacity);\n this.cameFromScratch = new Int32Array(capacity);\n this.visitedScratch = new Uint8Array(capacity);\n this.hScratch = new Float64Array(capacity);\n this.gScoreRevScratch = new Float64Array(capacity);\n this.cameFromRevScratch = new Int32Array(capacity);\n this.visitedRevScratch = new Uint8Array(capacity);\n this.hRevScratch = new Float64Array(capacity);\n this.scratchCapacity = capacity;\n }\n\n\n // Iterate all consecutive segment pairs in a LineString FeatureCollection.\n // Kept as a simple loop helper to avoid repeating nested iteration logic.\n private forEachSegment(\n network: FeatureCollection<LineString>,\n fn: (a: Position, b: Position) => void,\n ): void {\n const features = network.features;\n for (let f = 0, fLen = features.length; f < fLen; f++) {\n const lineCoords = features[f].geometry.coordinates;\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) {\n // GeoJSON coordinates are compatible with Position (number[]), and the project assumes [lng,lat]\n fn(lineCoords[i] as Position, lineCoords[i + 1] as Position);\n }\n }\n }\n\n // Hot-path coordinate indexer used by both build/expand.\n // Accepts explicit maps/arrays so callers can hoist them once.\n private indexCoordinate(\n coord: Position,\n coordIndexMapLocal: Map<number, Map<number, number>>,\n coordsLocal: Position[],\n onNewIndex?: (index: number) => void,\n ): number {\n const lng = coord[0];\n const lat = coord[1];\n\n let latMap = coordIndexMapLocal.get(lng);\n if (latMap === undefined) {\n latMap = new Map<number, number>();\n coordIndexMapLocal.set(lng, latMap);\n }\n\n let idx = latMap.get(lat);\n if (idx === undefined) {\n idx = coordsLocal.length;\n coordsLocal.push(coord);\n latMap.set(lat, idx);\n if (onNewIndex) onNewIndex(idx);\n }\n\n return idx;\n }\n\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance } ","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}"],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","FourAryHeap","keys","this","values","idxs","length","insertCounter","_proto","prototype","insert","key","value","i","ck","cv","ci","p","pk","pi","extractMin","n","minValue","last","bubbleDown","size","k","v","idx","nodeK","nodeV","nodeI","c1","smallest","sK","sI","sV","c2","k2","i2","c3","k3","i3","c4","k4","i4","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","csrOffsets","csrIndices","csrDistances","csrNodeCount","gScoreScratch","cameFromScratch","visitedScratch","hScratch","gScoreRevScratch","cameFromRevScratch","visitedRevScratch","hRevScratch","scratchCapacity","heap","buildRouteGraph","_this","coordIndexMapLocal","coordsLocal","measureDistance","degree","forEachSegment","b","_degree$indexA","_degree$indexB","indexA","indexCoordinate","indexB","nodeCount","offsets","Int32Array","_degree$i","deg","totalEdges","indices","distances","Float64Array","cursor","slice","get","segmentDistance","pos","Array","expandRouteGraph","_this2","Error","type","features","concat","adj","undefined","push","node","distance","rebuildCsrFromAdjacency","covered","min","neighbors","endOff","len","nb","getRoute","start","end","startIndex","getOrCreateIndex","geometry","endIndex","coords","ensureScratch","gF","gR","prevF","nextR","visF","visR","hF","hR","fill","Number","POSITIVE_INFINITY","openF","openR","bestPathCost","meetingNode","lastExtractedGForward","lastExtractedGReverse","current","total","nbNode","tentativeG","otherG","path","cur","reverse","properties","coord","lng","lat","latMap","set","index","oldCount","newOffsets","capacity","Uint8Array","fn","f","fLen","lineCoords","onNewIndex","FE","E2","RAD","cosLat","w2","w","m","kx","ky","deltaLng","dx","dy"],"mappings":"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"}
@@ -4,6 +4,7 @@ import { createCheapRuler } from "./distance/cheap-ruler";
4
4
  import { HeapConstructor } from "./heap/heap";
5
5
  interface Router {
6
6
  buildRouteGraph(network: FeatureCollection<LineString>): void;
7
+ expandRouteGraph(network: FeatureCollection<LineString>): void;
7
8
  getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;
8
9
  }
9
10
  declare class TerraRoute implements Router {
@@ -21,6 +22,10 @@ declare class TerraRoute implements Router {
21
22
  private cameFromScratch;
22
23
  private visitedScratch;
23
24
  private hScratch;
25
+ private gScoreRevScratch;
26
+ private cameFromRevScratch;
27
+ private visitedRevScratch;
28
+ private hRevScratch;
24
29
  private scratchCapacity;
25
30
  constructor(options?: {
26
31
  distanceMeasurement?: (a: Position, b: Position) => number;
@@ -31,6 +36,16 @@ declare class TerraRoute implements Router {
31
36
  * Two-pass build: pass 1 assigns node indices and counts degrees; pass 2 fills CSR arrays.
32
37
  */
33
38
  buildRouteGraph(network: FeatureCollection<LineString>): void;
39
+ /**
40
+ * Expands (merges) the existing graph with an additional LineString FeatureCollection.
41
+ */
42
+ expandRouteGraph(network: FeatureCollection<LineString>): void;
43
+ /**
44
+ * Rebuild CSR arrays for the full node set, using:
45
+ * - Existing CSR edges (from the last build/expand)
46
+ * - Any additional edges stored in `adjacencyList`
47
+ */
48
+ private rebuildCsrFromAdjacency;
34
49
  /**
35
50
  * Computes the shortest route between two points in the network using the A* algorithm.
36
51
  *
@@ -47,5 +62,7 @@ declare class TerraRoute implements Router {
47
62
  */
48
63
  private getOrCreateIndex;
49
64
  private ensureScratch;
65
+ private forEachSegment;
66
+ private indexCoordinate;
50
67
  }
51
68
  export { TerraRoute, createCheapRuler, haversineDistance };
@@ -1,2 +1,2 @@
1
- const t=(t,s)=>{const e=t=>t*Math.PI/180,i=e(t[1]),n=e(t[0]),r=e(s[1]),c=r-i,h=e(s[0])-n,o=Math.sin(c/2)*Math.sin(c/2)+Math.cos(i)*Math.cos(r)*Math.sin(h/2)*Math.sin(h/2);return 2*Math.atan2(Math.sqrt(o),Math.sqrt(1-o))*6371e3/1e3};function s(t){const s=1/298.257223563,e=s*(2-s),i=Math.PI/180,n=Math.cos(t*i),r=1/(1-e*(1-n*n)),c=Math.sqrt(r),h=6378.137*i,o=h*c*n,a=h*c*r*(1-e);return function(t,s){let e=t[0]-s[0];for(;e<-180;)e+=360;for(;e>180;)e-=360;const i=e*o,n=(t[1]-s[1])*a;return Math.sqrt(i*i+n*n)}}class e{constructor(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}insert(t,s){let e=this.length;this.length=e+1;let i=t,n=s,r=this.insertCounter++;for(;e>0;){const t=e-1>>>2,s=this.keys[t],n=this.idxs[t];if(i>s||i===s&&r>n)break;this.keys[e]=s,this.values[e]=this.values[t],this.idxs[e]=n,e=t}this.keys[e]=i,this.values[e]=n,this.idxs[e]=r}extractMin(){const t=this.length;if(0===t)return null;const s=this.values[0],e=t-1;return this.length=e,e>0&&(this.keys[0]=this.keys[e],this.values[0]=this.values[e],this.idxs[0]=this.idxs[e],this.bubbleDown(0)),s}size(){return this.length}bubbleDown(t){const s=this.length,e=this.keys,i=this.values,n=this.idxs,r=e[t],c=i[t],h=n[t];for(;;){const c=1+(t<<2);if(c>=s)break;let o=c,a=e[c],l=n[c],u=i[c];const d=c+1;if(d<s){const t=e[d],s=n[d];(t<a||t===a&&s<l)&&(o=d,a=t,l=s,u=i[d])}const f=c+2;if(f<s){const t=e[f],s=n[f];(t<a||t===a&&s<l)&&(o=f,a=t,l=s,u=i[f])}const g=c+3;if(g<s){const t=e[g],s=n[g];(t<a||t===a&&s<l)&&(o=g,a=t,l=s,u=i[g])}if(!(a<r||a===r&&l<h))break;e[t]=a,i[t]=u,n[t]=l,t=o}e[t]=r,i[t]=c,n[t]=h}}class i{constructor(s){var i,n;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(i=null==s?void 0:s.distanceMeasurement)?i:t,this.heapConstructor=null!=(n=null==s?void 0:s.heap)?n:e}buildRouteGraph(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;const s=this.coordinateIndexMap,e=this.coordinates,i=this.distanceMeasurement,n=t.features,r=[];for(let t=0,i=n.length;t<i;t++){const i=n[t].geometry.coordinates;for(let t=0,n=i.length-1;t<n;t++){var c,h;const n=i[t],o=i[t+1],a=n[0],l=n[1],u=o[0],d=o[1];let f=s.get(a);void 0===f&&(f=new Map,s.set(a,f));let g=f.get(l);void 0===g&&(g=e.length,e.push(n),f.set(l,g));let M=s.get(u);void 0===M&&(M=new Map,s.set(u,M));let p=M.get(d);void 0===p&&(p=e.length,e.push(o),M.set(d,p)),r[g]=(null!=(c=r[g])?c:0)+1,r[p]=(null!=(h=r[p])?h:0)+1}}const o=this.coordinates.length;this.csrNodeCount=o;const a=new Int32Array(o+1);for(let t=0;t<o;t++){var l;const s=null!=(l=r[t])?l:0;a[t+1]=a[t]+s}const u=a[o],d=new Int32Array(u),f=new Float64Array(u),g=a.slice();for(let t=0,s=n.length;t<s;t++){const s=n[t].geometry.coordinates;for(let t=0,e=s.length-1;t<e;t++){const e=s[t],n=s[t+1],r=e[1],c=n[0],h=n[1],o=this.coordinateIndexMap.get(e[0]).get(r),a=this.coordinateIndexMap.get(c).get(h),l=i(e,n);let u=g[o]++;d[u]=a,f[u]=l,u=g[a]++,d[u]=o,f[u]=l}}this.csrOffsets=a,this.csrIndices=d,this.csrDistances=f,this.adjacencyList=new Array(o)}getRoute(t,s){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");const e=this.getOrCreateIndex(t.geometry.coordinates),i=this.getOrCreateIndex(s.geometry.coordinates);if(e===i)return null;const n=this.coordinates,r=this.adjacencyList,c=this.distanceMeasurement,h=n.length;this.ensureScratch(h);const o=this.gScoreScratch,a=this.cameFromScratch,l=this.visitedScratch,u=this.hScratch;o.fill(Number.POSITIVE_INFINITY,0,h),a.fill(-1,0,h),l.fill(0,0,h),u.fill(-1,0,h);const d=new this.heapConstructor,f=n[i];let g=u[e];for(g<0&&(g=c(n[e],f),u[e]=g),d.insert(g,e),o[e]=0;d.size()>0;){const t=d.extractMin();if(0===l[t]){if(t===i)break;if(l[t]=1,this.csrOffsets&&t<this.csrNodeCount){const s=this.csrOffsets,e=this.csrIndices,i=this.csrDistances,r=s[t+1];for(let h=s[t];h<r;h++){const s=e[h],r=o[t]+i[h];if(r<o[s]){o[s]=r,a[s]=t;let e=u[s];e<0&&(e=c(n[s],f),u[s]=e),d.insert(r+e,s)}}}else{const s=r[t];if(!s||0===s.length)continue;for(let e=0,i=s.length;e<i;e++){const i=s[e],r=i.node,h=o[t]+i.distance;if(h<o[r]){o[r]=h,a[r]=t;let s=u[r];s<0&&(s=c(n[r],f),u[r]=s),d.insert(h+s,r)}}}}}if(a[i]<0)return null;const M=[];let p=i;for(;p!==e;)M.push(n[p]),p=a[p];return M.push(n[e]),M.reverse(),{type:"Feature",geometry:{type:"LineString",coordinates:M},properties:{}}}getOrCreateIndex(t){const s=t[0],e=t[1];let i=this.coordinateIndexMap.get(s);void 0===i&&(i=new Map,this.coordinateIndexMap.set(s,i));let n=i.get(e);if(void 0===n&&(n=this.coordinates.length,this.coordinates.push(t),i.set(e,n),this.adjacencyList[n]=[],this.csrOffsets)){const t=this.csrNodeCount;if(n===t){const s=new Int32Array(t+2);s.set(this.csrOffsets,0),s[t+1]=s[t],this.csrOffsets=s,this.csrNodeCount=t+1}}return n}ensureScratch(t){if(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch)return;const s=0|t;this.gScoreScratch=new Float64Array(s),this.cameFromScratch=new Int32Array(s),this.visitedScratch=new Uint8Array(s),this.hScratch=new Float64Array(s),this.scratchCapacity=s}}export{i as TerraRoute,s as createCheapRuler,t as haversineDistance};
1
+ const t=(t,s)=>{const e=t=>t*Math.PI/180,i=e(t[1]),n=e(t[0]),r=e(s[1]),c=r-i,h=e(s[0])-n,o=Math.sin(c/2)*Math.sin(c/2)+Math.cos(i)*Math.cos(r)*Math.sin(h/2)*Math.sin(h/2);return 2*Math.atan2(Math.sqrt(o),Math.sqrt(1-o))*6371e3/1e3};function s(t){const s=1/298.257223563,e=s*(2-s),i=Math.PI/180,n=Math.cos(t*i),r=1/(1-e*(1-n*n)),c=Math.sqrt(r),h=6378.137*i,o=h*c*n,a=h*c*r*(1-e);return function(t,s){let e=t[0]-s[0];for(;e<-180;)e+=360;for(;e>180;)e-=360;const i=e*o,n=(t[1]-s[1])*a;return Math.sqrt(i*i+n*n)}}class e{constructor(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}insert(t,s){let e=this.length;this.length=e+1;let i=t,n=s,r=this.insertCounter++;for(;e>0;){const t=e-1>>>2,s=this.keys[t],n=this.idxs[t];if(i>s||i===s&&r>n)break;this.keys[e]=s,this.values[e]=this.values[t],this.idxs[e]=n,e=t}this.keys[e]=i,this.values[e]=n,this.idxs[e]=r}extractMin(){const t=this.length;if(0===t)return null;const s=this.values[0],e=t-1;return this.length=e,e>0&&(this.keys[0]=this.keys[e],this.values[0]=this.values[e],this.idxs[0]=this.idxs[e],this.bubbleDown(0)),s}size(){return this.length}bubbleDown(t){const s=this.length,e=this.keys,i=this.values,n=this.idxs,r=e[t],c=i[t],h=n[t];for(;;){const c=1+(t<<2);if(c>=s)break;let o=c,a=e[c],l=n[c],d=i[c];const u=c+1;if(u<s){const t=e[u],s=n[u];(t<a||t===a&&s<l)&&(o=u,a=t,l=s,d=i[u])}const f=c+2;if(f<s){const t=e[f],s=n[f];(t<a||t===a&&s<l)&&(o=f,a=t,l=s,d=i[f])}const I=c+3;if(I<s){const t=e[I],s=n[I];(t<a||t===a&&s<l)&&(o=I,a=t,l=s,d=i[I])}if(!(a<r||a===r&&l<h))break;e[t]=a,i[t]=d,n[t]=l,t=o}e[t]=r,i[t]=c,n[t]=h}}class i{constructor(s){var i,n;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.gScoreRevScratch=null,this.cameFromRevScratch=null,this.visitedRevScratch=null,this.hRevScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(i=null==s?void 0:s.distanceMeasurement)?i:t,this.heapConstructor=null!=(n=null==s?void 0:s.heap)?n:e}buildRouteGraph(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;const s=this.coordinateIndexMap,e=this.coordinates,i=this.distanceMeasurement,n=[];this.forEachSegment(t,(t,i)=>{var r,c;const h=this.indexCoordinate(t,s,e),o=this.indexCoordinate(i,s,e);n[h]=(null!=(r=n[h])?r:0)+1,n[o]=(null!=(c=n[o])?c:0)+1});const r=this.coordinates.length;this.csrNodeCount=r;const c=new Int32Array(r+1);for(let t=0;t<r;t++){var h;const s=null!=(h=n[t])?h:0;c[t+1]=c[t]+s}const o=c[r],a=new Int32Array(o),l=new Float64Array(o),d=c.slice();this.forEachSegment(t,(t,s)=>{const e=this.coordinateIndexMap.get(t[0]).get(t[1]),n=this.coordinateIndexMap.get(s[0]).get(s[1]),r=i(t,s);let c=d[e]++;a[c]=n,l[c]=r,c=d[n]++,a[c]=e,l[c]=r}),this.csrOffsets=c,this.csrIndices=a,this.csrDistances=l,this.adjacencyList=new Array(r)}expandRouteGraph(t){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");this.network={type:"FeatureCollection",features:[...this.network.features,...t.features]};const s=this.coordinateIndexMap,e=this.coordinates,i=this.distanceMeasurement,n=this.adjacencyList;for(let t=0;t<n.length;t++)void 0===n[t]&&(n[t]=[]);this.forEachSegment(t,(t,r)=>{const c=this.indexCoordinate(t,s,e,t=>{n[t]=[]}),h=this.indexCoordinate(r,s,e,t=>{n[t]=[]}),o=i(t,r);n[c].push({node:h,distance:o}),n[h].push({node:c,distance:o})}),this.rebuildCsrFromAdjacency()}rebuildCsrFromAdjacency(){const t=this.coordinates.length,s=this.adjacencyList,e=new Int32Array(t);if(this.csrOffsets&&this.csrIndices&&this.csrDistances){const s=this.csrOffsets,i=Math.min(this.csrNodeCount,t);for(let t=0;t<i;t++)e[t]+=s[t+1]-s[t]}for(let i=0;i<t;i++){const t=s[i];t&&t.length&&(e[i]+=t.length)}const i=new Int32Array(t+1);for(let s=0;s<t;s++)i[s+1]=i[s]+e[s];const n=i[t],r=new Int32Array(n),c=new Float64Array(n),h=i.slice();if(this.csrOffsets&&this.csrIndices&&this.csrDistances){const s=this.csrOffsets,e=this.csrIndices,i=this.csrDistances,n=Math.min(this.csrNodeCount,t);for(let t=0;t<n;t++){const n=s[t+1];let o=h[t];for(let h=s[t];h<n;h++)r[o]=e[h],c[o]=i[h],o++;h[t]=o}}for(let e=0;e<t;e++){const t=s[e];if(!t||0===t.length)continue;let i=h[e];for(let s=0,e=t.length;s<e;s++){const e=t[s];r[i]=e.node,c[i]=e.distance,i++}h[e]=i}this.csrOffsets=i,this.csrIndices=r,this.csrDistances=c,this.csrNodeCount=t,this.adjacencyList=new Array(t)}getRoute(t,s){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");const e=this.getOrCreateIndex(t.geometry.coordinates),i=this.getOrCreateIndex(s.geometry.coordinates);if(e===i)return null;const n=this.coordinates,r=this.adjacencyList,c=n.length;this.ensureScratch(c);const h=this.gScoreScratch,o=this.gScoreRevScratch,a=this.cameFromScratch,l=this.cameFromRevScratch,d=this.visitedScratch,u=this.visitedRevScratch,f=this.hScratch,I=this.hRevScratch;h.fill(Number.POSITIVE_INFINITY,0,c),o.fill(Number.POSITIVE_INFINITY,0,c),a.fill(-1,0,c),l.fill(-1,0,c),d.fill(0,0,c),u.fill(0,0,c),f.fill(-1,0,c),I.fill(-1,0,c);const S=new this.heapConstructor,g=new this.heapConstructor;h[e]=0,o[i]=0,S.insert(0,e),g.insert(0,i);let v=Number.POSITIVE_INFINITY,y=-1,m=0,w=0;for(;S.size()>0&&g.size()>0&&!(y>=0&&m+w>=v);)if(S.size()<=g.size()){const t=S.extractMin();if(0!==d[t])continue;if(m=h[t],d[t]=1,0!==u[t]){const s=h[t]+o[t];s<v&&(v=s,y=t)}if(this.csrOffsets&&t<this.csrNodeCount){const s=this.csrOffsets,e=this.csrIndices,i=this.csrDistances;for(let n=s[t],r=s[t+1];n<r;n++){const s=e[n],r=h[t]+i[n];if(r<h[s]){h[s]=r,a[s]=t;const e=o[s];if(e!==Number.POSITIVE_INFINITY){const t=r+e;t<v&&(v=t,y=s)}S.insert(r,s)}}}else{const s=r[t];if(s&&s.length)for(let e=0,i=s.length;e<i;e++){const i=s[e],n=i.node,r=h[t]+i.distance;if(r<h[n]){h[n]=r,a[n]=t;const s=o[n];if(s!==Number.POSITIVE_INFINITY){const t=r+s;t<v&&(v=t,y=n)}S.insert(r,n)}}}}else{const t=g.extractMin();if(0!==u[t])continue;if(w=o[t],u[t]=1,0!==d[t]){const s=h[t]+o[t];s<v&&(v=s,y=t)}if(this.csrOffsets&&t<this.csrNodeCount){const s=this.csrOffsets,e=this.csrIndices,i=this.csrDistances;for(let n=s[t],r=s[t+1];n<r;n++){const s=e[n],r=o[t]+i[n];if(r<o[s]){o[s]=r,l[s]=t;const e=h[s];if(e!==Number.POSITIVE_INFINITY){const t=r+e;t<v&&(v=t,y=s)}g.insert(r,s)}}}else{const s=r[t];if(s&&s.length)for(let e=0,i=s.length;e<i;e++){const i=s[e],n=i.node,r=o[t]+i.distance;if(r<o[n]){o[n]=r,l[n]=t;const s=h[n];if(s!==Number.POSITIVE_INFINITY){const t=r+s;t<v&&(v=t,y=n)}g.insert(r,n)}}}}if(y<0)return null;const p=[];let M=y;for(;M!==e&&M>=0;)p.push(n[M]),M=a[M];if(M!==e)return null;for(p.push(n[e]),p.reverse(),M=y;M!==i;){if(M=l[M],M<0)return null;p.push(n[M])}return{type:"Feature",geometry:{type:"LineString",coordinates:p},properties:{}}}getOrCreateIndex(t){const s=t[0],e=t[1];let i=this.coordinateIndexMap.get(s);void 0===i&&(i=new Map,this.coordinateIndexMap.set(s,i));let n=i.get(e);if(void 0===n&&(n=this.coordinates.length,this.coordinates.push(t),i.set(e,n),this.adjacencyList[n]=[],this.csrOffsets)){const t=this.csrNodeCount;if(n===t){const s=new Int32Array(t+2);s.set(this.csrOffsets,0),s[t+1]=s[t],this.csrOffsets=s,this.csrNodeCount=t+1}}return n}ensureScratch(t){if(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch&&this.gScoreRevScratch&&this.cameFromRevScratch&&this.visitedRevScratch&&this.hRevScratch)return;const s=0|t;this.gScoreScratch=new Float64Array(s),this.cameFromScratch=new Int32Array(s),this.visitedScratch=new Uint8Array(s),this.hScratch=new Float64Array(s),this.gScoreRevScratch=new Float64Array(s),this.cameFromRevScratch=new Int32Array(s),this.visitedRevScratch=new Uint8Array(s),this.hRevScratch=new Float64Array(s),this.scratchCapacity=s}forEachSegment(t,s){const e=t.features;for(let t=0,i=e.length;t<i;t++){const i=e[t].geometry.coordinates;for(let t=0,e=i.length-1;t<e;t++)s(i[t],i[t+1])}}indexCoordinate(t,s,e,i){const n=t[0],r=t[1];let c=s.get(n);void 0===c&&(c=new Map,s.set(n,c));let h=c.get(r);return void 0===h&&(h=e.length,e.push(t),c.set(r,h),i&&i(h)),h}}export{i as TerraRoute,s as createCheapRuler,t as haversineDistance};
2
2
  //# sourceMappingURL=terra-route.modern.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.modern.js","sources":["../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/heap/four-ary-heap.ts","../src/terra-route.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n\n // Convert distance from meters to kilometers\n return distance / 1000;\n}\n","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}","import { Heap } from \"./heap\";\n\ninterface Node {\n key: number;\n value: number;\n index: number; // insertion order for stable tie-breaking\n}\n\n/**\n * A 4-ary min-heap with stable tie-breaking on insertion order.\n * Parent(i) = floor((i - 1) / 4)\n * Children(i) = 4*i + 1 .. 4*i + 4\n */\nexport class FourAryHeap implements Heap {\n // Parallel arrays for fewer object allocations and faster property access\n private keys: number[] = [];\n private values: number[] = [];\n private idxs: number[] = [];\n private length = 0; // number of valid elements\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n // Bubble-up using local variables (avoid temporary node object)\n let i = this.length;\n this.length = i + 1;\n\n let ck = key;\n let cv = value;\n let ci = this.insertCounter++;\n\n while (i > 0) {\n const p = (i - 1) >>> 2; // divide by 4\n const pk = this.keys[p];\n const pi = this.idxs[p];\n if (ck > pk || (ck === pk && ci > pi)) break;\n\n // move parent down\n this.keys[i] = pk;\n this.values[i] = this.values[p];\n this.idxs[i] = pi;\n i = p;\n }\n\n // place the new node\n this.keys[i] = ck;\n this.values[i] = cv;\n this.idxs[i] = ci;\n }\n\n extractMin(): number | null {\n const n = this.length;\n if (n === 0) return null;\n\n const minValue = this.values[0];\n const last = n - 1;\n this.length = last;\n\n if (last > 0) {\n // Move last element to root then bubble down\n this.keys[0] = this.keys[last];\n this.values[0] = this.values[last];\n this.idxs[0] = this.idxs[last];\n this.bubbleDown(0);\n }\n\n return minValue;\n }\n\n size(): number {\n return this.length;\n }\n\n private bubbleDown(i: number): void {\n const n = this.length;\n const k = this.keys;\n const v = this.values;\n const idx = this.idxs;\n\n const nodeK = k[i];\n const nodeV = v[i];\n const nodeI = idx[i];\n\n while (true) {\n const c1 = (i << 2) + 1; // 4*i + 1\n if (c1 >= n) break; // no children\n\n // find smallest among up to 4 children\n let smallest = c1;\n let sK = k[c1];\n let sI = idx[c1];\n let sV = v[c1];\n\n const c2 = c1 + 1;\n if (c2 < n) {\n const k2 = k[c2];\n const i2 = idx[c2];\n if (k2 < sK || (k2 === sK && i2 < sI)) {\n smallest = c2;\n sK = k2;\n sI = i2;\n sV = v[c2];\n }\n }\n\n const c3 = c1 + 2;\n if (c3 < n) {\n const k3 = k[c3];\n const i3 = idx[c3];\n if (k3 < sK || (k3 === sK && i3 < sI)) {\n smallest = c3;\n sK = k3;\n sI = i3;\n sV = v[c3];\n }\n }\n\n const c4 = c1 + 3;\n if (c4 < n) {\n const k4 = k[c4];\n const i4 = idx[c4];\n if (k4 < sK || (k4 === sK && i4 < sI)) {\n smallest = c4;\n sK = k4;\n sI = i4;\n sV = v[c4];\n }\n }\n\n if (sK < nodeK || (sK === nodeK && sI < nodeI)) {\n k[i] = sK;\n v[i] = sV;\n idx[i] = sI;\n i = smallest;\n } else {\n break;\n }\n }\n\n k[i] = nodeK;\n v[i] = nodeV;\n idx[i] = nodeI;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\"; // Import GeoJSON types\nimport { haversineDistance } from \"./distance/haversine\"; // Great-circle distance function (default heuristic/edge weight)\nimport { createCheapRuler } from \"./distance/cheap-ruler\"; // Factory for faster planar distance (exported for consumers)\nimport { HeapConstructor } from \"./heap/heap\"; // Heap interface so users can plug custom heaps\nimport { FourAryHeap } from \"./heap/four-ary-heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null; // The last network used to build the graph\n private distanceMeasurement: (a: Position, b: Position) => number; // Distance function used for edges and heuristic\n private heapConstructor: HeapConstructor; // Heap class used by A*\n\n // Map from longitude → (map from latitude → index) to deduplicate coordinates and get node indices quickly\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map(); // Nested map for exact coord lookup\n private coordinates: Position[] = []; // Array of all unique coordinates by index\n // Sparse adjacency list used during build and for any nodes added dynamically later\n private adjacencyList: Array<Array<{ node: number, distance: number }>> = []; // Per-node neighbor arrays used only pre-CSR or for dynamic nodes\n\n // Compressed Sparse Row adjacency representation for fast neighbor iteration in getRoute\n private csrOffsets: Int32Array | null = null; // Row pointer: length = nodeCount + 1, offsets into indices/distances\n private csrIndices: Int32Array | null = null; // Column indices: neighbor node IDs, length = totalEdges\n private csrDistances: Float64Array | null = null; // Edge weights aligned to csrIndices, length = totalEdges\n private csrNodeCount = 0; // Number of nodes captured in the CSR arrays\n\n // Reusable typed scratch buffers for A*\n private gScoreScratch: Float64Array | null = null; // gScore per node (cost from start)\n private cameFromScratch: Int32Array | null = null; // Predecessor per node for path reconstruction\n private visitedScratch: Uint8Array | null = null; // Visited set to avoid reprocessing\n private hScratch: Float64Array | null = null; // Per-node heuristic cache for the current query (lazy compute)\n private scratchCapacity = 0; // Current capacity of scratch arrays\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number; // Optional distance function override\n heap?: HeapConstructor; // Optional heap implementation override\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance; // Default to haversine\n this.heapConstructor = options?.heap ?? FourAryHeap; // Default to MinHeap\n }\n\n /**\n * Builds a graph (CSR) from a LineString FeatureCollection.\n * Two-pass build: pass 1 assigns node indices and counts degrees; pass 2 fills CSR arrays.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network; // Keep a reference to the network\n\n // Reset internal structures for a fresh build\n this.coordinateIndexMap = new Map(); // Clear coordinate index map\n this.coordinates = []; // Clear coordinates array\n this.adjacencyList = []; // Will not be populated during build; reserved for dynamic nodes post-build\n // Reset CSR structures (will rebuild below)\n this.csrOffsets = null;\n this.csrIndices = null;\n this.csrDistances = null;\n this.csrNodeCount = 0;\n\n // Hoist to locals for speed (avoid repeated property lookups in hot loops)\n const coordIndexMapLocal = this.coordinateIndexMap; // Local alias for coord map\n const coordsLocal = this.coordinates; // Local alias for coordinates array\n const measureDistance = this.distanceMeasurement; // Local alias for distance function\n\n const features = network.features; // All LineString features\n\n // Pass 1: assign indices and count degrees per node\n const degree: number[] = []; // Dynamic degree array; grows as nodes are discovered\n for (let f = 0, fLen = features.length; f < fLen; f++) { // Iterate features\n const feature = features[f]; // Current feature\n const lineCoords = feature.geometry.coordinates; // Coordinates for this LineString\n\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) { // Iterate segment pairs\n const a = lineCoords[i]; // Segment start coord\n const b = lineCoords[i + 1]; // Segment end coord\n\n const lngA = a[0], latA = a[1]; // Keys for A\n const lngB = b[0], latB = b[1]; // Keys for B\n\n // Index A\n let latMapA = coordIndexMapLocal.get(lngA);\n if (latMapA === undefined) {\n latMapA = new Map<number, number>();\n coordIndexMapLocal.set(lngA, latMapA);\n }\n let indexA = latMapA.get(latA);\n if (indexA === undefined) {\n indexA = coordsLocal.length;\n coordsLocal.push(a);\n latMapA.set(latA, indexA);\n }\n\n // Index B\n let latMapB = coordIndexMapLocal.get(lngB);\n if (latMapB === undefined) {\n latMapB = new Map<number, number>();\n coordIndexMapLocal.set(lngB, latMapB);\n }\n let indexB = latMapB.get(latB);\n if (indexB === undefined) {\n indexB = coordsLocal.length;\n coordsLocal.push(b);\n latMapB.set(latB, indexB);\n }\n\n // Count degree for both directions\n degree[indexA] = (degree[indexA] ?? 0) + 1;\n degree[indexB] = (degree[indexB] ?? 0) + 1;\n }\n }\n\n // Build CSR arrays from degree counts\n const nodeCount = this.coordinates.length; // Total nodes discovered\n this.csrNodeCount = nodeCount; // CSR covers all built nodes\n const offsets = new Int32Array(nodeCount + 1); // Row pointer array\n for (let i = 0; i < nodeCount; i++) {\n const deg = degree[i] ?? 0; // Degree of node i\n offsets[i + 1] = offsets[i] + deg; // Prefix sum\n }\n const totalEdges = offsets[nodeCount]; // Total adjacency entries\n const indices = new Int32Array(totalEdges); // Neighbor indices array\n const distances = new Float64Array(totalEdges); // Distances array aligned to indices\n\n // Pass 2: fill CSR arrays using a write cursor per node\n const cursor = offsets.slice(); // Current write positions per node\n for (let f = 0, fLen = features.length; f < fLen; f++) {\n const feature = features[f];\n const lineCoords = feature.geometry.coordinates;\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) {\n const a = lineCoords[i];\n const b = lineCoords[i + 1];\n\n const lngA = a[0], latA = a[1];\n const lngB = b[0], latB = b[1];\n\n // Read back indices (guaranteed to exist from pass 1)\n const indexA = this.coordinateIndexMap.get(lngA)!.get(latA)!;\n const indexB = this.coordinateIndexMap.get(lngB)!.get(latB)!;\n\n const segmentDistance = measureDistance(a, b); // Edge weight once\n\n // Write A → B\n let pos = cursor[indexA]++;\n indices[pos] = indexB;\n distances[pos] = segmentDistance;\n // Write B → A\n pos = cursor[indexB]++;\n indices[pos] = indexA;\n distances[pos] = segmentDistance;\n }\n }\n\n // Commit CSR to instance\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n\n // Prepare sparse shell only for dynamically added nodes later (no prefilled neighbor arrays)\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>, // Start point feature\n end: Feature<Point> // End point feature\n ): Feature<LineString> | null {\n if (this.network === null) { // Guard: graph must be built first\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Ensure start/end exist in index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates); // Get or insert start node index\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates); // Get or insert end node index\n\n // Trivial case: same node\n if (startIndex === endIndex) {\n return null; // No path needed\n }\n\n // Local aliases\n const coords = this.coordinates; // Alias to coordinates array\n const adj = this.adjacencyList; // Alias to sparse adjacency list (for dynamic nodes)\n const measureDistance = this.distanceMeasurement; // Alias to distance function\n\n // Ensure and init scratch buffers\n const nodeCount = coords.length; // Current number of nodes (may be >= csrNodeCount if new nodes added)\n this.ensureScratch(nodeCount); // Allocate scratch arrays if needed\n\n // Non-null after ensure\n const gScore = this.gScoreScratch!; // gScore pointer\n const cameFrom = this.cameFromScratch!; // cameFrom pointer\n const visited = this.visitedScratch!; // visited pointer\n const hCache = this.hScratch!; // heuristic cache pointer\n\n // Reset only the used range for speed\n gScore.fill(Number.POSITIVE_INFINITY, 0, nodeCount); // Init gScore to +∞\n cameFrom.fill(-1, 0, nodeCount); // Init predecessors to -1 (unknown)\n visited.fill(0, 0, nodeCount); // Init visited flags to 0\n hCache.fill(-1, 0, nodeCount); // Init heuristic cache with sentinel (-1 means unknown)\n\n // Create min-heap (priority queue)\n const openSet = new this.heapConstructor();\n\n // Precompute heuristic for start to prime the queue cost\n const endCoord = coords[endIndex]; // Cache end coordinate for heuristic\n let hStart = hCache[startIndex];\n\n if (hStart < 0) {\n hStart = measureDistance(coords[startIndex], endCoord);\n hCache[startIndex] = hStart;\n }\n\n openSet.insert(hStart, startIndex); // Insert start with f = g(0) + h(start)\n gScore[startIndex] = 0; // g(start) = 0\n\n while (openSet.size() > 0) { // Main A* loop until queue empty\n const current = openSet.extractMin()!; // Pop node with smallest f\n if (visited[current] !== 0) { // Skip if already finalized\n continue;\n }\n if (current === endIndex) { // Early exit if reached goal\n break;\n }\n visited[current] = 1; // Mark as visited\n\n // Prefer CSR neighbors if available for this node, fall back to sparse list for dynamically added nodes\n\n if (this.csrOffsets && current < this.csrNodeCount) { // Use CSR fast path\n const csrOffsets = this.csrOffsets!; // Local CSR offsets (non-null here)\n const csrIndices = this.csrIndices!; // Local CSR neighbors\n const csrDistances = this.csrDistances!; // Local CSR weights\n const startOff = csrOffsets[current]; // Row start for current\n const endOff = csrOffsets[current + 1]; // Row end for current\n\n for (let i = startOff; i < endOff; i++) { // Iterate neighbors in CSR slice\n const nbNode = csrIndices[i]; // Neighbor node id\n const tentativeG = gScore[current] + csrDistances[i]; // g' = g(current) + w(current, nb)\n if (tentativeG < gScore[nbNode]) { // Relaxation check\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n // A* priority = g + h (cache h per node for this query)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Push/update neighbor into open set\n }\n }\n }\n // Fallback: use sparse adjacency (only for nodes added after CSR build)\n else {\n\n const neighbors = adj[current]; // Neighbor list for current\n if (!neighbors || neighbors.length === 0) continue; // No neighbors\n for (let i = 0, n = neighbors.length; i < n; i++) { // Iterate neighbors\n const nb = neighbors[i]; // Neighbor entry\n const nbNode = nb.node; // Neighbor id\n\n const tentativeG = gScore[current] + nb.distance; // g' via current\n if (tentativeG < gScore[nbNode]) { // Relax if better\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n\n // A* priority = g + h (cached)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Enqueue neighbor\n }\n }\n }\n }\n\n // If goal was never reached/relaxed, return null\n if (cameFrom[endIndex] < 0) {\n return null;\n }\n\n // Reconstruct path (push + reverse to avoid O(n^2) unshift)\n const path: Position[] = [];\n let cur = endIndex;\n while (cur !== startIndex) {\n path.push(coords[cur]);\n cur = cameFrom[cur];\n }\n // Include start coordinate\n path.push(coords[startIndex]);\n\n // Reverse to get start→end order\n path.reverse();\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number { // Ensure a coordinate has a node index, creating if absent\n const lng = coord[0]; // Extract longitude\n const lat = coord[1]; // Extract latitude\n\n let latMap = this.coordinateIndexMap.get(lng); // Get lat→index map for this longitude\n if (latMap === undefined) { // Create if missing\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n\n let index = latMap.get(lat); // Lookup index by latitude\n if (index === undefined) { // If not found, append new node\n\n index = this.coordinates.length; // New index at end\n this.coordinates.push(coord); // Store coordinate\n latMap.set(lat, index); // Record mapping\n\n // Ensure sparse adjacency slot for dynamically added nodes\n this.adjacencyList[index] = []; // Init empty neighbor array\n\n // Extend CSR offsets to keep indices consistent (no neighbors for new node)\n if (this.csrOffsets) { // Only adjust if CSR already built\n\n // Only need to expand offsets by one; indices/distances remain unchanged\n const oldCount = this.csrNodeCount; // Nodes currently covered by CSR\n\n // Appending exactly one new node at the end\n if (index === oldCount) {\n const newOffsets = new Int32Array(oldCount + 2); // Allocate offsets for +1 node\n newOffsets.set(this.csrOffsets, 0); // Copy previous offsets\n\n // Last offset repeats to indicate zero neighbors\n newOffsets[oldCount + 1] = newOffsets[oldCount]; // Replicate last pointer\n this.csrOffsets = newOffsets; // Swap in new offsets\n this.csrNodeCount = oldCount + 1; // Increment CSR node count\n }\n }\n }\n\n return index;\n }\n\n // Ensure scratch arrays are allocated with at least `size` capacity.\n private ensureScratch(size: number): void {\n const ifAlreadyBigEnough = this.scratchCapacity >= size\n && this.gScoreScratch\n && this.cameFromScratch\n && this.visitedScratch\n && this.hScratch;\n\n if (ifAlreadyBigEnough) {\n return; // Nothing to do\n }\n const capacity = size | 0; // Ensure integer\n this.gScoreScratch = new Float64Array(capacity);\n this.cameFromScratch = new Int32Array(capacity);\n this.visitedScratch = new Uint8Array(capacity);\n this.hScratch = new Float64Array(capacity);\n this.scratchCapacity = capacity;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance } "],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","createCheapRuler","lat","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy","FourAryHeap","constructor","keys","this","values","idxs","length","insertCounter","insert","key","value","i","ck","cv","ci","p","pk","pi","extractMin","n","minValue","last","bubbleDown","size","k","v","idx","nodeK","nodeV","nodeI","c1","smallest","sK","sI","sV","c2","k2","i2","c3","k3","i3","c4","k4","i4","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","csrOffsets","csrIndices","csrDistances","csrNodeCount","gScoreScratch","cameFromScratch","visitedScratch","hScratch","scratchCapacity","heap","buildRouteGraph","coordIndexMapLocal","coordsLocal","measureDistance","features","degree","f","fLen","lineCoords","geometry","len","_degree$indexA","_degree$indexB","lngA","latA","lngB","latB","latMapA","get","undefined","set","indexA","push","latMapB","indexB","nodeCount","offsets","Int32Array","_degree$i","deg","totalEdges","indices","distances","Float64Array","cursor","slice","segmentDistance","pos","Array","getRoute","start","end","Error","startIndex","getOrCreateIndex","endIndex","coords","adj","ensureScratch","gScore","cameFrom","visited","hCache","fill","Number","POSITIVE_INFINITY","openSet","endCoord","hStart","current","endOff","nbNode","tentativeG","hVal","neighbors","nb","node","distance","path","cur","reverse","type","properties","coord","lng","latMap","index","oldCount","newOffsets","capacity","Uint8Array"],"mappings":"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"}
1
+ {"version":3,"file":"terra-route.modern.js","sources":["../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/heap/four-ary-heap.ts","../src/terra-route.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n\n // Convert distance from meters to kilometers\n return distance / 1000;\n}\n","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}","import { Heap } from \"./heap\";\n\ninterface Node {\n key: number;\n value: number;\n index: number; // insertion order for stable tie-breaking\n}\n\n/**\n * A 4-ary min-heap with stable tie-breaking on insertion order.\n * Parent(i) = floor((i - 1) / 4)\n * Children(i) = 4*i + 1 .. 4*i + 4\n */\nexport class FourAryHeap implements Heap {\n // Parallel arrays for fewer object allocations and faster property access\n private keys: number[] = [];\n private values: number[] = [];\n private idxs: number[] = [];\n private length = 0; // number of valid elements\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n // Bubble-up using local variables (avoid temporary node object)\n let i = this.length;\n this.length = i + 1;\n\n let ck = key;\n let cv = value;\n let ci = this.insertCounter++;\n\n while (i > 0) {\n const p = (i - 1) >>> 2; // divide by 4\n const pk = this.keys[p];\n const pi = this.idxs[p];\n if (ck > pk || (ck === pk && ci > pi)) break;\n\n // move parent down\n this.keys[i] = pk;\n this.values[i] = this.values[p];\n this.idxs[i] = pi;\n i = p;\n }\n\n // place the new node\n this.keys[i] = ck;\n this.values[i] = cv;\n this.idxs[i] = ci;\n }\n\n extractMin(): number | null {\n const n = this.length;\n if (n === 0) return null;\n\n const minValue = this.values[0];\n const last = n - 1;\n this.length = last;\n\n if (last > 0) {\n // Move last element to root then bubble down\n this.keys[0] = this.keys[last];\n this.values[0] = this.values[last];\n this.idxs[0] = this.idxs[last];\n this.bubbleDown(0);\n }\n\n return minValue;\n }\n\n size(): number {\n return this.length;\n }\n\n private bubbleDown(i: number): void {\n const n = this.length;\n const k = this.keys;\n const v = this.values;\n const idx = this.idxs;\n\n const nodeK = k[i];\n const nodeV = v[i];\n const nodeI = idx[i];\n\n while (true) {\n const c1 = (i << 2) + 1; // 4*i + 1\n if (c1 >= n) break; // no children\n\n // find smallest among up to 4 children\n let smallest = c1;\n let sK = k[c1];\n let sI = idx[c1];\n let sV = v[c1];\n\n const c2 = c1 + 1;\n if (c2 < n) {\n const k2 = k[c2];\n const i2 = idx[c2];\n if (k2 < sK || (k2 === sK && i2 < sI)) {\n smallest = c2;\n sK = k2;\n sI = i2;\n sV = v[c2];\n }\n }\n\n const c3 = c1 + 2;\n if (c3 < n) {\n const k3 = k[c3];\n const i3 = idx[c3];\n if (k3 < sK || (k3 === sK && i3 < sI)) {\n smallest = c3;\n sK = k3;\n sI = i3;\n sV = v[c3];\n }\n }\n\n const c4 = c1 + 3;\n if (c4 < n) {\n const k4 = k[c4];\n const i4 = idx[c4];\n if (k4 < sK || (k4 === sK && i4 < sI)) {\n smallest = c4;\n sK = k4;\n sI = i4;\n sV = v[c4];\n }\n }\n\n if (sK < nodeK || (sK === nodeK && sI < nodeI)) {\n k[i] = sK;\n v[i] = sV;\n idx[i] = sI;\n i = smallest;\n } else {\n break;\n }\n }\n\n k[i] = nodeK;\n v[i] = nodeV;\n idx[i] = nodeI;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\"; // Import GeoJSON types\nimport { haversineDistance } from \"./distance/haversine\"; // Great-circle distance function (default heuristic/edge weight)\nimport { createCheapRuler } from \"./distance/cheap-ruler\"; // Factory for faster planar distance (exported for consumers)\nimport { HeapConstructor } from \"./heap/heap\"; // Heap interface so users can plug custom heaps\nimport { FourAryHeap } from \"./heap/four-ary-heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n expandRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null; // The last network used to build the graph\n private distanceMeasurement: (a: Position, b: Position) => number; // Distance function used for edges and heuristic\n private heapConstructor: HeapConstructor; // Heap class used by A*\n\n // Map from longitude → (map from latitude → index) to deduplicate coordinates and get node indices quickly\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map(); // Nested map for exact coord lookup\n private coordinates: Position[] = []; // Array of all unique coordinates by index\n // Sparse adjacency list used during build and for any nodes added dynamically later\n private adjacencyList: Array<Array<{ node: number, distance: number }>> = []; // Per-node neighbor arrays used only pre-CSR or for dynamic nodes\n\n // Compressed Sparse Row adjacency representation for fast neighbor iteration in getRoute\n private csrOffsets: Int32Array | null = null; // Row pointer: length = nodeCount + 1, offsets into indices/distances\n private csrIndices: Int32Array | null = null; // Column indices: neighbor node IDs, length = totalEdges\n private csrDistances: Float64Array | null = null; // Edge weights aligned to csrIndices, length = totalEdges\n private csrNodeCount = 0; // Number of nodes captured in the CSR arrays\n\n // Reusable typed scratch buffers for A*\n private gScoreScratch: Float64Array | null = null; // gScore per node (cost from start)\n private cameFromScratch: Int32Array | null = null; // Predecessor per node for path reconstruction\n private visitedScratch: Uint8Array | null = null; // Visited set to avoid reprocessing\n private hScratch: Float64Array | null = null; // Per-node heuristic cache for the current query (lazy compute)\n // Reverse-direction scratch for bidirectional search\n private gScoreRevScratch: Float64Array | null = null; // gScore per node from the end\n private cameFromRevScratch: Int32Array | null = null; // Successor per node (next step toward the end)\n private visitedRevScratch: Uint8Array | null = null; // Visited set for reverse search\n private hRevScratch: Float64Array | null = null; // Heuristic cache for reverse direction per query\n private scratchCapacity = 0; // Current capacity of scratch arrays\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number; // Optional distance function override\n heap?: HeapConstructor; // Optional heap implementation override\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance; // Default to haversine\n this.heapConstructor = options?.heap ?? FourAryHeap; // Default to MinHeap\n }\n\n /**\n * Builds a graph (CSR) from a LineString FeatureCollection.\n * Two-pass build: pass 1 assigns node indices and counts degrees; pass 2 fills CSR arrays.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network; // Keep a reference to the network\n\n // Reset internal structures for a fresh build\n this.coordinateIndexMap = new Map(); // Clear coordinate index map\n this.coordinates = []; // Clear coordinates array\n this.adjacencyList = []; // Will not be populated during build; reserved for dynamic nodes post-build\n // Reset CSR structures (will rebuild below)\n this.csrOffsets = null;\n this.csrIndices = null;\n this.csrDistances = null;\n this.csrNodeCount = 0;\n\n // Hoist to locals for speed (avoid repeated property lookups in hot loops)\n const coordIndexMapLocal = this.coordinateIndexMap; // Local alias for coord map\n const coordsLocal = this.coordinates; // Local alias for coordinates array\n const measureDistance = this.distanceMeasurement; // Local alias for distance function\n\n // Pass 1: assign indices and count degrees per node\n const degree: number[] = []; // Dynamic degree array; grows as nodes are discovered\n this.forEachSegment(network, (a, b) => {\n const indexA = this.indexCoordinate(a, coordIndexMapLocal, coordsLocal);\n const indexB = this.indexCoordinate(b, coordIndexMapLocal, coordsLocal);\n\n degree[indexA] = (degree[indexA] ?? 0) + 1;\n degree[indexB] = (degree[indexB] ?? 0) + 1;\n });\n\n // Build CSR arrays from degree counts\n const nodeCount = this.coordinates.length; // Total nodes discovered\n this.csrNodeCount = nodeCount; // CSR covers all built nodes\n const offsets = new Int32Array(nodeCount + 1); // Row pointer array\n for (let i = 0; i < nodeCount; i++) {\n const deg = degree[i] ?? 0; // Degree of node i\n offsets[i + 1] = offsets[i] + deg; // Prefix sum\n }\n const totalEdges = offsets[nodeCount]; // Total adjacency entries\n const indices = new Int32Array(totalEdges); // Neighbor indices array\n const distances = new Float64Array(totalEdges); // Distances array aligned to indices\n\n // Pass 2: fill CSR arrays using a write cursor per node\n const cursor = offsets.slice(); // Current write positions per node\n this.forEachSegment(network, (a, b) => {\n // Read back indices (guaranteed to exist from pass 1)\n const indexA = this.coordinateIndexMap.get(a[0])!.get(a[1])!;\n const indexB = this.coordinateIndexMap.get(b[0])!.get(b[1])!;\n\n const segmentDistance = measureDistance(a, b); // Edge weight once\n\n let pos = cursor[indexA]++;\n indices[pos] = indexB;\n distances[pos] = segmentDistance;\n pos = cursor[indexB]++;\n indices[pos] = indexA;\n distances[pos] = segmentDistance;\n });\n\n // Commit CSR to instance\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n\n // Prepare sparse shell only for dynamically added nodes later (no prefilled neighbor arrays)\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Expands (merges) the existing graph with an additional LineString FeatureCollection.\n */\n public expandRouteGraph(network: FeatureCollection<LineString>): void {\n if (this.network === null) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Merge the feature arrays for reference/debugging. We avoid copying properties deeply.\n this.network = {\n type: \"FeatureCollection\",\n features: [...this.network.features, ...network.features],\n };\n\n const coordIndexMapLocal = this.coordinateIndexMap;\n const coordsLocal = this.coordinates;\n const measureDistance = this.distanceMeasurement;\n const adj = this.adjacencyList;\n\n // Ensure we have adjacency arrays for any existing CSR-only nodes.\n // (During buildRouteGraph, adjacencyList is sized but entries are undefined.)\n for (let i = 0; i < adj.length; i++) {\n if (adj[i] === undefined) adj[i] = [];\n }\n\n // Add new edges into the adjacency list (sparse), then rebuild CSR from adjacency.\n this.forEachSegment(network, (a, b) => {\n const indexA = this.indexCoordinate(a, coordIndexMapLocal, coordsLocal, (idx) => { adj[idx] = []; });\n const indexB = this.indexCoordinate(b, coordIndexMapLocal, coordsLocal, (idx) => { adj[idx] = []; });\n\n const segmentDistance = measureDistance(a, b);\n\n adj[indexA].push({ node: indexB, distance: segmentDistance });\n adj[indexB].push({ node: indexA, distance: segmentDistance });\n });\n\n this.rebuildCsrFromAdjacency();\n }\n\n /**\n * Rebuild CSR arrays for the full node set, using:\n * - Existing CSR edges (from the last build/expand)\n * - Any additional edges stored in `adjacencyList`\n */\n private rebuildCsrFromAdjacency(): void {\n const nodeCount = this.coordinates.length;\n const adj = this.adjacencyList;\n\n // Compute degree using CSR degree + adjacency degree\n const degree = new Int32Array(nodeCount);\n\n if (this.csrOffsets && this.csrIndices && this.csrDistances) {\n const csrOffsets = this.csrOffsets;\n const covered = Math.min(this.csrNodeCount, nodeCount);\n for (let i = 0; i < covered; i++) {\n degree[i] += (csrOffsets[i + 1] - csrOffsets[i]);\n }\n }\n\n for (let i = 0; i < nodeCount; i++) {\n const neighbors = adj[i];\n if (neighbors && neighbors.length) degree[i] += neighbors.length;\n }\n\n const offsets = new Int32Array(nodeCount + 1);\n for (let i = 0; i < nodeCount; i++) {\n offsets[i + 1] = offsets[i] + degree[i];\n }\n const totalEdges = offsets[nodeCount];\n const indices = new Int32Array(totalEdges);\n const distances = new Float64Array(totalEdges);\n const cursor = offsets.slice();\n\n // Copy existing CSR edges first\n if (this.csrOffsets && this.csrIndices && this.csrDistances) {\n const csrOffsets = this.csrOffsets;\n const csrIndices = this.csrIndices;\n const csrDistances = this.csrDistances;\n const covered = Math.min(this.csrNodeCount, nodeCount);\n for (let n = 0; n < covered; n++) {\n const startOff = csrOffsets[n];\n const endOff = csrOffsets[n + 1];\n let pos = cursor[n];\n for (let i = startOff; i < endOff; i++) {\n indices[pos] = csrIndices[i];\n distances[pos] = csrDistances[i];\n pos++;\n }\n cursor[n] = pos;\n }\n }\n\n // Append adjacency edges\n for (let n = 0; n < nodeCount; n++) {\n const neighbors = adj[n];\n if (!neighbors || neighbors.length === 0) continue;\n let pos = cursor[n];\n for (let i = 0, len = neighbors.length; i < len; i++) {\n const nb = neighbors[i];\n indices[pos] = nb.node;\n distances[pos] = nb.distance;\n pos++;\n }\n cursor[n] = pos;\n }\n\n // Commit and reset adjacency (we've absorbed edges into CSR)\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n this.csrNodeCount = nodeCount;\n\n // Keep adjacency list for *future* dynamic additions, but clear existing edges to avoid duplication.\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>, // Start point feature\n end: Feature<Point> // End point feature\n ): Feature<LineString> | null {\n if (this.network === null) { // Guard: graph must be built first\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Ensure start/end exist in index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates); // Get or insert start node index\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates); // Get or insert end node index\n\n // Trivial case: same node\n if (startIndex === endIndex) {\n return null; // No path needed\n }\n\n // Local aliases\n const coords = this.coordinates; // Alias to coordinates array\n const adj = this.adjacencyList; // Alias to sparse adjacency list (for dynamic nodes)\n\n // Ensure and init scratch buffers\n const nodeCount = coords.length; // Current number of nodes (may be >= csrNodeCount if new nodes added)\n this.ensureScratch(nodeCount); // Allocate scratch arrays if needed\n\n // Non-null after ensure\n const gF = this.gScoreScratch!; // forward gScore (from start)\n const gR = this.gScoreRevScratch!; // reverse gScore (from end)\n const prevF = this.cameFromScratch!; // predecessor in forward search\n const nextR = this.cameFromRevScratch!; // successor in reverse search (toward end)\n const visF = this.visitedScratch!;\n const visR = this.visitedRevScratch!;\n const hF = this.hScratch!;\n const hR = this.hRevScratch!;\n\n gF.fill(Number.POSITIVE_INFINITY, 0, nodeCount);\n gR.fill(Number.POSITIVE_INFINITY, 0, nodeCount);\n prevF.fill(-1, 0, nodeCount);\n nextR.fill(-1, 0, nodeCount);\n visF.fill(0, 0, nodeCount);\n visR.fill(0, 0, nodeCount);\n hF.fill(-1, 0, nodeCount);\n hR.fill(-1, 0, nodeCount);\n\n const openF = new this.heapConstructor();\n const openR = new this.heapConstructor();\n\n const startCoord = coords[startIndex];\n const endCoord = coords[endIndex];\n\n gF[startIndex] = 0;\n gR[endIndex] = 0;\n\n // Bidirectional Dijkstra (A* with zero heuristic). This keeps correctness simple and matches the\n // reference pathfinder while still saving work by meeting in the middle.\n openF.insert(0, startIndex);\n openR.insert(0, endIndex);\n\n // Best meeting point found so far\n let bestPathCost = Number.POSITIVE_INFINITY;\n let meetingNode = -1;\n\n // Main bidirectional loop: expand alternately.\n // Without a heap peek, a safe and effective stopping rule is based on the last extracted keys:\n // once min_g_forward + min_g_reverse >= bestPathCost, no shorter path can still be found.\n let lastExtractedGForward = 0;\n let lastExtractedGReverse = 0;\n while (openF.size() > 0 && openR.size() > 0) {\n if (meetingNode >= 0 && (lastExtractedGForward + lastExtractedGReverse) >= bestPathCost) {\n break;\n }\n\n // Expand one step from each side, prioritizing the smaller frontier.\n const expandForward = openF.size() <= openR.size();\n\n if (expandForward) {\n const current = openF.extractMin()!;\n if (visF[current] !== 0) continue;\n lastExtractedGForward = gF[current];\n visF[current] = 1;\n\n // If reverse has finalized this node, we have a candidate meeting.\n if (visR[current] !== 0) {\n const total = gF[current] + gR[current];\n if (total < bestPathCost) {\n bestPathCost = total;\n meetingNode = current;\n }\n }\n\n // Relax neighbors and push newly improved ones\n if (this.csrOffsets && current < this.csrNodeCount) {\n const csrOffsets = this.csrOffsets!;\n const csrIndices = this.csrIndices!;\n const csrDistances = this.csrDistances!;\n for (let i = csrOffsets[current], endOff = csrOffsets[current + 1]; i < endOff; i++) {\n const nbNode = csrIndices[i];\n const tentativeG = gF[current] + csrDistances[i];\n if (tentativeG < gF[nbNode]) {\n gF[nbNode] = tentativeG;\n prevF[nbNode] = current;\n const otherG = gR[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openF.insert(tentativeG, nbNode);\n }\n }\n } else {\n const neighbors = adj[current];\n if (neighbors && neighbors.length) {\n for (let i = 0, n = neighbors.length; i < n; i++) {\n const nb = neighbors[i];\n const nbNode = nb.node;\n const tentativeG = gF[current] + nb.distance;\n if (tentativeG < gF[nbNode]) {\n gF[nbNode] = tentativeG;\n prevF[nbNode] = current;\n const otherG = gR[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openF.insert(tentativeG, nbNode);\n }\n }\n }\n }\n } else {\n const current = openR.extractMin()!;\n if (visR[current] !== 0) continue;\n lastExtractedGReverse = gR[current];\n visR[current] = 1;\n\n if (visF[current] !== 0) {\n const total = gF[current] + gR[current];\n if (total < bestPathCost) {\n bestPathCost = total;\n meetingNode = current;\n }\n }\n\n // Reverse direction: same neighbor iteration because graph is undirected.\n // Store successor pointer (next step toward end) i.e. nextR[neighbor] = current.\n if (this.csrOffsets && current < this.csrNodeCount) {\n const csrOffsets = this.csrOffsets!;\n const csrIndices = this.csrIndices!;\n const csrDistances = this.csrDistances!;\n for (let i = csrOffsets[current], endOff = csrOffsets[current + 1]; i < endOff; i++) {\n const nbNode = csrIndices[i];\n const tentativeG = gR[current] + csrDistances[i];\n if (tentativeG < gR[nbNode]) {\n gR[nbNode] = tentativeG;\n nextR[nbNode] = current;\n const otherG = gF[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openR.insert(tentativeG, nbNode);\n }\n }\n } else {\n const neighbors = adj[current];\n if (neighbors && neighbors.length) {\n for (let i = 0, n = neighbors.length; i < n; i++) {\n const nb = neighbors[i];\n const nbNode = nb.node;\n const tentativeG = gR[current] + nb.distance;\n if (tentativeG < gR[nbNode]) {\n gR[nbNode] = tentativeG;\n nextR[nbNode] = current;\n const otherG = gF[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openR.insert(tentativeG, nbNode);\n }\n }\n }\n }\n }\n }\n\n if (meetingNode < 0) {\n return null;\n }\n\n // Reconstruct path: start -> meeting using prevF, then meeting -> end using nextR\n const path: Position[] = [];\n\n // Walk back from meeting to start, collecting nodes\n let cur = meetingNode;\n while (cur !== startIndex && cur >= 0) {\n path.push(coords[cur]);\n cur = prevF[cur];\n }\n if (cur !== startIndex) {\n // Forward tree doesn't connect start to meeting (shouldn't happen if meeting is valid)\n return null;\n }\n path.push(coords[startIndex]);\n path.reverse();\n\n // Walk from meeting to end (skip meeting node because it's already included)\n cur = meetingNode;\n while (cur !== endIndex) {\n cur = nextR[cur];\n if (cur < 0) {\n return null;\n }\n path.push(coords[cur]);\n }\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number { // Ensure a coordinate has a node index, creating if absent\n const lng = coord[0]; // Extract longitude\n const lat = coord[1]; // Extract latitude\n\n let latMap = this.coordinateIndexMap.get(lng); // Get lat→index map for this longitude\n if (latMap === undefined) { // Create if missing\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n\n let index = latMap.get(lat); // Lookup index by latitude\n if (index === undefined) { // If not found, append new node\n\n index = this.coordinates.length; // New index at end\n this.coordinates.push(coord); // Store coordinate\n latMap.set(lat, index); // Record mapping\n\n // Ensure sparse adjacency slot for dynamically added nodes\n this.adjacencyList[index] = []; // Init empty neighbor array\n\n // Extend CSR offsets to keep indices consistent (no neighbors for new node)\n if (this.csrOffsets) { // Only adjust if CSR already built\n\n // Only need to expand offsets by one; indices/distances remain unchanged\n const oldCount = this.csrNodeCount; // Nodes currently covered by CSR\n\n // Appending exactly one new node at the end\n if (index === oldCount) {\n const newOffsets = new Int32Array(oldCount + 2); // Allocate offsets for +1 node\n newOffsets.set(this.csrOffsets, 0); // Copy previous offsets\n\n // Last offset repeats to indicate zero neighbors\n newOffsets[oldCount + 1] = newOffsets[oldCount]; // Replicate last pointer\n this.csrOffsets = newOffsets; // Swap in new offsets\n this.csrNodeCount = oldCount + 1; // Increment CSR node count\n }\n }\n }\n\n return index;\n }\n\n // Ensure scratch arrays are allocated with at least `size` capacity.\n private ensureScratch(size: number): void {\n const ifAlreadyBigEnough = this.scratchCapacity >= size\n && this.gScoreScratch\n && this.cameFromScratch\n && this.visitedScratch\n && this.hScratch\n && this.gScoreRevScratch\n && this.cameFromRevScratch\n && this.visitedRevScratch\n && this.hRevScratch;\n\n if (ifAlreadyBigEnough) {\n return; // Nothing to do\n }\n const capacity = size | 0; // Ensure integer\n this.gScoreScratch = new Float64Array(capacity);\n this.cameFromScratch = new Int32Array(capacity);\n this.visitedScratch = new Uint8Array(capacity);\n this.hScratch = new Float64Array(capacity);\n this.gScoreRevScratch = new Float64Array(capacity);\n this.cameFromRevScratch = new Int32Array(capacity);\n this.visitedRevScratch = new Uint8Array(capacity);\n this.hRevScratch = new Float64Array(capacity);\n this.scratchCapacity = capacity;\n }\n\n\n // Iterate all consecutive segment pairs in a LineString FeatureCollection.\n // Kept as a simple loop helper to avoid repeating nested iteration logic.\n private forEachSegment(\n network: FeatureCollection<LineString>,\n fn: (a: Position, b: Position) => void,\n ): void {\n const features = network.features;\n for (let f = 0, fLen = features.length; f < fLen; f++) {\n const lineCoords = features[f].geometry.coordinates;\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) {\n // GeoJSON coordinates are compatible with Position (number[]), and the project assumes [lng,lat]\n fn(lineCoords[i] as Position, lineCoords[i + 1] as Position);\n }\n }\n }\n\n // Hot-path coordinate indexer used by both build/expand.\n // Accepts explicit maps/arrays so callers can hoist them once.\n private indexCoordinate(\n coord: Position,\n coordIndexMapLocal: Map<number, Map<number, number>>,\n coordsLocal: Position[],\n onNewIndex?: (index: number) => void,\n ): number {\n const lng = coord[0];\n const lat = coord[1];\n\n let latMap = coordIndexMapLocal.get(lng);\n if (latMap === undefined) {\n latMap = new Map<number, number>();\n coordIndexMapLocal.set(lng, latMap);\n }\n\n let idx = latMap.get(lat);\n if (idx === undefined) {\n idx = coordsLocal.length;\n coordsLocal.push(coord);\n latMap.set(lat, idx);\n if (onNewIndex) onNewIndex(idx);\n }\n\n return idx;\n }\n\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance } "],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","createCheapRuler","lat","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy","FourAryHeap","constructor","keys","this","values","idxs","length","insertCounter","insert","key","value","i","ck","cv","ci","p","pk","pi","extractMin","n","minValue","last","bubbleDown","size","k","v","idx","nodeK","nodeV","nodeI","c1","smallest","sK","sI","sV","c2","k2","i2","c3","k3","i3","c4","k4","i4","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","csrOffsets","csrIndices","csrDistances","csrNodeCount","gScoreScratch","cameFromScratch","visitedScratch","hScratch","gScoreRevScratch","cameFromRevScratch","visitedRevScratch","hRevScratch","scratchCapacity","heap","buildRouteGraph","coordIndexMapLocal","coordsLocal","measureDistance","degree","forEachSegment","_degree$indexA","_degree$indexB","indexA","indexCoordinate","indexB","nodeCount","offsets","Int32Array","_degree$i","deg","totalEdges","indices","distances","Float64Array","cursor","slice","get","segmentDistance","pos","Array","expandRouteGraph","Error","type","features","adj","undefined","push","node","distance","rebuildCsrFromAdjacency","covered","min","neighbors","endOff","len","nb","getRoute","start","end","startIndex","getOrCreateIndex","geometry","endIndex","coords","ensureScratch","gF","gR","prevF","nextR","visF","visR","hF","hR","fill","Number","POSITIVE_INFINITY","openF","openR","bestPathCost","meetingNode","lastExtractedGForward","lastExtractedGReverse","current","total","nbNode","tentativeG","otherG","path","cur","reverse","properties","coord","lng","latMap","set","index","oldCount","newOffsets","capacity","Uint8Array","fn","f","fLen","lineCoords","onNewIndex"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var t=function(t,s){var e=function(t){return t*Math.PI/180},r=e(t[1]),i=e(t[0]),n=e(s[1]),a=n-r,h=e(s[0])-i,c=Math.sin(a/2)*Math.sin(a/2)+Math.cos(r)*Math.cos(n)*Math.sin(h/2)*Math.sin(h/2);return 2*Math.atan2(Math.sqrt(c),Math.sqrt(1-c))*6371e3/1e3};function s(t){var s=1/298.257223563,e=s*(2-s),r=Math.PI/180,i=Math.cos(t*r),n=1/(1-e*(1-i*i)),a=Math.sqrt(n),h=6378.137*r,c=h*a*i,o=h*a*n*(1-e);return function(t,s){for(var e=t[0]-s[0];e<-180;)e+=360;for(;e>180;)e-=360;var r=e*c,i=(t[1]-s[1])*o;return Math.sqrt(r*r+i*i)}}var e=/*#__PURE__*/function(){function t(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}var s=t.prototype;return s.insert=function(t,s){var e=this.length;this.length=e+1;for(var r=t,i=s,n=this.insertCounter++;e>0;){var a=e-1>>>2,h=this.keys[a],c=this.idxs[a];if(r>h||r===h&&n>c)break;this.keys[e]=h,this.values[e]=this.values[a],this.idxs[e]=c,e=a}this.keys[e]=r,this.values[e]=i,this.idxs[e]=n},s.extractMin=function(){var t=this.length;if(0===t)return null;var s=this.values[0],e=t-1;return this.length=e,e>0&&(this.keys[0]=this.keys[e],this.values[0]=this.values[e],this.idxs[0]=this.idxs[e],this.bubbleDown(0)),s},s.size=function(){return this.length},s.bubbleDown=function(t){for(var s=this.length,e=this.keys,r=this.values,i=this.idxs,n=e[t],a=r[t],h=i[t];;){var c=1+(t<<2);if(c>=s)break;var o=c,u=e[c],l=i[c],v=r[c],d=c+1;if(d<s){var f=e[d],g=i[d];(f<u||f===u&&g<l)&&(o=d,u=f,l=g,v=r[d])}var p=c+2;if(p<s){var y=e[p],M=i[p];(y<u||y===u&&M<l)&&(o=p,u=y,l=M,v=r[p])}var I=c+3;if(I<s){var w=e[I],S=i[I];(w<u||w===u&&S<l)&&(o=I,u=w,l=S,v=r[I])}if(!(u<n||u===n&&l<h))break;e[t]=u,r[t]=v,i[t]=l,t=o}e[t]=n,r[t]=a,i[t]=h},t}(),r=/*#__PURE__*/function(){function s(s){var r,i;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(r=null==s?void 0:s.distanceMeasurement)?r:t,this.heapConstructor=null!=(i=null==s?void 0:s.heap)?i:e}var r=s.prototype;return r.buildRouteGraph=function(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;for(var s=this.coordinateIndexMap,e=this.coordinates,r=this.distanceMeasurement,i=t.features,n=[],a=0,h=i.length;a<h;a++)for(var c=i[a].geometry.coordinates,o=0,u=c.length-1;o<u;o++){var l,v,d=c[o],f=c[o+1],g=d[0],p=d[1],y=f[0],M=f[1],I=s.get(g);void 0===I&&(I=new Map,s.set(g,I));var w=I.get(p);void 0===w&&(w=e.length,e.push(d),I.set(p,w));var S=s.get(y);void 0===S&&(S=new Map,s.set(y,S));var x=S.get(M);void 0===x&&(x=e.length,e.push(f),S.set(M,x)),n[w]=(null!=(l=n[w])?l:0)+1,n[x]=(null!=(v=n[x])?v:0)+1}var m=this.coordinates.length;this.csrNodeCount=m;for(var C=new Int32Array(m+1),k=0;k<m;k++){var b,O=null!=(b=n[k])?b:0;C[k+1]=C[k]+O}for(var N=C[m],A=new Int32Array(N),F=new Float64Array(N),D=C.slice(),L=0,j=i.length;L<j;L++)for(var q=i[L].geometry.coordinates,P=0,R=q.length-1;P<R;P++){var z=q[P],E=q[P+1],G=z[1],T=E[0],U=E[1],V=this.coordinateIndexMap.get(z[0]).get(G),Y=this.coordinateIndexMap.get(T).get(U),_=r(z,E),B=D[V]++;A[B]=Y,F[B]=_,A[B=D[Y]++]=V,F[B]=_}this.csrOffsets=C,this.csrIndices=A,this.csrDistances=F,this.adjacencyList=new Array(m)},r.getRoute=function(t,s){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var e=this.getOrCreateIndex(t.geometry.coordinates),r=this.getOrCreateIndex(s.geometry.coordinates);if(e===r)return null;var i=this.coordinates,n=this.adjacencyList,a=this.distanceMeasurement,h=i.length;this.ensureScratch(h);var c=this.gScoreScratch,o=this.cameFromScratch,u=this.visitedScratch,l=this.hScratch;c.fill(Number.POSITIVE_INFINITY,0,h),o.fill(-1,0,h),u.fill(0,0,h),l.fill(-1,0,h);var v=new this.heapConstructor,d=i[r],f=l[e];for(f<0&&(f=a(i[e],d),l[e]=f),v.insert(f,e),c[e]=0;v.size()>0;){var g=v.extractMin();if(0===u[g]){if(g===r)break;if(u[g]=1,this.csrOffsets&&g<this.csrNodeCount)for(var p=this.csrOffsets,y=this.csrIndices,M=this.csrDistances,I=p[g+1],w=p[g];w<I;w++){var S=y[w],x=c[g]+M[w];if(x<c[S]){c[S]=x,o[S]=g;var m=l[S];m<0&&(m=a(i[S],d),l[S]=m),v.insert(x+m,S)}}else{var C=n[g];if(!C||0===C.length)continue;for(var k=0,b=C.length;k<b;k++){var O=C[k],N=O.node,A=c[g]+O.distance;if(A<c[N]){c[N]=A,o[N]=g;var F=l[N];F<0&&(F=a(i[N],d),l[N]=F),v.insert(A+F,N)}}}}}if(o[r]<0)return null;for(var D=[],L=r;L!==e;)D.push(i[L]),L=o[L];return D.push(i[e]),D.reverse(),{type:"Feature",geometry:{type:"LineString",coordinates:D},properties:{}}},r.getOrCreateIndex=function(t){var s=t[0],e=t[1],r=this.coordinateIndexMap.get(s);void 0===r&&(r=new Map,this.coordinateIndexMap.set(s,r));var i=r.get(e);if(void 0===i&&(i=this.coordinates.length,this.coordinates.push(t),r.set(e,i),this.adjacencyList[i]=[],this.csrOffsets)){var n=this.csrNodeCount;if(i===n){var a=new Int32Array(n+2);a.set(this.csrOffsets,0),a[n+1]=a[n],this.csrOffsets=a,this.csrNodeCount=n+1}}return i},r.ensureScratch=function(t){if(!(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch)){var s=0|t;this.gScoreScratch=new Float64Array(s),this.cameFromScratch=new Int32Array(s),this.visitedScratch=new Uint8Array(s),this.hScratch=new Float64Array(s),this.scratchCapacity=s}},s}();export{r as TerraRoute,s as createCheapRuler,t as haversineDistance};
1
+ var t=function(t,r){var s=function(t){return t*Math.PI/180},e=s(t[1]),i=s(t[0]),n=s(r[1]),a=n-e,h=s(r[0])-i,c=Math.sin(a/2)*Math.sin(a/2)+Math.cos(e)*Math.cos(n)*Math.sin(h/2)*Math.sin(h/2);return 2*Math.atan2(Math.sqrt(c),Math.sqrt(1-c))*6371e3/1e3};function r(t){var r=1/298.257223563,s=r*(2-r),e=Math.PI/180,i=Math.cos(t*e),n=1/(1-s*(1-i*i)),a=Math.sqrt(n),h=6378.137*e,c=h*a*i,o=h*a*n*(1-s);return function(t,r){for(var s=t[0]-r[0];s<-180;)s+=360;for(;s>180;)s-=360;var e=s*c,i=(t[1]-r[1])*o;return Math.sqrt(e*e+i*i)}}var s=/*#__PURE__*/function(){function t(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}var r=t.prototype;return r.insert=function(t,r){var s=this.length;this.length=s+1;for(var e=t,i=r,n=this.insertCounter++;s>0;){var a=s-1>>>2,h=this.keys[a],c=this.idxs[a];if(e>h||e===h&&n>c)break;this.keys[s]=h,this.values[s]=this.values[a],this.idxs[s]=c,s=a}this.keys[s]=e,this.values[s]=i,this.idxs[s]=n},r.extractMin=function(){var t=this.length;if(0===t)return null;var r=this.values[0],s=t-1;return this.length=s,s>0&&(this.keys[0]=this.keys[s],this.values[0]=this.values[s],this.idxs[0]=this.idxs[s],this.bubbleDown(0)),r},r.size=function(){return this.length},r.bubbleDown=function(t){for(var r=this.length,s=this.keys,e=this.values,i=this.idxs,n=s[t],a=e[t],h=i[t];;){var c=1+(t<<2);if(c>=r)break;var o=c,u=s[c],l=i[c],f=e[c],v=c+1;if(v<r){var d=s[v],I=i[v];(d<u||d===u&&I<l)&&(o=v,u=d,l=I,f=e[v])}var S=c+2;if(S<r){var g=s[S],y=i[S];(g<u||g===u&&y<l)&&(o=S,u=g,l=y,f=e[S])}var p=c+3;if(p<r){var m=s[p],w=i[p];(m<u||m===u&&w<l)&&(o=p,u=m,l=w,f=e[p])}if(!(u<n||u===n&&l<h))break;s[t]=u,e[t]=f,i[t]=l,t=o}s[t]=n,e[t]=a,i[t]=h},t}(),e=/*#__PURE__*/function(){function r(r){var e,i;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.gScoreRevScratch=null,this.cameFromRevScratch=null,this.visitedRevScratch=null,this.hRevScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(e=null==r?void 0:r.distanceMeasurement)?e:t,this.heapConstructor=null!=(i=null==r?void 0:r.heap)?i:s}var e=r.prototype;return e.buildRouteGraph=function(t){var r=this;this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;var s=this.coordinateIndexMap,e=this.coordinates,i=this.distanceMeasurement,n=[];this.forEachSegment(t,function(t,i){var a,h,c=r.indexCoordinate(t,s,e),o=r.indexCoordinate(i,s,e);n[c]=(null!=(a=n[c])?a:0)+1,n[o]=(null!=(h=n[o])?h:0)+1});var a=this.coordinates.length;this.csrNodeCount=a;for(var h=new Int32Array(a+1),c=0;c<a;c++){var o,u=null!=(o=n[c])?o:0;h[c+1]=h[c]+u}var l=h[a],f=new Int32Array(l),v=new Float64Array(l),d=h.slice();this.forEachSegment(t,function(t,s){var e=r.coordinateIndexMap.get(t[0]).get(t[1]),n=r.coordinateIndexMap.get(s[0]).get(s[1]),a=i(t,s),h=d[e]++;f[h]=n,v[h]=a,h=d[n]++,f[h]=e,v[h]=a}),this.csrOffsets=h,this.csrIndices=f,this.csrDistances=v,this.adjacencyList=new Array(a)},e.expandRouteGraph=function(t){var r=this;if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");this.network={type:"FeatureCollection",features:[].concat(this.network.features,t.features)};for(var s=this.coordinateIndexMap,e=this.coordinates,i=this.distanceMeasurement,n=this.adjacencyList,a=0;a<n.length;a++)void 0===n[a]&&(n[a]=[]);this.forEachSegment(t,function(t,a){var h=r.indexCoordinate(t,s,e,function(t){n[t]=[]}),c=r.indexCoordinate(a,s,e,function(t){n[t]=[]}),o=i(t,a);n[h].push({node:c,distance:o}),n[c].push({node:h,distance:o})}),this.rebuildCsrFromAdjacency()},e.rebuildCsrFromAdjacency=function(){var t=this.coordinates.length,r=this.adjacencyList,s=new Int32Array(t);if(this.csrOffsets&&this.csrIndices&&this.csrDistances)for(var e=this.csrOffsets,i=Math.min(this.csrNodeCount,t),n=0;n<i;n++)s[n]+=e[n+1]-e[n];for(var a=0;a<t;a++){var h=r[a];h&&h.length&&(s[a]+=h.length)}for(var c=new Int32Array(t+1),o=0;o<t;o++)c[o+1]=c[o]+s[o];var u=c[t],l=new Int32Array(u),f=new Float64Array(u),v=c.slice();if(this.csrOffsets&&this.csrIndices&&this.csrDistances)for(var d=this.csrOffsets,I=this.csrIndices,S=this.csrDistances,g=Math.min(this.csrNodeCount,t),y=0;y<g;y++){for(var p=d[y+1],m=v[y],w=d[y];w<p;w++)l[m]=I[w],f[m]=S[w],m++;v[y]=m}for(var M=0;M<t;M++){var N=r[M];if(N&&0!==N.length){for(var C=v[M],x=0,F=N.length;x<F;x++){var O=N[x];l[C]=O.node,f[C]=O.distance,C++}v[M]=C}}this.csrOffsets=c,this.csrIndices=l,this.csrDistances=f,this.csrNodeCount=t,this.adjacencyList=new Array(t)},e.getRoute=function(t,r){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var s=this.getOrCreateIndex(t.geometry.coordinates),e=this.getOrCreateIndex(r.geometry.coordinates);if(s===e)return null;var i=this.coordinates,n=this.adjacencyList,a=i.length;this.ensureScratch(a);var h=this.gScoreScratch,c=this.gScoreRevScratch,o=this.cameFromScratch,u=this.cameFromRevScratch,l=this.visitedScratch,f=this.visitedRevScratch,v=this.hScratch,d=this.hRevScratch;h.fill(Number.POSITIVE_INFINITY,0,a),c.fill(Number.POSITIVE_INFINITY,0,a),o.fill(-1,0,a),u.fill(-1,0,a),l.fill(0,0,a),f.fill(0,0,a),v.fill(-1,0,a),d.fill(-1,0,a);var I=new this.heapConstructor,S=new this.heapConstructor;h[s]=0,c[e]=0,I.insert(0,s),S.insert(0,e);for(var g=Number.POSITIVE_INFINITY,y=-1,p=0,m=0;I.size()>0&&S.size()>0&&!(y>=0&&p+m>=g);)if(I.size()<=S.size()){var w=I.extractMin();if(0!==l[w])continue;if(p=h[w],l[w]=1,0!==f[w]){var M=h[w]+c[w];M<g&&(g=M,y=w)}if(this.csrOffsets&&w<this.csrNodeCount)for(var N=this.csrOffsets,C=this.csrIndices,x=this.csrDistances,F=N[w],O=N[w+1];F<O;F++){var b=C[F],R=h[w]+x[F];if(R<h[b]){h[b]=R,o[b]=w;var k=c[b];if(k!==Number.POSITIVE_INFINITY){var A=R+k;A<g&&(g=A,y=b)}I.insert(R,b)}}else{var T=n[w];if(T&&T.length)for(var E=0,D=T.length;E<D;E++){var P=T[E],j=P.node,L=h[w]+P.distance;if(L<h[j]){h[j]=L,o[j]=w;var V=c[j];if(V!==Number.POSITIVE_INFINITY){var Y=L+V;Y<g&&(g=Y,y=j)}I.insert(L,j)}}}}else{var _=S.extractMin();if(0!==f[_])continue;if(m=c[_],f[_]=1,0!==l[_]){var z=h[_]+c[_];z<g&&(g=z,y=_)}if(this.csrOffsets&&_<this.csrNodeCount)for(var q=this.csrOffsets,G=this.csrIndices,U=this.csrDistances,B=q[_],H=q[_+1];B<H;B++){var J=G[B],K=c[_]+U[B];if(K<c[J]){c[J]=K,u[J]=_;var Q=h[J];if(Q!==Number.POSITIVE_INFINITY){var W=K+Q;W<g&&(g=W,y=J)}S.insert(K,J)}}else{var X=n[_];if(X&&X.length)for(var Z=0,$=X.length;Z<$;Z++){var tt=X[Z],rt=tt.node,st=c[_]+tt.distance;if(st<c[rt]){c[rt]=st,u[rt]=_;var et=h[rt];if(et!==Number.POSITIVE_INFINITY){var it=st+et;it<g&&(g=it,y=rt)}S.insert(st,rt)}}}}if(y<0)return null;for(var nt=[],at=y;at!==s&&at>=0;)nt.push(i[at]),at=o[at];if(at!==s)return null;for(nt.push(i[s]),nt.reverse(),at=y;at!==e;){if((at=u[at])<0)return null;nt.push(i[at])}return{type:"Feature",geometry:{type:"LineString",coordinates:nt},properties:{}}},e.getOrCreateIndex=function(t){var r=t[0],s=t[1],e=this.coordinateIndexMap.get(r);void 0===e&&(e=new Map,this.coordinateIndexMap.set(r,e));var i=e.get(s);if(void 0===i&&(i=this.coordinates.length,this.coordinates.push(t),e.set(s,i),this.adjacencyList[i]=[],this.csrOffsets)){var n=this.csrNodeCount;if(i===n){var a=new Int32Array(n+2);a.set(this.csrOffsets,0),a[n+1]=a[n],this.csrOffsets=a,this.csrNodeCount=n+1}}return i},e.ensureScratch=function(t){if(!(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch&&this.gScoreRevScratch&&this.cameFromRevScratch&&this.visitedRevScratch&&this.hRevScratch)){var r=0|t;this.gScoreScratch=new Float64Array(r),this.cameFromScratch=new Int32Array(r),this.visitedScratch=new Uint8Array(r),this.hScratch=new Float64Array(r),this.gScoreRevScratch=new Float64Array(r),this.cameFromRevScratch=new Int32Array(r),this.visitedRevScratch=new Uint8Array(r),this.hRevScratch=new Float64Array(r),this.scratchCapacity=r}},e.forEachSegment=function(t,r){for(var s=t.features,e=0,i=s.length;e<i;e++)for(var n=s[e].geometry.coordinates,a=0,h=n.length-1;a<h;a++)r(n[a],n[a+1])},e.indexCoordinate=function(t,r,s,e){var i=t[0],n=t[1],a=r.get(i);void 0===a&&(a=new Map,r.set(i,a));var h=a.get(n);return void 0===h&&(h=s.length,s.push(t),a.set(n,h),e&&e(h)),h},r}();export{e as TerraRoute,r as createCheapRuler,t as haversineDistance};
2
2
  //# sourceMappingURL=terra-route.module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.module.js","sources":["../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/heap/four-ary-heap.ts","../src/terra-route.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n\n // Convert distance from meters to kilometers\n return distance / 1000;\n}\n","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}","import { Heap } from \"./heap\";\n\ninterface Node {\n key: number;\n value: number;\n index: number; // insertion order for stable tie-breaking\n}\n\n/**\n * A 4-ary min-heap with stable tie-breaking on insertion order.\n * Parent(i) = floor((i - 1) / 4)\n * Children(i) = 4*i + 1 .. 4*i + 4\n */\nexport class FourAryHeap implements Heap {\n // Parallel arrays for fewer object allocations and faster property access\n private keys: number[] = [];\n private values: number[] = [];\n private idxs: number[] = [];\n private length = 0; // number of valid elements\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n // Bubble-up using local variables (avoid temporary node object)\n let i = this.length;\n this.length = i + 1;\n\n let ck = key;\n let cv = value;\n let ci = this.insertCounter++;\n\n while (i > 0) {\n const p = (i - 1) >>> 2; // divide by 4\n const pk = this.keys[p];\n const pi = this.idxs[p];\n if (ck > pk || (ck === pk && ci > pi)) break;\n\n // move parent down\n this.keys[i] = pk;\n this.values[i] = this.values[p];\n this.idxs[i] = pi;\n i = p;\n }\n\n // place the new node\n this.keys[i] = ck;\n this.values[i] = cv;\n this.idxs[i] = ci;\n }\n\n extractMin(): number | null {\n const n = this.length;\n if (n === 0) return null;\n\n const minValue = this.values[0];\n const last = n - 1;\n this.length = last;\n\n if (last > 0) {\n // Move last element to root then bubble down\n this.keys[0] = this.keys[last];\n this.values[0] = this.values[last];\n this.idxs[0] = this.idxs[last];\n this.bubbleDown(0);\n }\n\n return minValue;\n }\n\n size(): number {\n return this.length;\n }\n\n private bubbleDown(i: number): void {\n const n = this.length;\n const k = this.keys;\n const v = this.values;\n const idx = this.idxs;\n\n const nodeK = k[i];\n const nodeV = v[i];\n const nodeI = idx[i];\n\n while (true) {\n const c1 = (i << 2) + 1; // 4*i + 1\n if (c1 >= n) break; // no children\n\n // find smallest among up to 4 children\n let smallest = c1;\n let sK = k[c1];\n let sI = idx[c1];\n let sV = v[c1];\n\n const c2 = c1 + 1;\n if (c2 < n) {\n const k2 = k[c2];\n const i2 = idx[c2];\n if (k2 < sK || (k2 === sK && i2 < sI)) {\n smallest = c2;\n sK = k2;\n sI = i2;\n sV = v[c2];\n }\n }\n\n const c3 = c1 + 2;\n if (c3 < n) {\n const k3 = k[c3];\n const i3 = idx[c3];\n if (k3 < sK || (k3 === sK && i3 < sI)) {\n smallest = c3;\n sK = k3;\n sI = i3;\n sV = v[c3];\n }\n }\n\n const c4 = c1 + 3;\n if (c4 < n) {\n const k4 = k[c4];\n const i4 = idx[c4];\n if (k4 < sK || (k4 === sK && i4 < sI)) {\n smallest = c4;\n sK = k4;\n sI = i4;\n sV = v[c4];\n }\n }\n\n if (sK < nodeK || (sK === nodeK && sI < nodeI)) {\n k[i] = sK;\n v[i] = sV;\n idx[i] = sI;\n i = smallest;\n } else {\n break;\n }\n }\n\n k[i] = nodeK;\n v[i] = nodeV;\n idx[i] = nodeI;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\"; // Import GeoJSON types\nimport { haversineDistance } from \"./distance/haversine\"; // Great-circle distance function (default heuristic/edge weight)\nimport { createCheapRuler } from \"./distance/cheap-ruler\"; // Factory for faster planar distance (exported for consumers)\nimport { HeapConstructor } from \"./heap/heap\"; // Heap interface so users can plug custom heaps\nimport { FourAryHeap } from \"./heap/four-ary-heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null; // The last network used to build the graph\n private distanceMeasurement: (a: Position, b: Position) => number; // Distance function used for edges and heuristic\n private heapConstructor: HeapConstructor; // Heap class used by A*\n\n // Map from longitude → (map from latitude → index) to deduplicate coordinates and get node indices quickly\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map(); // Nested map for exact coord lookup\n private coordinates: Position[] = []; // Array of all unique coordinates by index\n // Sparse adjacency list used during build and for any nodes added dynamically later\n private adjacencyList: Array<Array<{ node: number, distance: number }>> = []; // Per-node neighbor arrays used only pre-CSR or for dynamic nodes\n\n // Compressed Sparse Row adjacency representation for fast neighbor iteration in getRoute\n private csrOffsets: Int32Array | null = null; // Row pointer: length = nodeCount + 1, offsets into indices/distances\n private csrIndices: Int32Array | null = null; // Column indices: neighbor node IDs, length = totalEdges\n private csrDistances: Float64Array | null = null; // Edge weights aligned to csrIndices, length = totalEdges\n private csrNodeCount = 0; // Number of nodes captured in the CSR arrays\n\n // Reusable typed scratch buffers for A*\n private gScoreScratch: Float64Array | null = null; // gScore per node (cost from start)\n private cameFromScratch: Int32Array | null = null; // Predecessor per node for path reconstruction\n private visitedScratch: Uint8Array | null = null; // Visited set to avoid reprocessing\n private hScratch: Float64Array | null = null; // Per-node heuristic cache for the current query (lazy compute)\n private scratchCapacity = 0; // Current capacity of scratch arrays\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number; // Optional distance function override\n heap?: HeapConstructor; // Optional heap implementation override\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance; // Default to haversine\n this.heapConstructor = options?.heap ?? FourAryHeap; // Default to MinHeap\n }\n\n /**\n * Builds a graph (CSR) from a LineString FeatureCollection.\n * Two-pass build: pass 1 assigns node indices and counts degrees; pass 2 fills CSR arrays.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network; // Keep a reference to the network\n\n // Reset internal structures for a fresh build\n this.coordinateIndexMap = new Map(); // Clear coordinate index map\n this.coordinates = []; // Clear coordinates array\n this.adjacencyList = []; // Will not be populated during build; reserved for dynamic nodes post-build\n // Reset CSR structures (will rebuild below)\n this.csrOffsets = null;\n this.csrIndices = null;\n this.csrDistances = null;\n this.csrNodeCount = 0;\n\n // Hoist to locals for speed (avoid repeated property lookups in hot loops)\n const coordIndexMapLocal = this.coordinateIndexMap; // Local alias for coord map\n const coordsLocal = this.coordinates; // Local alias for coordinates array\n const measureDistance = this.distanceMeasurement; // Local alias for distance function\n\n const features = network.features; // All LineString features\n\n // Pass 1: assign indices and count degrees per node\n const degree: number[] = []; // Dynamic degree array; grows as nodes are discovered\n for (let f = 0, fLen = features.length; f < fLen; f++) { // Iterate features\n const feature = features[f]; // Current feature\n const lineCoords = feature.geometry.coordinates; // Coordinates for this LineString\n\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) { // Iterate segment pairs\n const a = lineCoords[i]; // Segment start coord\n const b = lineCoords[i + 1]; // Segment end coord\n\n const lngA = a[0], latA = a[1]; // Keys for A\n const lngB = b[0], latB = b[1]; // Keys for B\n\n // Index A\n let latMapA = coordIndexMapLocal.get(lngA);\n if (latMapA === undefined) {\n latMapA = new Map<number, number>();\n coordIndexMapLocal.set(lngA, latMapA);\n }\n let indexA = latMapA.get(latA);\n if (indexA === undefined) {\n indexA = coordsLocal.length;\n coordsLocal.push(a);\n latMapA.set(latA, indexA);\n }\n\n // Index B\n let latMapB = coordIndexMapLocal.get(lngB);\n if (latMapB === undefined) {\n latMapB = new Map<number, number>();\n coordIndexMapLocal.set(lngB, latMapB);\n }\n let indexB = latMapB.get(latB);\n if (indexB === undefined) {\n indexB = coordsLocal.length;\n coordsLocal.push(b);\n latMapB.set(latB, indexB);\n }\n\n // Count degree for both directions\n degree[indexA] = (degree[indexA] ?? 0) + 1;\n degree[indexB] = (degree[indexB] ?? 0) + 1;\n }\n }\n\n // Build CSR arrays from degree counts\n const nodeCount = this.coordinates.length; // Total nodes discovered\n this.csrNodeCount = nodeCount; // CSR covers all built nodes\n const offsets = new Int32Array(nodeCount + 1); // Row pointer array\n for (let i = 0; i < nodeCount; i++) {\n const deg = degree[i] ?? 0; // Degree of node i\n offsets[i + 1] = offsets[i] + deg; // Prefix sum\n }\n const totalEdges = offsets[nodeCount]; // Total adjacency entries\n const indices = new Int32Array(totalEdges); // Neighbor indices array\n const distances = new Float64Array(totalEdges); // Distances array aligned to indices\n\n // Pass 2: fill CSR arrays using a write cursor per node\n const cursor = offsets.slice(); // Current write positions per node\n for (let f = 0, fLen = features.length; f < fLen; f++) {\n const feature = features[f];\n const lineCoords = feature.geometry.coordinates;\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) {\n const a = lineCoords[i];\n const b = lineCoords[i + 1];\n\n const lngA = a[0], latA = a[1];\n const lngB = b[0], latB = b[1];\n\n // Read back indices (guaranteed to exist from pass 1)\n const indexA = this.coordinateIndexMap.get(lngA)!.get(latA)!;\n const indexB = this.coordinateIndexMap.get(lngB)!.get(latB)!;\n\n const segmentDistance = measureDistance(a, b); // Edge weight once\n\n // Write A → B\n let pos = cursor[indexA]++;\n indices[pos] = indexB;\n distances[pos] = segmentDistance;\n // Write B → A\n pos = cursor[indexB]++;\n indices[pos] = indexA;\n distances[pos] = segmentDistance;\n }\n }\n\n // Commit CSR to instance\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n\n // Prepare sparse shell only for dynamically added nodes later (no prefilled neighbor arrays)\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>, // Start point feature\n end: Feature<Point> // End point feature\n ): Feature<LineString> | null {\n if (this.network === null) { // Guard: graph must be built first\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Ensure start/end exist in index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates); // Get or insert start node index\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates); // Get or insert end node index\n\n // Trivial case: same node\n if (startIndex === endIndex) {\n return null; // No path needed\n }\n\n // Local aliases\n const coords = this.coordinates; // Alias to coordinates array\n const adj = this.adjacencyList; // Alias to sparse adjacency list (for dynamic nodes)\n const measureDistance = this.distanceMeasurement; // Alias to distance function\n\n // Ensure and init scratch buffers\n const nodeCount = coords.length; // Current number of nodes (may be >= csrNodeCount if new nodes added)\n this.ensureScratch(nodeCount); // Allocate scratch arrays if needed\n\n // Non-null after ensure\n const gScore = this.gScoreScratch!; // gScore pointer\n const cameFrom = this.cameFromScratch!; // cameFrom pointer\n const visited = this.visitedScratch!; // visited pointer\n const hCache = this.hScratch!; // heuristic cache pointer\n\n // Reset only the used range for speed\n gScore.fill(Number.POSITIVE_INFINITY, 0, nodeCount); // Init gScore to +∞\n cameFrom.fill(-1, 0, nodeCount); // Init predecessors to -1 (unknown)\n visited.fill(0, 0, nodeCount); // Init visited flags to 0\n hCache.fill(-1, 0, nodeCount); // Init heuristic cache with sentinel (-1 means unknown)\n\n // Create min-heap (priority queue)\n const openSet = new this.heapConstructor();\n\n // Precompute heuristic for start to prime the queue cost\n const endCoord = coords[endIndex]; // Cache end coordinate for heuristic\n let hStart = hCache[startIndex];\n\n if (hStart < 0) {\n hStart = measureDistance(coords[startIndex], endCoord);\n hCache[startIndex] = hStart;\n }\n\n openSet.insert(hStart, startIndex); // Insert start with f = g(0) + h(start)\n gScore[startIndex] = 0; // g(start) = 0\n\n while (openSet.size() > 0) { // Main A* loop until queue empty\n const current = openSet.extractMin()!; // Pop node with smallest f\n if (visited[current] !== 0) { // Skip if already finalized\n continue;\n }\n if (current === endIndex) { // Early exit if reached goal\n break;\n }\n visited[current] = 1; // Mark as visited\n\n // Prefer CSR neighbors if available for this node, fall back to sparse list for dynamically added nodes\n\n if (this.csrOffsets && current < this.csrNodeCount) { // Use CSR fast path\n const csrOffsets = this.csrOffsets!; // Local CSR offsets (non-null here)\n const csrIndices = this.csrIndices!; // Local CSR neighbors\n const csrDistances = this.csrDistances!; // Local CSR weights\n const startOff = csrOffsets[current]; // Row start for current\n const endOff = csrOffsets[current + 1]; // Row end for current\n\n for (let i = startOff; i < endOff; i++) { // Iterate neighbors in CSR slice\n const nbNode = csrIndices[i]; // Neighbor node id\n const tentativeG = gScore[current] + csrDistances[i]; // g' = g(current) + w(current, nb)\n if (tentativeG < gScore[nbNode]) { // Relaxation check\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n // A* priority = g + h (cache h per node for this query)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Push/update neighbor into open set\n }\n }\n }\n // Fallback: use sparse adjacency (only for nodes added after CSR build)\n else {\n\n const neighbors = adj[current]; // Neighbor list for current\n if (!neighbors || neighbors.length === 0) continue; // No neighbors\n for (let i = 0, n = neighbors.length; i < n; i++) { // Iterate neighbors\n const nb = neighbors[i]; // Neighbor entry\n const nbNode = nb.node; // Neighbor id\n\n const tentativeG = gScore[current] + nb.distance; // g' via current\n if (tentativeG < gScore[nbNode]) { // Relax if better\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n\n // A* priority = g + h (cached)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Enqueue neighbor\n }\n }\n }\n }\n\n // If goal was never reached/relaxed, return null\n if (cameFrom[endIndex] < 0) {\n return null;\n }\n\n // Reconstruct path (push + reverse to avoid O(n^2) unshift)\n const path: Position[] = [];\n let cur = endIndex;\n while (cur !== startIndex) {\n path.push(coords[cur]);\n cur = cameFrom[cur];\n }\n // Include start coordinate\n path.push(coords[startIndex]);\n\n // Reverse to get start→end order\n path.reverse();\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number { // Ensure a coordinate has a node index, creating if absent\n const lng = coord[0]; // Extract longitude\n const lat = coord[1]; // Extract latitude\n\n let latMap = this.coordinateIndexMap.get(lng); // Get lat→index map for this longitude\n if (latMap === undefined) { // Create if missing\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n\n let index = latMap.get(lat); // Lookup index by latitude\n if (index === undefined) { // If not found, append new node\n\n index = this.coordinates.length; // New index at end\n this.coordinates.push(coord); // Store coordinate\n latMap.set(lat, index); // Record mapping\n\n // Ensure sparse adjacency slot for dynamically added nodes\n this.adjacencyList[index] = []; // Init empty neighbor array\n\n // Extend CSR offsets to keep indices consistent (no neighbors for new node)\n if (this.csrOffsets) { // Only adjust if CSR already built\n\n // Only need to expand offsets by one; indices/distances remain unchanged\n const oldCount = this.csrNodeCount; // Nodes currently covered by CSR\n\n // Appending exactly one new node at the end\n if (index === oldCount) {\n const newOffsets = new Int32Array(oldCount + 2); // Allocate offsets for +1 node\n newOffsets.set(this.csrOffsets, 0); // Copy previous offsets\n\n // Last offset repeats to indicate zero neighbors\n newOffsets[oldCount + 1] = newOffsets[oldCount]; // Replicate last pointer\n this.csrOffsets = newOffsets; // Swap in new offsets\n this.csrNodeCount = oldCount + 1; // Increment CSR node count\n }\n }\n }\n\n return index;\n }\n\n // Ensure scratch arrays are allocated with at least `size` capacity.\n private ensureScratch(size: number): void {\n const ifAlreadyBigEnough = this.scratchCapacity >= size\n && this.gScoreScratch\n && this.cameFromScratch\n && this.visitedScratch\n && this.hScratch;\n\n if (ifAlreadyBigEnough) {\n return; // Nothing to do\n }\n const capacity = size | 0; // Ensure integer\n this.gScoreScratch = new Float64Array(capacity);\n this.cameFromScratch = new Int32Array(capacity);\n this.visitedScratch = new Uint8Array(capacity);\n this.hScratch = new Float64Array(capacity);\n this.scratchCapacity = capacity;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance } "],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","createCheapRuler","lat","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy","FourAryHeap","keys","this","values","idxs","length","insertCounter","_proto","prototype","insert","key","value","i","ck","cv","ci","p","pk","pi","extractMin","n","minValue","last","bubbleDown","size","k","v","idx","nodeK","nodeV","nodeI","c1","smallest","sK","sI","sV","c2","k2","i2","c3","k3","i3","c4","k4","i4","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","csrOffsets","csrIndices","csrDistances","csrNodeCount","gScoreScratch","cameFromScratch","visitedScratch","hScratch","scratchCapacity","heap","buildRouteGraph","coordIndexMapLocal","coordsLocal","measureDistance","features","degree","f","fLen","lineCoords","geometry","len","_degree$indexA","_degree$indexB","lngA","latA","lngB","latB","latMapA","get","undefined","set","indexA","push","latMapB","indexB","nodeCount","offsets","Int32Array","_degree$_i","deg","totalEdges","indices","distances","Float64Array","cursor","slice","segmentDistance","pos","Array","getRoute","start","end","Error","startIndex","getOrCreateIndex","endIndex","coords","adj","ensureScratch","gScore","cameFrom","visited","hCache","fill","Number","POSITIVE_INFINITY","openSet","endCoord","hStart","current","endOff","nbNode","tentativeG","hVal","neighbors","nb","node","distance","path","cur","reverse","type","properties","coord","lng","latMap","index","oldCount","newOffsets","capacity","Uint8Array"],"mappings":"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"}
1
+ {"version":3,"file":"terra-route.module.js","sources":["../src/distance/haversine.ts","../src/distance/cheap-ruler.ts","../src/heap/four-ary-heap.ts","../src/terra-route.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n\n // Convert distance from meters to kilometers\n return distance / 1000;\n}\n","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}","import { Heap } from \"./heap\";\n\ninterface Node {\n key: number;\n value: number;\n index: number; // insertion order for stable tie-breaking\n}\n\n/**\n * A 4-ary min-heap with stable tie-breaking on insertion order.\n * Parent(i) = floor((i - 1) / 4)\n * Children(i) = 4*i + 1 .. 4*i + 4\n */\nexport class FourAryHeap implements Heap {\n // Parallel arrays for fewer object allocations and faster property access\n private keys: number[] = [];\n private values: number[] = [];\n private idxs: number[] = [];\n private length = 0; // number of valid elements\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n // Bubble-up using local variables (avoid temporary node object)\n let i = this.length;\n this.length = i + 1;\n\n let ck = key;\n let cv = value;\n let ci = this.insertCounter++;\n\n while (i > 0) {\n const p = (i - 1) >>> 2; // divide by 4\n const pk = this.keys[p];\n const pi = this.idxs[p];\n if (ck > pk || (ck === pk && ci > pi)) break;\n\n // move parent down\n this.keys[i] = pk;\n this.values[i] = this.values[p];\n this.idxs[i] = pi;\n i = p;\n }\n\n // place the new node\n this.keys[i] = ck;\n this.values[i] = cv;\n this.idxs[i] = ci;\n }\n\n extractMin(): number | null {\n const n = this.length;\n if (n === 0) return null;\n\n const minValue = this.values[0];\n const last = n - 1;\n this.length = last;\n\n if (last > 0) {\n // Move last element to root then bubble down\n this.keys[0] = this.keys[last];\n this.values[0] = this.values[last];\n this.idxs[0] = this.idxs[last];\n this.bubbleDown(0);\n }\n\n return minValue;\n }\n\n size(): number {\n return this.length;\n }\n\n private bubbleDown(i: number): void {\n const n = this.length;\n const k = this.keys;\n const v = this.values;\n const idx = this.idxs;\n\n const nodeK = k[i];\n const nodeV = v[i];\n const nodeI = idx[i];\n\n while (true) {\n const c1 = (i << 2) + 1; // 4*i + 1\n if (c1 >= n) break; // no children\n\n // find smallest among up to 4 children\n let smallest = c1;\n let sK = k[c1];\n let sI = idx[c1];\n let sV = v[c1];\n\n const c2 = c1 + 1;\n if (c2 < n) {\n const k2 = k[c2];\n const i2 = idx[c2];\n if (k2 < sK || (k2 === sK && i2 < sI)) {\n smallest = c2;\n sK = k2;\n sI = i2;\n sV = v[c2];\n }\n }\n\n const c3 = c1 + 2;\n if (c3 < n) {\n const k3 = k[c3];\n const i3 = idx[c3];\n if (k3 < sK || (k3 === sK && i3 < sI)) {\n smallest = c3;\n sK = k3;\n sI = i3;\n sV = v[c3];\n }\n }\n\n const c4 = c1 + 3;\n if (c4 < n) {\n const k4 = k[c4];\n const i4 = idx[c4];\n if (k4 < sK || (k4 === sK && i4 < sI)) {\n smallest = c4;\n sK = k4;\n sI = i4;\n sV = v[c4];\n }\n }\n\n if (sK < nodeK || (sK === nodeK && sI < nodeI)) {\n k[i] = sK;\n v[i] = sV;\n idx[i] = sI;\n i = smallest;\n } else {\n break;\n }\n }\n\n k[i] = nodeK;\n v[i] = nodeV;\n idx[i] = nodeI;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\"; // Import GeoJSON types\nimport { haversineDistance } from \"./distance/haversine\"; // Great-circle distance function (default heuristic/edge weight)\nimport { createCheapRuler } from \"./distance/cheap-ruler\"; // Factory for faster planar distance (exported for consumers)\nimport { HeapConstructor } from \"./heap/heap\"; // Heap interface so users can plug custom heaps\nimport { FourAryHeap } from \"./heap/four-ary-heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n expandRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null; // The last network used to build the graph\n private distanceMeasurement: (a: Position, b: Position) => number; // Distance function used for edges and heuristic\n private heapConstructor: HeapConstructor; // Heap class used by A*\n\n // Map from longitude → (map from latitude → index) to deduplicate coordinates and get node indices quickly\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map(); // Nested map for exact coord lookup\n private coordinates: Position[] = []; // Array of all unique coordinates by index\n // Sparse adjacency list used during build and for any nodes added dynamically later\n private adjacencyList: Array<Array<{ node: number, distance: number }>> = []; // Per-node neighbor arrays used only pre-CSR or for dynamic nodes\n\n // Compressed Sparse Row adjacency representation for fast neighbor iteration in getRoute\n private csrOffsets: Int32Array | null = null; // Row pointer: length = nodeCount + 1, offsets into indices/distances\n private csrIndices: Int32Array | null = null; // Column indices: neighbor node IDs, length = totalEdges\n private csrDistances: Float64Array | null = null; // Edge weights aligned to csrIndices, length = totalEdges\n private csrNodeCount = 0; // Number of nodes captured in the CSR arrays\n\n // Reusable typed scratch buffers for A*\n private gScoreScratch: Float64Array | null = null; // gScore per node (cost from start)\n private cameFromScratch: Int32Array | null = null; // Predecessor per node for path reconstruction\n private visitedScratch: Uint8Array | null = null; // Visited set to avoid reprocessing\n private hScratch: Float64Array | null = null; // Per-node heuristic cache for the current query (lazy compute)\n // Reverse-direction scratch for bidirectional search\n private gScoreRevScratch: Float64Array | null = null; // gScore per node from the end\n private cameFromRevScratch: Int32Array | null = null; // Successor per node (next step toward the end)\n private visitedRevScratch: Uint8Array | null = null; // Visited set for reverse search\n private hRevScratch: Float64Array | null = null; // Heuristic cache for reverse direction per query\n private scratchCapacity = 0; // Current capacity of scratch arrays\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number; // Optional distance function override\n heap?: HeapConstructor; // Optional heap implementation override\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance; // Default to haversine\n this.heapConstructor = options?.heap ?? FourAryHeap; // Default to MinHeap\n }\n\n /**\n * Builds a graph (CSR) from a LineString FeatureCollection.\n * Two-pass build: pass 1 assigns node indices and counts degrees; pass 2 fills CSR arrays.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network; // Keep a reference to the network\n\n // Reset internal structures for a fresh build\n this.coordinateIndexMap = new Map(); // Clear coordinate index map\n this.coordinates = []; // Clear coordinates array\n this.adjacencyList = []; // Will not be populated during build; reserved for dynamic nodes post-build\n // Reset CSR structures (will rebuild below)\n this.csrOffsets = null;\n this.csrIndices = null;\n this.csrDistances = null;\n this.csrNodeCount = 0;\n\n // Hoist to locals for speed (avoid repeated property lookups in hot loops)\n const coordIndexMapLocal = this.coordinateIndexMap; // Local alias for coord map\n const coordsLocal = this.coordinates; // Local alias for coordinates array\n const measureDistance = this.distanceMeasurement; // Local alias for distance function\n\n // Pass 1: assign indices and count degrees per node\n const degree: number[] = []; // Dynamic degree array; grows as nodes are discovered\n this.forEachSegment(network, (a, b) => {\n const indexA = this.indexCoordinate(a, coordIndexMapLocal, coordsLocal);\n const indexB = this.indexCoordinate(b, coordIndexMapLocal, coordsLocal);\n\n degree[indexA] = (degree[indexA] ?? 0) + 1;\n degree[indexB] = (degree[indexB] ?? 0) + 1;\n });\n\n // Build CSR arrays from degree counts\n const nodeCount = this.coordinates.length; // Total nodes discovered\n this.csrNodeCount = nodeCount; // CSR covers all built nodes\n const offsets = new Int32Array(nodeCount + 1); // Row pointer array\n for (let i = 0; i < nodeCount; i++) {\n const deg = degree[i] ?? 0; // Degree of node i\n offsets[i + 1] = offsets[i] + deg; // Prefix sum\n }\n const totalEdges = offsets[nodeCount]; // Total adjacency entries\n const indices = new Int32Array(totalEdges); // Neighbor indices array\n const distances = new Float64Array(totalEdges); // Distances array aligned to indices\n\n // Pass 2: fill CSR arrays using a write cursor per node\n const cursor = offsets.slice(); // Current write positions per node\n this.forEachSegment(network, (a, b) => {\n // Read back indices (guaranteed to exist from pass 1)\n const indexA = this.coordinateIndexMap.get(a[0])!.get(a[1])!;\n const indexB = this.coordinateIndexMap.get(b[0])!.get(b[1])!;\n\n const segmentDistance = measureDistance(a, b); // Edge weight once\n\n let pos = cursor[indexA]++;\n indices[pos] = indexB;\n distances[pos] = segmentDistance;\n pos = cursor[indexB]++;\n indices[pos] = indexA;\n distances[pos] = segmentDistance;\n });\n\n // Commit CSR to instance\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n\n // Prepare sparse shell only for dynamically added nodes later (no prefilled neighbor arrays)\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Expands (merges) the existing graph with an additional LineString FeatureCollection.\n */\n public expandRouteGraph(network: FeatureCollection<LineString>): void {\n if (this.network === null) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Merge the feature arrays for reference/debugging. We avoid copying properties deeply.\n this.network = {\n type: \"FeatureCollection\",\n features: [...this.network.features, ...network.features],\n };\n\n const coordIndexMapLocal = this.coordinateIndexMap;\n const coordsLocal = this.coordinates;\n const measureDistance = this.distanceMeasurement;\n const adj = this.adjacencyList;\n\n // Ensure we have adjacency arrays for any existing CSR-only nodes.\n // (During buildRouteGraph, adjacencyList is sized but entries are undefined.)\n for (let i = 0; i < adj.length; i++) {\n if (adj[i] === undefined) adj[i] = [];\n }\n\n // Add new edges into the adjacency list (sparse), then rebuild CSR from adjacency.\n this.forEachSegment(network, (a, b) => {\n const indexA = this.indexCoordinate(a, coordIndexMapLocal, coordsLocal, (idx) => { adj[idx] = []; });\n const indexB = this.indexCoordinate(b, coordIndexMapLocal, coordsLocal, (idx) => { adj[idx] = []; });\n\n const segmentDistance = measureDistance(a, b);\n\n adj[indexA].push({ node: indexB, distance: segmentDistance });\n adj[indexB].push({ node: indexA, distance: segmentDistance });\n });\n\n this.rebuildCsrFromAdjacency();\n }\n\n /**\n * Rebuild CSR arrays for the full node set, using:\n * - Existing CSR edges (from the last build/expand)\n * - Any additional edges stored in `adjacencyList`\n */\n private rebuildCsrFromAdjacency(): void {\n const nodeCount = this.coordinates.length;\n const adj = this.adjacencyList;\n\n // Compute degree using CSR degree + adjacency degree\n const degree = new Int32Array(nodeCount);\n\n if (this.csrOffsets && this.csrIndices && this.csrDistances) {\n const csrOffsets = this.csrOffsets;\n const covered = Math.min(this.csrNodeCount, nodeCount);\n for (let i = 0; i < covered; i++) {\n degree[i] += (csrOffsets[i + 1] - csrOffsets[i]);\n }\n }\n\n for (let i = 0; i < nodeCount; i++) {\n const neighbors = adj[i];\n if (neighbors && neighbors.length) degree[i] += neighbors.length;\n }\n\n const offsets = new Int32Array(nodeCount + 1);\n for (let i = 0; i < nodeCount; i++) {\n offsets[i + 1] = offsets[i] + degree[i];\n }\n const totalEdges = offsets[nodeCount];\n const indices = new Int32Array(totalEdges);\n const distances = new Float64Array(totalEdges);\n const cursor = offsets.slice();\n\n // Copy existing CSR edges first\n if (this.csrOffsets && this.csrIndices && this.csrDistances) {\n const csrOffsets = this.csrOffsets;\n const csrIndices = this.csrIndices;\n const csrDistances = this.csrDistances;\n const covered = Math.min(this.csrNodeCount, nodeCount);\n for (let n = 0; n < covered; n++) {\n const startOff = csrOffsets[n];\n const endOff = csrOffsets[n + 1];\n let pos = cursor[n];\n for (let i = startOff; i < endOff; i++) {\n indices[pos] = csrIndices[i];\n distances[pos] = csrDistances[i];\n pos++;\n }\n cursor[n] = pos;\n }\n }\n\n // Append adjacency edges\n for (let n = 0; n < nodeCount; n++) {\n const neighbors = adj[n];\n if (!neighbors || neighbors.length === 0) continue;\n let pos = cursor[n];\n for (let i = 0, len = neighbors.length; i < len; i++) {\n const nb = neighbors[i];\n indices[pos] = nb.node;\n distances[pos] = nb.distance;\n pos++;\n }\n cursor[n] = pos;\n }\n\n // Commit and reset adjacency (we've absorbed edges into CSR)\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n this.csrNodeCount = nodeCount;\n\n // Keep adjacency list for *future* dynamic additions, but clear existing edges to avoid duplication.\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>, // Start point feature\n end: Feature<Point> // End point feature\n ): Feature<LineString> | null {\n if (this.network === null) { // Guard: graph must be built first\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Ensure start/end exist in index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates); // Get or insert start node index\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates); // Get or insert end node index\n\n // Trivial case: same node\n if (startIndex === endIndex) {\n return null; // No path needed\n }\n\n // Local aliases\n const coords = this.coordinates; // Alias to coordinates array\n const adj = this.adjacencyList; // Alias to sparse adjacency list (for dynamic nodes)\n\n // Ensure and init scratch buffers\n const nodeCount = coords.length; // Current number of nodes (may be >= csrNodeCount if new nodes added)\n this.ensureScratch(nodeCount); // Allocate scratch arrays if needed\n\n // Non-null after ensure\n const gF = this.gScoreScratch!; // forward gScore (from start)\n const gR = this.gScoreRevScratch!; // reverse gScore (from end)\n const prevF = this.cameFromScratch!; // predecessor in forward search\n const nextR = this.cameFromRevScratch!; // successor in reverse search (toward end)\n const visF = this.visitedScratch!;\n const visR = this.visitedRevScratch!;\n const hF = this.hScratch!;\n const hR = this.hRevScratch!;\n\n gF.fill(Number.POSITIVE_INFINITY, 0, nodeCount);\n gR.fill(Number.POSITIVE_INFINITY, 0, nodeCount);\n prevF.fill(-1, 0, nodeCount);\n nextR.fill(-1, 0, nodeCount);\n visF.fill(0, 0, nodeCount);\n visR.fill(0, 0, nodeCount);\n hF.fill(-1, 0, nodeCount);\n hR.fill(-1, 0, nodeCount);\n\n const openF = new this.heapConstructor();\n const openR = new this.heapConstructor();\n\n const startCoord = coords[startIndex];\n const endCoord = coords[endIndex];\n\n gF[startIndex] = 0;\n gR[endIndex] = 0;\n\n // Bidirectional Dijkstra (A* with zero heuristic). This keeps correctness simple and matches the\n // reference pathfinder while still saving work by meeting in the middle.\n openF.insert(0, startIndex);\n openR.insert(0, endIndex);\n\n // Best meeting point found so far\n let bestPathCost = Number.POSITIVE_INFINITY;\n let meetingNode = -1;\n\n // Main bidirectional loop: expand alternately.\n // Without a heap peek, a safe and effective stopping rule is based on the last extracted keys:\n // once min_g_forward + min_g_reverse >= bestPathCost, no shorter path can still be found.\n let lastExtractedGForward = 0;\n let lastExtractedGReverse = 0;\n while (openF.size() > 0 && openR.size() > 0) {\n if (meetingNode >= 0 && (lastExtractedGForward + lastExtractedGReverse) >= bestPathCost) {\n break;\n }\n\n // Expand one step from each side, prioritizing the smaller frontier.\n const expandForward = openF.size() <= openR.size();\n\n if (expandForward) {\n const current = openF.extractMin()!;\n if (visF[current] !== 0) continue;\n lastExtractedGForward = gF[current];\n visF[current] = 1;\n\n // If reverse has finalized this node, we have a candidate meeting.\n if (visR[current] !== 0) {\n const total = gF[current] + gR[current];\n if (total < bestPathCost) {\n bestPathCost = total;\n meetingNode = current;\n }\n }\n\n // Relax neighbors and push newly improved ones\n if (this.csrOffsets && current < this.csrNodeCount) {\n const csrOffsets = this.csrOffsets!;\n const csrIndices = this.csrIndices!;\n const csrDistances = this.csrDistances!;\n for (let i = csrOffsets[current], endOff = csrOffsets[current + 1]; i < endOff; i++) {\n const nbNode = csrIndices[i];\n const tentativeG = gF[current] + csrDistances[i];\n if (tentativeG < gF[nbNode]) {\n gF[nbNode] = tentativeG;\n prevF[nbNode] = current;\n const otherG = gR[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openF.insert(tentativeG, nbNode);\n }\n }\n } else {\n const neighbors = adj[current];\n if (neighbors && neighbors.length) {\n for (let i = 0, n = neighbors.length; i < n; i++) {\n const nb = neighbors[i];\n const nbNode = nb.node;\n const tentativeG = gF[current] + nb.distance;\n if (tentativeG < gF[nbNode]) {\n gF[nbNode] = tentativeG;\n prevF[nbNode] = current;\n const otherG = gR[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openF.insert(tentativeG, nbNode);\n }\n }\n }\n }\n } else {\n const current = openR.extractMin()!;\n if (visR[current] !== 0) continue;\n lastExtractedGReverse = gR[current];\n visR[current] = 1;\n\n if (visF[current] !== 0) {\n const total = gF[current] + gR[current];\n if (total < bestPathCost) {\n bestPathCost = total;\n meetingNode = current;\n }\n }\n\n // Reverse direction: same neighbor iteration because graph is undirected.\n // Store successor pointer (next step toward end) i.e. nextR[neighbor] = current.\n if (this.csrOffsets && current < this.csrNodeCount) {\n const csrOffsets = this.csrOffsets!;\n const csrIndices = this.csrIndices!;\n const csrDistances = this.csrDistances!;\n for (let i = csrOffsets[current], endOff = csrOffsets[current + 1]; i < endOff; i++) {\n const nbNode = csrIndices[i];\n const tentativeG = gR[current] + csrDistances[i];\n if (tentativeG < gR[nbNode]) {\n gR[nbNode] = tentativeG;\n nextR[nbNode] = current;\n const otherG = gF[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openR.insert(tentativeG, nbNode);\n }\n }\n } else {\n const neighbors = adj[current];\n if (neighbors && neighbors.length) {\n for (let i = 0, n = neighbors.length; i < n; i++) {\n const nb = neighbors[i];\n const nbNode = nb.node;\n const tentativeG = gR[current] + nb.distance;\n if (tentativeG < gR[nbNode]) {\n gR[nbNode] = tentativeG;\n nextR[nbNode] = current;\n const otherG = gF[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openR.insert(tentativeG, nbNode);\n }\n }\n }\n }\n }\n }\n\n if (meetingNode < 0) {\n return null;\n }\n\n // Reconstruct path: start -> meeting using prevF, then meeting -> end using nextR\n const path: Position[] = [];\n\n // Walk back from meeting to start, collecting nodes\n let cur = meetingNode;\n while (cur !== startIndex && cur >= 0) {\n path.push(coords[cur]);\n cur = prevF[cur];\n }\n if (cur !== startIndex) {\n // Forward tree doesn't connect start to meeting (shouldn't happen if meeting is valid)\n return null;\n }\n path.push(coords[startIndex]);\n path.reverse();\n\n // Walk from meeting to end (skip meeting node because it's already included)\n cur = meetingNode;\n while (cur !== endIndex) {\n cur = nextR[cur];\n if (cur < 0) {\n return null;\n }\n path.push(coords[cur]);\n }\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number { // Ensure a coordinate has a node index, creating if absent\n const lng = coord[0]; // Extract longitude\n const lat = coord[1]; // Extract latitude\n\n let latMap = this.coordinateIndexMap.get(lng); // Get lat→index map for this longitude\n if (latMap === undefined) { // Create if missing\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n\n let index = latMap.get(lat); // Lookup index by latitude\n if (index === undefined) { // If not found, append new node\n\n index = this.coordinates.length; // New index at end\n this.coordinates.push(coord); // Store coordinate\n latMap.set(lat, index); // Record mapping\n\n // Ensure sparse adjacency slot for dynamically added nodes\n this.adjacencyList[index] = []; // Init empty neighbor array\n\n // Extend CSR offsets to keep indices consistent (no neighbors for new node)\n if (this.csrOffsets) { // Only adjust if CSR already built\n\n // Only need to expand offsets by one; indices/distances remain unchanged\n const oldCount = this.csrNodeCount; // Nodes currently covered by CSR\n\n // Appending exactly one new node at the end\n if (index === oldCount) {\n const newOffsets = new Int32Array(oldCount + 2); // Allocate offsets for +1 node\n newOffsets.set(this.csrOffsets, 0); // Copy previous offsets\n\n // Last offset repeats to indicate zero neighbors\n newOffsets[oldCount + 1] = newOffsets[oldCount]; // Replicate last pointer\n this.csrOffsets = newOffsets; // Swap in new offsets\n this.csrNodeCount = oldCount + 1; // Increment CSR node count\n }\n }\n }\n\n return index;\n }\n\n // Ensure scratch arrays are allocated with at least `size` capacity.\n private ensureScratch(size: number): void {\n const ifAlreadyBigEnough = this.scratchCapacity >= size\n && this.gScoreScratch\n && this.cameFromScratch\n && this.visitedScratch\n && this.hScratch\n && this.gScoreRevScratch\n && this.cameFromRevScratch\n && this.visitedRevScratch\n && this.hRevScratch;\n\n if (ifAlreadyBigEnough) {\n return; // Nothing to do\n }\n const capacity = size | 0; // Ensure integer\n this.gScoreScratch = new Float64Array(capacity);\n this.cameFromScratch = new Int32Array(capacity);\n this.visitedScratch = new Uint8Array(capacity);\n this.hScratch = new Float64Array(capacity);\n this.gScoreRevScratch = new Float64Array(capacity);\n this.cameFromRevScratch = new Int32Array(capacity);\n this.visitedRevScratch = new Uint8Array(capacity);\n this.hRevScratch = new Float64Array(capacity);\n this.scratchCapacity = capacity;\n }\n\n\n // Iterate all consecutive segment pairs in a LineString FeatureCollection.\n // Kept as a simple loop helper to avoid repeating nested iteration logic.\n private forEachSegment(\n network: FeatureCollection<LineString>,\n fn: (a: Position, b: Position) => void,\n ): void {\n const features = network.features;\n for (let f = 0, fLen = features.length; f < fLen; f++) {\n const lineCoords = features[f].geometry.coordinates;\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) {\n // GeoJSON coordinates are compatible with Position (number[]), and the project assumes [lng,lat]\n fn(lineCoords[i] as Position, lineCoords[i + 1] as Position);\n }\n }\n }\n\n // Hot-path coordinate indexer used by both build/expand.\n // Accepts explicit maps/arrays so callers can hoist them once.\n private indexCoordinate(\n coord: Position,\n coordIndexMapLocal: Map<number, Map<number, number>>,\n coordsLocal: Position[],\n onNewIndex?: (index: number) => void,\n ): number {\n const lng = coord[0];\n const lat = coord[1];\n\n let latMap = coordIndexMapLocal.get(lng);\n if (latMap === undefined) {\n latMap = new Map<number, number>();\n coordIndexMapLocal.set(lng, latMap);\n }\n\n let idx = latMap.get(lat);\n if (idx === undefined) {\n idx = coordsLocal.length;\n coordsLocal.push(coord);\n latMap.set(lat, idx);\n if (onNewIndex) onNewIndex(idx);\n }\n\n return idx;\n }\n\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance } "],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","createCheapRuler","lat","FE","E2","RAD","cosLat","w2","w","m","kx","ky","b","deltaLng","dx","dy","FourAryHeap","keys","this","values","idxs","length","insertCounter","_proto","prototype","insert","key","value","i","ck","cv","ci","p","pk","pi","extractMin","n","minValue","last","bubbleDown","size","k","v","idx","nodeK","nodeV","nodeI","c1","smallest","sK","sI","sV","c2","k2","i2","c3","k3","i3","c4","k4","i4","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","csrOffsets","csrIndices","csrDistances","csrNodeCount","gScoreScratch","cameFromScratch","visitedScratch","hScratch","gScoreRevScratch","cameFromRevScratch","visitedRevScratch","hRevScratch","scratchCapacity","heap","buildRouteGraph","_this","coordIndexMapLocal","coordsLocal","measureDistance","degree","forEachSegment","_degree$indexA","_degree$indexB","indexA","indexCoordinate","indexB","nodeCount","offsets","Int32Array","_degree$i","deg","totalEdges","indices","distances","Float64Array","cursor","slice","get","segmentDistance","pos","Array","expandRouteGraph","_this2","Error","type","features","concat","adj","undefined","push","node","distance","rebuildCsrFromAdjacency","covered","min","neighbors","endOff","len","nb","getRoute","start","end","startIndex","getOrCreateIndex","geometry","endIndex","coords","ensureScratch","gF","gR","prevF","nextR","visF","visR","hF","hR","fill","Number","POSITIVE_INFINITY","openF","openR","bestPathCost","meetingNode","lastExtractedGForward","lastExtractedGReverse","current","total","nbNode","tentativeG","otherG","path","cur","reverse","properties","coord","lng","latMap","set","index","oldCount","newOffsets","capacity","Uint8Array","fn","f","fLen","lineCoords","onNewIndex"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t||self).terraRoute={})}(this,function(t){var e=function(t,e){var s=function(t){return t*Math.PI/180},r=s(t[1]),i=s(t[0]),n=s(e[1]),a=n-r,h=s(e[0])-i,o=Math.sin(a/2)*Math.sin(a/2)+Math.cos(r)*Math.cos(n)*Math.sin(h/2)*Math.sin(h/2);return 2*Math.atan2(Math.sqrt(o),Math.sqrt(1-o))*6371e3/1e3},s=/*#__PURE__*/function(){function t(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}var e=t.prototype;return e.insert=function(t,e){var s=this.length;this.length=s+1;for(var r=t,i=e,n=this.insertCounter++;s>0;){var a=s-1>>>2,h=this.keys[a],o=this.idxs[a];if(r>h||r===h&&n>o)break;this.keys[s]=h,this.values[s]=this.values[a],this.idxs[s]=o,s=a}this.keys[s]=r,this.values[s]=i,this.idxs[s]=n},e.extractMin=function(){var t=this.length;if(0===t)return null;var e=this.values[0],s=t-1;return this.length=s,s>0&&(this.keys[0]=this.keys[s],this.values[0]=this.values[s],this.idxs[0]=this.idxs[s],this.bubbleDown(0)),e},e.size=function(){return this.length},e.bubbleDown=function(t){for(var e=this.length,s=this.keys,r=this.values,i=this.idxs,n=s[t],a=r[t],h=i[t];;){var o=1+(t<<2);if(o>=e)break;var c=o,u=s[o],l=i[o],d=r[o],f=o+1;if(f<e){var v=s[f],g=i[f];(v<u||v===u&&g<l)&&(c=f,u=v,l=g,d=r[f])}var p=o+2;if(p<e){var y=s[p],M=i[p];(y<u||y===u&&M<l)&&(c=p,u=y,l=M,d=r[p])}var I=o+3;if(I<e){var w=s[I],S=i[I];(w<u||w===u&&S<l)&&(c=I,u=w,l=S,d=r[I])}if(!(u<n||u===n&&l<h))break;s[t]=u,r[t]=d,i[t]=l,t=c}s[t]=n,r[t]=a,i[t]=h},t}();t.TerraRoute=/*#__PURE__*/function(){function t(t){var r,i;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(r=null==t?void 0:t.distanceMeasurement)?r:e,this.heapConstructor=null!=(i=null==t?void 0:t.heap)?i:s}var r=t.prototype;return r.buildRouteGraph=function(t){this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;for(var e=this.coordinateIndexMap,s=this.coordinates,r=this.distanceMeasurement,i=t.features,n=[],a=0,h=i.length;a<h;a++)for(var o=i[a].geometry.coordinates,c=0,u=o.length-1;c<u;c++){var l,d,f=o[c],v=o[c+1],g=f[0],p=f[1],y=v[0],M=v[1],I=e.get(g);void 0===I&&(I=new Map,e.set(g,I));var w=I.get(p);void 0===w&&(w=s.length,s.push(f),I.set(p,w));var S=e.get(y);void 0===S&&(S=new Map,e.set(y,S));var x=S.get(M);void 0===x&&(x=s.length,s.push(v),S.set(M,x)),n[w]=(null!=(l=n[w])?l:0)+1,n[x]=(null!=(d=n[x])?d:0)+1}var m=this.coordinates.length;this.csrNodeCount=m;for(var C=new Int32Array(m+1),b=0;b<m;b++){var k,O=null!=(k=n[b])?k:0;C[b+1]=C[b]+O}for(var N=C[m],A=new Int32Array(N),F=new Float64Array(N),D=C.slice(),j=0,L=i.length;j<L;j++)for(var R=i[j].geometry.coordinates,T=0,q=R.length-1;T<q;T++){var P=R[T],z=R[T+1],E=P[1],G=z[0],U=z[1],V=this.coordinateIndexMap.get(P[0]).get(E),Y=this.coordinateIndexMap.get(G).get(U),_=r(P,z),B=D[V]++;A[B]=Y,F[B]=_,A[B=D[Y]++]=V,F[B]=_}this.csrOffsets=C,this.csrIndices=A,this.csrDistances=F,this.adjacencyList=new Array(m)},r.getRoute=function(t,e){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var s=this.getOrCreateIndex(t.geometry.coordinates),r=this.getOrCreateIndex(e.geometry.coordinates);if(s===r)return null;var i=this.coordinates,n=this.adjacencyList,a=this.distanceMeasurement,h=i.length;this.ensureScratch(h);var o=this.gScoreScratch,c=this.cameFromScratch,u=this.visitedScratch,l=this.hScratch;o.fill(Number.POSITIVE_INFINITY,0,h),c.fill(-1,0,h),u.fill(0,0,h),l.fill(-1,0,h);var d=new this.heapConstructor,f=i[r],v=l[s];for(v<0&&(v=a(i[s],f),l[s]=v),d.insert(v,s),o[s]=0;d.size()>0;){var g=d.extractMin();if(0===u[g]){if(g===r)break;if(u[g]=1,this.csrOffsets&&g<this.csrNodeCount)for(var p=this.csrOffsets,y=this.csrIndices,M=this.csrDistances,I=p[g+1],w=p[g];w<I;w++){var S=y[w],x=o[g]+M[w];if(x<o[S]){o[S]=x,c[S]=g;var m=l[S];m<0&&(m=a(i[S],f),l[S]=m),d.insert(x+m,S)}}else{var C=n[g];if(!C||0===C.length)continue;for(var b=0,k=C.length;b<k;b++){var O=C[b],N=O.node,A=o[g]+O.distance;if(A<o[N]){o[N]=A,c[N]=g;var F=l[N];F<0&&(F=a(i[N],f),l[N]=F),d.insert(A+F,N)}}}}}if(c[r]<0)return null;for(var D=[],j=r;j!==s;)D.push(i[j]),j=c[j];return D.push(i[s]),D.reverse(),{type:"Feature",geometry:{type:"LineString",coordinates:D},properties:{}}},r.getOrCreateIndex=function(t){var e=t[0],s=t[1],r=this.coordinateIndexMap.get(e);void 0===r&&(r=new Map,this.coordinateIndexMap.set(e,r));var i=r.get(s);if(void 0===i&&(i=this.coordinates.length,this.coordinates.push(t),r.set(s,i),this.adjacencyList[i]=[],this.csrOffsets)){var n=this.csrNodeCount;if(i===n){var a=new Int32Array(n+2);a.set(this.csrOffsets,0),a[n+1]=a[n],this.csrOffsets=a,this.csrNodeCount=n+1}}return i},r.ensureScratch=function(t){if(!(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch)){var e=0|t;this.gScoreScratch=new Float64Array(e),this.cameFromScratch=new Int32Array(e),this.visitedScratch=new Uint8Array(e),this.hScratch=new Float64Array(e),this.scratchCapacity=e}},t}(),t.createCheapRuler=function(t){var e=1/298.257223563,s=e*(2-e),r=Math.PI/180,i=Math.cos(t*r),n=1/(1-s*(1-i*i)),a=Math.sqrt(n),h=6378.137*r,o=h*a*i,c=h*a*n*(1-s);return function(t,e){for(var s=t[0]-e[0];s<-180;)s+=360;for(;s>180;)s-=360;var r=s*o,i=(t[1]-e[1])*c;return Math.sqrt(r*r+i*i)}},t.haversineDistance=e});
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t||self).terraRoute={})}(this,function(t){var e=function(t,e){var r=function(t){return t*Math.PI/180},s=r(t[1]),i=r(t[0]),n=r(e[1]),a=n-s,h=r(e[0])-i,c=Math.sin(a/2)*Math.sin(a/2)+Math.cos(s)*Math.cos(n)*Math.sin(h/2)*Math.sin(h/2);return 2*Math.atan2(Math.sqrt(c),Math.sqrt(1-c))*6371e3/1e3},r=/*#__PURE__*/function(){function t(){this.keys=[],this.values=[],this.idxs=[],this.length=0,this.insertCounter=0}var e=t.prototype;return e.insert=function(t,e){var r=this.length;this.length=r+1;for(var s=t,i=e,n=this.insertCounter++;r>0;){var a=r-1>>>2,h=this.keys[a],c=this.idxs[a];if(s>h||s===h&&n>c)break;this.keys[r]=h,this.values[r]=this.values[a],this.idxs[r]=c,r=a}this.keys[r]=s,this.values[r]=i,this.idxs[r]=n},e.extractMin=function(){var t=this.length;if(0===t)return null;var e=this.values[0],r=t-1;return this.length=r,r>0&&(this.keys[0]=this.keys[r],this.values[0]=this.values[r],this.idxs[0]=this.idxs[r],this.bubbleDown(0)),e},e.size=function(){return this.length},e.bubbleDown=function(t){for(var e=this.length,r=this.keys,s=this.values,i=this.idxs,n=r[t],a=s[t],h=i[t];;){var c=1+(t<<2);if(c>=e)break;var o=c,u=r[c],f=i[c],l=s[c],d=c+1;if(d<e){var v=r[d],I=i[d];(v<u||v===u&&I<f)&&(o=d,u=v,f=I,l=s[d])}var g=c+2;if(g<e){var S=r[g],y=i[g];(S<u||S===u&&y<f)&&(o=g,u=S,f=y,l=s[g])}var p=c+3;if(p<e){var m=r[p],w=i[p];(m<u||m===u&&w<f)&&(o=p,u=m,f=w,l=s[p])}if(!(u<n||u===n&&f<h))break;r[t]=u,s[t]=l,i[t]=f,t=o}r[t]=n,s[t]=a,i[t]=h},t}();t.TerraRoute=/*#__PURE__*/function(){function t(t){var s,i;this.network=null,this.distanceMeasurement=void 0,this.heapConstructor=void 0,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0,this.gScoreScratch=null,this.cameFromScratch=null,this.visitedScratch=null,this.hScratch=null,this.gScoreRevScratch=null,this.cameFromRevScratch=null,this.visitedRevScratch=null,this.hRevScratch=null,this.scratchCapacity=0,this.distanceMeasurement=null!=(s=null==t?void 0:t.distanceMeasurement)?s:e,this.heapConstructor=null!=(i=null==t?void 0:t.heap)?i:r}var s=t.prototype;return s.buildRouteGraph=function(t){var e=this;this.network=t,this.coordinateIndexMap=new Map,this.coordinates=[],this.adjacencyList=[],this.csrOffsets=null,this.csrIndices=null,this.csrDistances=null,this.csrNodeCount=0;var r=this.coordinateIndexMap,s=this.coordinates,i=this.distanceMeasurement,n=[];this.forEachSegment(t,function(t,i){var a,h,c=e.indexCoordinate(t,r,s),o=e.indexCoordinate(i,r,s);n[c]=(null!=(a=n[c])?a:0)+1,n[o]=(null!=(h=n[o])?h:0)+1});var a=this.coordinates.length;this.csrNodeCount=a;for(var h=new Int32Array(a+1),c=0;c<a;c++){var o,u=null!=(o=n[c])?o:0;h[c+1]=h[c]+u}var f=h[a],l=new Int32Array(f),d=new Float64Array(f),v=h.slice();this.forEachSegment(t,function(t,r){var s=e.coordinateIndexMap.get(t[0]).get(t[1]),n=e.coordinateIndexMap.get(r[0]).get(r[1]),a=i(t,r),h=v[s]++;l[h]=n,d[h]=a,h=v[n]++,l[h]=s,d[h]=a}),this.csrOffsets=h,this.csrIndices=l,this.csrDistances=d,this.adjacencyList=new Array(a)},s.expandRouteGraph=function(t){var e=this;if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");this.network={type:"FeatureCollection",features:[].concat(this.network.features,t.features)};for(var r=this.coordinateIndexMap,s=this.coordinates,i=this.distanceMeasurement,n=this.adjacencyList,a=0;a<n.length;a++)void 0===n[a]&&(n[a]=[]);this.forEachSegment(t,function(t,a){var h=e.indexCoordinate(t,r,s,function(t){n[t]=[]}),c=e.indexCoordinate(a,r,s,function(t){n[t]=[]}),o=i(t,a);n[h].push({node:c,distance:o}),n[c].push({node:h,distance:o})}),this.rebuildCsrFromAdjacency()},s.rebuildCsrFromAdjacency=function(){var t=this.coordinates.length,e=this.adjacencyList,r=new Int32Array(t);if(this.csrOffsets&&this.csrIndices&&this.csrDistances)for(var s=this.csrOffsets,i=Math.min(this.csrNodeCount,t),n=0;n<i;n++)r[n]+=s[n+1]-s[n];for(var a=0;a<t;a++){var h=e[a];h&&h.length&&(r[a]+=h.length)}for(var c=new Int32Array(t+1),o=0;o<t;o++)c[o+1]=c[o]+r[o];var u=c[t],f=new Int32Array(u),l=new Float64Array(u),d=c.slice();if(this.csrOffsets&&this.csrIndices&&this.csrDistances)for(var v=this.csrOffsets,I=this.csrIndices,g=this.csrDistances,S=Math.min(this.csrNodeCount,t),y=0;y<S;y++){for(var p=v[y+1],m=d[y],w=v[y];w<p;w++)f[m]=I[w],l[m]=g[w],m++;d[y]=m}for(var M=0;M<t;M++){var N=e[M];if(N&&0!==N.length){for(var C=d[M],x=0,b=N.length;x<b;x++){var F=N[x];f[C]=F.node,l[C]=F.distance,C++}d[M]=C}}this.csrOffsets=c,this.csrIndices=f,this.csrDistances=l,this.csrNodeCount=t,this.adjacencyList=new Array(t)},s.getRoute=function(t,e){if(null===this.network)throw new Error("Network not built. Please call buildRouteGraph(network) first.");var r=this.getOrCreateIndex(t.geometry.coordinates),s=this.getOrCreateIndex(e.geometry.coordinates);if(r===s)return null;var i=this.coordinates,n=this.adjacencyList,a=i.length;this.ensureScratch(a);var h=this.gScoreScratch,c=this.gScoreRevScratch,o=this.cameFromScratch,u=this.cameFromRevScratch,f=this.visitedScratch,l=this.visitedRevScratch,d=this.hScratch,v=this.hRevScratch;h.fill(Number.POSITIVE_INFINITY,0,a),c.fill(Number.POSITIVE_INFINITY,0,a),o.fill(-1,0,a),u.fill(-1,0,a),f.fill(0,0,a),l.fill(0,0,a),d.fill(-1,0,a),v.fill(-1,0,a);var I=new this.heapConstructor,g=new this.heapConstructor;h[r]=0,c[s]=0,I.insert(0,r),g.insert(0,s);for(var S=Number.POSITIVE_INFINITY,y=-1,p=0,m=0;I.size()>0&&g.size()>0&&!(y>=0&&p+m>=S);)if(I.size()<=g.size()){var w=I.extractMin();if(0!==f[w])continue;if(p=h[w],f[w]=1,0!==l[w]){var M=h[w]+c[w];M<S&&(S=M,y=w)}if(this.csrOffsets&&w<this.csrNodeCount)for(var N=this.csrOffsets,C=this.csrIndices,x=this.csrDistances,b=N[w],F=N[w+1];b<F;b++){var O=C[b],R=h[w]+x[b];if(R<h[O]){h[O]=R,o[O]=w;var k=c[O];if(k!==Number.POSITIVE_INFINITY){var A=R+k;A<S&&(S=A,y=O)}I.insert(R,O)}}else{var T=n[w];if(T&&T.length)for(var E=0,D=T.length;E<D;E++){var j=T[E],P=j.node,L=h[w]+j.distance;if(L<h[P]){h[P]=L,o[P]=w;var V=c[P];if(V!==Number.POSITIVE_INFINITY){var Y=L+V;Y<S&&(S=Y,y=P)}I.insert(L,P)}}}}else{var _=g.extractMin();if(0!==l[_])continue;if(m=c[_],l[_]=1,0!==f[_]){var z=h[_]+c[_];z<S&&(S=z,y=_)}if(this.csrOffsets&&_<this.csrNodeCount)for(var q=this.csrOffsets,G=this.csrIndices,U=this.csrDistances,B=q[_],H=q[_+1];B<H;B++){var J=G[B],K=c[_]+U[B];if(K<c[J]){c[J]=K,u[J]=_;var Q=h[J];if(Q!==Number.POSITIVE_INFINITY){var W=K+Q;W<S&&(S=W,y=J)}g.insert(K,J)}}else{var X=n[_];if(X&&X.length)for(var Z=0,$=X.length;Z<$;Z++){var tt=X[Z],et=tt.node,rt=c[_]+tt.distance;if(rt<c[et]){c[et]=rt,u[et]=_;var st=h[et];if(st!==Number.POSITIVE_INFINITY){var it=rt+st;it<S&&(S=it,y=et)}g.insert(rt,et)}}}}if(y<0)return null;for(var nt=[],at=y;at!==r&&at>=0;)nt.push(i[at]),at=o[at];if(at!==r)return null;for(nt.push(i[r]),nt.reverse(),at=y;at!==s;){if((at=u[at])<0)return null;nt.push(i[at])}return{type:"Feature",geometry:{type:"LineString",coordinates:nt},properties:{}}},s.getOrCreateIndex=function(t){var e=t[0],r=t[1],s=this.coordinateIndexMap.get(e);void 0===s&&(s=new Map,this.coordinateIndexMap.set(e,s));var i=s.get(r);if(void 0===i&&(i=this.coordinates.length,this.coordinates.push(t),s.set(r,i),this.adjacencyList[i]=[],this.csrOffsets)){var n=this.csrNodeCount;if(i===n){var a=new Int32Array(n+2);a.set(this.csrOffsets,0),a[n+1]=a[n],this.csrOffsets=a,this.csrNodeCount=n+1}}return i},s.ensureScratch=function(t){if(!(this.scratchCapacity>=t&&this.gScoreScratch&&this.cameFromScratch&&this.visitedScratch&&this.hScratch&&this.gScoreRevScratch&&this.cameFromRevScratch&&this.visitedRevScratch&&this.hRevScratch)){var e=0|t;this.gScoreScratch=new Float64Array(e),this.cameFromScratch=new Int32Array(e),this.visitedScratch=new Uint8Array(e),this.hScratch=new Float64Array(e),this.gScoreRevScratch=new Float64Array(e),this.cameFromRevScratch=new Int32Array(e),this.visitedRevScratch=new Uint8Array(e),this.hRevScratch=new Float64Array(e),this.scratchCapacity=e}},s.forEachSegment=function(t,e){for(var r=t.features,s=0,i=r.length;s<i;s++)for(var n=r[s].geometry.coordinates,a=0,h=n.length-1;a<h;a++)e(n[a],n[a+1])},s.indexCoordinate=function(t,e,r,s){var i=t[0],n=t[1],a=e.get(i);void 0===a&&(a=new Map,e.set(i,a));var h=a.get(n);return void 0===h&&(h=r.length,r.push(t),a.set(n,h),s&&s(h)),h},t}(),t.createCheapRuler=function(t){var e=1/298.257223563,r=e*(2-e),s=Math.PI/180,i=Math.cos(t*s),n=1/(1-r*(1-i*i)),a=Math.sqrt(n),h=6378.137*s,c=h*a*i,o=h*a*n*(1-r);return function(t,e){for(var r=t[0]-e[0];r<-180;)r+=360;for(;r>180;)r-=360;var s=r*c,i=(t[1]-e[1])*o;return Math.sqrt(s*s+i*i)}},t.haversineDistance=e});
2
2
  //# sourceMappingURL=terra-route.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terra-route.umd.js","sources":["../src/distance/haversine.ts","../src/heap/four-ary-heap.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n\n // Convert distance from meters to kilometers\n return distance / 1000;\n}\n","import { Heap } from \"./heap\";\n\ninterface Node {\n key: number;\n value: number;\n index: number; // insertion order for stable tie-breaking\n}\n\n/**\n * A 4-ary min-heap with stable tie-breaking on insertion order.\n * Parent(i) = floor((i - 1) / 4)\n * Children(i) = 4*i + 1 .. 4*i + 4\n */\nexport class FourAryHeap implements Heap {\n // Parallel arrays for fewer object allocations and faster property access\n private keys: number[] = [];\n private values: number[] = [];\n private idxs: number[] = [];\n private length = 0; // number of valid elements\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n // Bubble-up using local variables (avoid temporary node object)\n let i = this.length;\n this.length = i + 1;\n\n let ck = key;\n let cv = value;\n let ci = this.insertCounter++;\n\n while (i > 0) {\n const p = (i - 1) >>> 2; // divide by 4\n const pk = this.keys[p];\n const pi = this.idxs[p];\n if (ck > pk || (ck === pk && ci > pi)) break;\n\n // move parent down\n this.keys[i] = pk;\n this.values[i] = this.values[p];\n this.idxs[i] = pi;\n i = p;\n }\n\n // place the new node\n this.keys[i] = ck;\n this.values[i] = cv;\n this.idxs[i] = ci;\n }\n\n extractMin(): number | null {\n const n = this.length;\n if (n === 0) return null;\n\n const minValue = this.values[0];\n const last = n - 1;\n this.length = last;\n\n if (last > 0) {\n // Move last element to root then bubble down\n this.keys[0] = this.keys[last];\n this.values[0] = this.values[last];\n this.idxs[0] = this.idxs[last];\n this.bubbleDown(0);\n }\n\n return minValue;\n }\n\n size(): number {\n return this.length;\n }\n\n private bubbleDown(i: number): void {\n const n = this.length;\n const k = this.keys;\n const v = this.values;\n const idx = this.idxs;\n\n const nodeK = k[i];\n const nodeV = v[i];\n const nodeI = idx[i];\n\n while (true) {\n const c1 = (i << 2) + 1; // 4*i + 1\n if (c1 >= n) break; // no children\n\n // find smallest among up to 4 children\n let smallest = c1;\n let sK = k[c1];\n let sI = idx[c1];\n let sV = v[c1];\n\n const c2 = c1 + 1;\n if (c2 < n) {\n const k2 = k[c2];\n const i2 = idx[c2];\n if (k2 < sK || (k2 === sK && i2 < sI)) {\n smallest = c2;\n sK = k2;\n sI = i2;\n sV = v[c2];\n }\n }\n\n const c3 = c1 + 2;\n if (c3 < n) {\n const k3 = k[c3];\n const i3 = idx[c3];\n if (k3 < sK || (k3 === sK && i3 < sI)) {\n smallest = c3;\n sK = k3;\n sI = i3;\n sV = v[c3];\n }\n }\n\n const c4 = c1 + 3;\n if (c4 < n) {\n const k4 = k[c4];\n const i4 = idx[c4];\n if (k4 < sK || (k4 === sK && i4 < sI)) {\n smallest = c4;\n sK = k4;\n sI = i4;\n sV = v[c4];\n }\n }\n\n if (sK < nodeK || (sK === nodeK && sI < nodeI)) {\n k[i] = sK;\n v[i] = sV;\n idx[i] = sI;\n i = smallest;\n } else {\n break;\n }\n }\n\n k[i] = nodeK;\n v[i] = nodeV;\n idx[i] = nodeI;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\"; // Import GeoJSON types\nimport { haversineDistance } from \"./distance/haversine\"; // Great-circle distance function (default heuristic/edge weight)\nimport { createCheapRuler } from \"./distance/cheap-ruler\"; // Factory for faster planar distance (exported for consumers)\nimport { HeapConstructor } from \"./heap/heap\"; // Heap interface so users can plug custom heaps\nimport { FourAryHeap } from \"./heap/four-ary-heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null; // The last network used to build the graph\n private distanceMeasurement: (a: Position, b: Position) => number; // Distance function used for edges and heuristic\n private heapConstructor: HeapConstructor; // Heap class used by A*\n\n // Map from longitude → (map from latitude → index) to deduplicate coordinates and get node indices quickly\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map(); // Nested map for exact coord lookup\n private coordinates: Position[] = []; // Array of all unique coordinates by index\n // Sparse adjacency list used during build and for any nodes added dynamically later\n private adjacencyList: Array<Array<{ node: number, distance: number }>> = []; // Per-node neighbor arrays used only pre-CSR or for dynamic nodes\n\n // Compressed Sparse Row adjacency representation for fast neighbor iteration in getRoute\n private csrOffsets: Int32Array | null = null; // Row pointer: length = nodeCount + 1, offsets into indices/distances\n private csrIndices: Int32Array | null = null; // Column indices: neighbor node IDs, length = totalEdges\n private csrDistances: Float64Array | null = null; // Edge weights aligned to csrIndices, length = totalEdges\n private csrNodeCount = 0; // Number of nodes captured in the CSR arrays\n\n // Reusable typed scratch buffers for A*\n private gScoreScratch: Float64Array | null = null; // gScore per node (cost from start)\n private cameFromScratch: Int32Array | null = null; // Predecessor per node for path reconstruction\n private visitedScratch: Uint8Array | null = null; // Visited set to avoid reprocessing\n private hScratch: Float64Array | null = null; // Per-node heuristic cache for the current query (lazy compute)\n private scratchCapacity = 0; // Current capacity of scratch arrays\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number; // Optional distance function override\n heap?: HeapConstructor; // Optional heap implementation override\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance; // Default to haversine\n this.heapConstructor = options?.heap ?? FourAryHeap; // Default to MinHeap\n }\n\n /**\n * Builds a graph (CSR) from a LineString FeatureCollection.\n * Two-pass build: pass 1 assigns node indices and counts degrees; pass 2 fills CSR arrays.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network; // Keep a reference to the network\n\n // Reset internal structures for a fresh build\n this.coordinateIndexMap = new Map(); // Clear coordinate index map\n this.coordinates = []; // Clear coordinates array\n this.adjacencyList = []; // Will not be populated during build; reserved for dynamic nodes post-build\n // Reset CSR structures (will rebuild below)\n this.csrOffsets = null;\n this.csrIndices = null;\n this.csrDistances = null;\n this.csrNodeCount = 0;\n\n // Hoist to locals for speed (avoid repeated property lookups in hot loops)\n const coordIndexMapLocal = this.coordinateIndexMap; // Local alias for coord map\n const coordsLocal = this.coordinates; // Local alias for coordinates array\n const measureDistance = this.distanceMeasurement; // Local alias for distance function\n\n const features = network.features; // All LineString features\n\n // Pass 1: assign indices and count degrees per node\n const degree: number[] = []; // Dynamic degree array; grows as nodes are discovered\n for (let f = 0, fLen = features.length; f < fLen; f++) { // Iterate features\n const feature = features[f]; // Current feature\n const lineCoords = feature.geometry.coordinates; // Coordinates for this LineString\n\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) { // Iterate segment pairs\n const a = lineCoords[i]; // Segment start coord\n const b = lineCoords[i + 1]; // Segment end coord\n\n const lngA = a[0], latA = a[1]; // Keys for A\n const lngB = b[0], latB = b[1]; // Keys for B\n\n // Index A\n let latMapA = coordIndexMapLocal.get(lngA);\n if (latMapA === undefined) {\n latMapA = new Map<number, number>();\n coordIndexMapLocal.set(lngA, latMapA);\n }\n let indexA = latMapA.get(latA);\n if (indexA === undefined) {\n indexA = coordsLocal.length;\n coordsLocal.push(a);\n latMapA.set(latA, indexA);\n }\n\n // Index B\n let latMapB = coordIndexMapLocal.get(lngB);\n if (latMapB === undefined) {\n latMapB = new Map<number, number>();\n coordIndexMapLocal.set(lngB, latMapB);\n }\n let indexB = latMapB.get(latB);\n if (indexB === undefined) {\n indexB = coordsLocal.length;\n coordsLocal.push(b);\n latMapB.set(latB, indexB);\n }\n\n // Count degree for both directions\n degree[indexA] = (degree[indexA] ?? 0) + 1;\n degree[indexB] = (degree[indexB] ?? 0) + 1;\n }\n }\n\n // Build CSR arrays from degree counts\n const nodeCount = this.coordinates.length; // Total nodes discovered\n this.csrNodeCount = nodeCount; // CSR covers all built nodes\n const offsets = new Int32Array(nodeCount + 1); // Row pointer array\n for (let i = 0; i < nodeCount; i++) {\n const deg = degree[i] ?? 0; // Degree of node i\n offsets[i + 1] = offsets[i] + deg; // Prefix sum\n }\n const totalEdges = offsets[nodeCount]; // Total adjacency entries\n const indices = new Int32Array(totalEdges); // Neighbor indices array\n const distances = new Float64Array(totalEdges); // Distances array aligned to indices\n\n // Pass 2: fill CSR arrays using a write cursor per node\n const cursor = offsets.slice(); // Current write positions per node\n for (let f = 0, fLen = features.length; f < fLen; f++) {\n const feature = features[f];\n const lineCoords = feature.geometry.coordinates;\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) {\n const a = lineCoords[i];\n const b = lineCoords[i + 1];\n\n const lngA = a[0], latA = a[1];\n const lngB = b[0], latB = b[1];\n\n // Read back indices (guaranteed to exist from pass 1)\n const indexA = this.coordinateIndexMap.get(lngA)!.get(latA)!;\n const indexB = this.coordinateIndexMap.get(lngB)!.get(latB)!;\n\n const segmentDistance = measureDistance(a, b); // Edge weight once\n\n // Write A → B\n let pos = cursor[indexA]++;\n indices[pos] = indexB;\n distances[pos] = segmentDistance;\n // Write B → A\n pos = cursor[indexB]++;\n indices[pos] = indexA;\n distances[pos] = segmentDistance;\n }\n }\n\n // Commit CSR to instance\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n\n // Prepare sparse shell only for dynamically added nodes later (no prefilled neighbor arrays)\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>, // Start point feature\n end: Feature<Point> // End point feature\n ): Feature<LineString> | null {\n if (this.network === null) { // Guard: graph must be built first\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Ensure start/end exist in index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates); // Get or insert start node index\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates); // Get or insert end node index\n\n // Trivial case: same node\n if (startIndex === endIndex) {\n return null; // No path needed\n }\n\n // Local aliases\n const coords = this.coordinates; // Alias to coordinates array\n const adj = this.adjacencyList; // Alias to sparse adjacency list (for dynamic nodes)\n const measureDistance = this.distanceMeasurement; // Alias to distance function\n\n // Ensure and init scratch buffers\n const nodeCount = coords.length; // Current number of nodes (may be >= csrNodeCount if new nodes added)\n this.ensureScratch(nodeCount); // Allocate scratch arrays if needed\n\n // Non-null after ensure\n const gScore = this.gScoreScratch!; // gScore pointer\n const cameFrom = this.cameFromScratch!; // cameFrom pointer\n const visited = this.visitedScratch!; // visited pointer\n const hCache = this.hScratch!; // heuristic cache pointer\n\n // Reset only the used range for speed\n gScore.fill(Number.POSITIVE_INFINITY, 0, nodeCount); // Init gScore to +∞\n cameFrom.fill(-1, 0, nodeCount); // Init predecessors to -1 (unknown)\n visited.fill(0, 0, nodeCount); // Init visited flags to 0\n hCache.fill(-1, 0, nodeCount); // Init heuristic cache with sentinel (-1 means unknown)\n\n // Create min-heap (priority queue)\n const openSet = new this.heapConstructor();\n\n // Precompute heuristic for start to prime the queue cost\n const endCoord = coords[endIndex]; // Cache end coordinate for heuristic\n let hStart = hCache[startIndex];\n\n if (hStart < 0) {\n hStart = measureDistance(coords[startIndex], endCoord);\n hCache[startIndex] = hStart;\n }\n\n openSet.insert(hStart, startIndex); // Insert start with f = g(0) + h(start)\n gScore[startIndex] = 0; // g(start) = 0\n\n while (openSet.size() > 0) { // Main A* loop until queue empty\n const current = openSet.extractMin()!; // Pop node with smallest f\n if (visited[current] !== 0) { // Skip if already finalized\n continue;\n }\n if (current === endIndex) { // Early exit if reached goal\n break;\n }\n visited[current] = 1; // Mark as visited\n\n // Prefer CSR neighbors if available for this node, fall back to sparse list for dynamically added nodes\n\n if (this.csrOffsets && current < this.csrNodeCount) { // Use CSR fast path\n const csrOffsets = this.csrOffsets!; // Local CSR offsets (non-null here)\n const csrIndices = this.csrIndices!; // Local CSR neighbors\n const csrDistances = this.csrDistances!; // Local CSR weights\n const startOff = csrOffsets[current]; // Row start for current\n const endOff = csrOffsets[current + 1]; // Row end for current\n\n for (let i = startOff; i < endOff; i++) { // Iterate neighbors in CSR slice\n const nbNode = csrIndices[i]; // Neighbor node id\n const tentativeG = gScore[current] + csrDistances[i]; // g' = g(current) + w(current, nb)\n if (tentativeG < gScore[nbNode]) { // Relaxation check\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n // A* priority = g + h (cache h per node for this query)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Push/update neighbor into open set\n }\n }\n }\n // Fallback: use sparse adjacency (only for nodes added after CSR build)\n else {\n\n const neighbors = adj[current]; // Neighbor list for current\n if (!neighbors || neighbors.length === 0) continue; // No neighbors\n for (let i = 0, n = neighbors.length; i < n; i++) { // Iterate neighbors\n const nb = neighbors[i]; // Neighbor entry\n const nbNode = nb.node; // Neighbor id\n\n const tentativeG = gScore[current] + nb.distance; // g' via current\n if (tentativeG < gScore[nbNode]) { // Relax if better\n gScore[nbNode] = tentativeG; // Update best g\n cameFrom[nbNode] = current; // Track predecessor\n\n // A* priority = g + h (cached)\n let hVal = hCache[nbNode];\n if (hVal < 0) { hVal = measureDistance(coords[nbNode], endCoord); hCache[nbNode] = hVal; }\n openSet.insert(tentativeG + hVal, nbNode); // Enqueue neighbor\n }\n }\n }\n }\n\n // If goal was never reached/relaxed, return null\n if (cameFrom[endIndex] < 0) {\n return null;\n }\n\n // Reconstruct path (push + reverse to avoid O(n^2) unshift)\n const path: Position[] = [];\n let cur = endIndex;\n while (cur !== startIndex) {\n path.push(coords[cur]);\n cur = cameFrom[cur];\n }\n // Include start coordinate\n path.push(coords[startIndex]);\n\n // Reverse to get start→end order\n path.reverse();\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number { // Ensure a coordinate has a node index, creating if absent\n const lng = coord[0]; // Extract longitude\n const lat = coord[1]; // Extract latitude\n\n let latMap = this.coordinateIndexMap.get(lng); // Get lat→index map for this longitude\n if (latMap === undefined) { // Create if missing\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n\n let index = latMap.get(lat); // Lookup index by latitude\n if (index === undefined) { // If not found, append new node\n\n index = this.coordinates.length; // New index at end\n this.coordinates.push(coord); // Store coordinate\n latMap.set(lat, index); // Record mapping\n\n // Ensure sparse adjacency slot for dynamically added nodes\n this.adjacencyList[index] = []; // Init empty neighbor array\n\n // Extend CSR offsets to keep indices consistent (no neighbors for new node)\n if (this.csrOffsets) { // Only adjust if CSR already built\n\n // Only need to expand offsets by one; indices/distances remain unchanged\n const oldCount = this.csrNodeCount; // Nodes currently covered by CSR\n\n // Appending exactly one new node at the end\n if (index === oldCount) {\n const newOffsets = new Int32Array(oldCount + 2); // Allocate offsets for +1 node\n newOffsets.set(this.csrOffsets, 0); // Copy previous offsets\n\n // Last offset repeats to indicate zero neighbors\n newOffsets[oldCount + 1] = newOffsets[oldCount]; // Replicate last pointer\n this.csrOffsets = newOffsets; // Swap in new offsets\n this.csrNodeCount = oldCount + 1; // Increment CSR node count\n }\n }\n }\n\n return index;\n }\n\n // Ensure scratch arrays are allocated with at least `size` capacity.\n private ensureScratch(size: number): void {\n const ifAlreadyBigEnough = this.scratchCapacity >= size\n && this.gScoreScratch\n && this.cameFromScratch\n && this.visitedScratch\n && this.hScratch;\n\n if (ifAlreadyBigEnough) {\n return; // Nothing to do\n }\n const capacity = size | 0; // Ensure integer\n this.gScoreScratch = new Float64Array(capacity);\n this.cameFromScratch = new Int32Array(capacity);\n this.visitedScratch = new Uint8Array(capacity);\n this.hScratch = new Float64Array(capacity);\n this.scratchCapacity = capacity;\n }\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance } ","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}"],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","FourAryHeap","keys","this","values","idxs","length","insertCounter","_proto","prototype","insert","key","value","i","ck","cv","ci","p","pk","pi","extractMin","n","minValue","last","bubbleDown","size","k","v","idx","nodeK","nodeV","nodeI","c1","smallest","sK","sI","sV","c2","k2","i2","c3","k3","i3","c4","k4","i4","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","csrOffsets","csrIndices","csrDistances","csrNodeCount","gScoreScratch","cameFromScratch","visitedScratch","hScratch","scratchCapacity","heap","buildRouteGraph","coordIndexMapLocal","coordsLocal","measureDistance","features","degree","f","fLen","lineCoords","geometry","len","_degree$indexA","_degree$indexB","b","lngA","latA","lngB","latB","latMapA","get","undefined","set","indexA","push","latMapB","indexB","nodeCount","offsets","Int32Array","_degree$_i","deg","totalEdges","indices","distances","Float64Array","cursor","slice","segmentDistance","pos","Array","getRoute","start","end","Error","startIndex","getOrCreateIndex","endIndex","coords","adj","ensureScratch","gScore","cameFrom","visited","hCache","fill","Number","POSITIVE_INFINITY","openSet","endCoord","hStart","current","endOff","nbNode","tentativeG","hVal","neighbors","nb","node","distance","path","cur","reverse","type","properties","coord","lng","lat","latMap","index","oldCount","newOffsets","capacity","Uint8Array","FE","E2","RAD","cosLat","w2","w","m","kx","ky","deltaLng","dx","dy"],"mappings":"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"}
1
+ {"version":3,"file":"terra-route.umd.js","sources":["../src/distance/haversine.ts","../src/heap/four-ary-heap.ts","../src/terra-route.ts","../src/distance/cheap-ruler.ts"],"sourcesContent":["import { Position } from \"geojson\";\n\n/** Distance measured in kilometers */\nexport const haversineDistance = (pointOne: Position, pointTwo: Position): number => {\n const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;\n\n const phiOne = toRadians(pointOne[1]);\n const lambdaOne = toRadians(pointOne[0]);\n const phiTwo = toRadians(pointTwo[1]);\n const lambdaTwo = toRadians(pointTwo[0]);\n const deltaPhi = phiTwo - phiOne;\n const deltalambda = lambdaTwo - lambdaOne;\n\n const a =\n Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n Math.cos(phiOne) *\n Math.cos(phiTwo) *\n Math.sin(deltalambda / 2) *\n Math.sin(deltalambda / 2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n const radius = 6371e3;\n const distance = radius * c;\n\n\n // Convert distance from meters to kilometers\n return distance / 1000;\n}\n","import { Heap } from \"./heap\";\n\ninterface Node {\n key: number;\n value: number;\n index: number; // insertion order for stable tie-breaking\n}\n\n/**\n * A 4-ary min-heap with stable tie-breaking on insertion order.\n * Parent(i) = floor((i - 1) / 4)\n * Children(i) = 4*i + 1 .. 4*i + 4\n */\nexport class FourAryHeap implements Heap {\n // Parallel arrays for fewer object allocations and faster property access\n private keys: number[] = [];\n private values: number[] = [];\n private idxs: number[] = [];\n private length = 0; // number of valid elements\n private insertCounter = 0;\n\n insert(key: number, value: number): void {\n // Bubble-up using local variables (avoid temporary node object)\n let i = this.length;\n this.length = i + 1;\n\n let ck = key;\n let cv = value;\n let ci = this.insertCounter++;\n\n while (i > 0) {\n const p = (i - 1) >>> 2; // divide by 4\n const pk = this.keys[p];\n const pi = this.idxs[p];\n if (ck > pk || (ck === pk && ci > pi)) break;\n\n // move parent down\n this.keys[i] = pk;\n this.values[i] = this.values[p];\n this.idxs[i] = pi;\n i = p;\n }\n\n // place the new node\n this.keys[i] = ck;\n this.values[i] = cv;\n this.idxs[i] = ci;\n }\n\n extractMin(): number | null {\n const n = this.length;\n if (n === 0) return null;\n\n const minValue = this.values[0];\n const last = n - 1;\n this.length = last;\n\n if (last > 0) {\n // Move last element to root then bubble down\n this.keys[0] = this.keys[last];\n this.values[0] = this.values[last];\n this.idxs[0] = this.idxs[last];\n this.bubbleDown(0);\n }\n\n return minValue;\n }\n\n size(): number {\n return this.length;\n }\n\n private bubbleDown(i: number): void {\n const n = this.length;\n const k = this.keys;\n const v = this.values;\n const idx = this.idxs;\n\n const nodeK = k[i];\n const nodeV = v[i];\n const nodeI = idx[i];\n\n while (true) {\n const c1 = (i << 2) + 1; // 4*i + 1\n if (c1 >= n) break; // no children\n\n // find smallest among up to 4 children\n let smallest = c1;\n let sK = k[c1];\n let sI = idx[c1];\n let sV = v[c1];\n\n const c2 = c1 + 1;\n if (c2 < n) {\n const k2 = k[c2];\n const i2 = idx[c2];\n if (k2 < sK || (k2 === sK && i2 < sI)) {\n smallest = c2;\n sK = k2;\n sI = i2;\n sV = v[c2];\n }\n }\n\n const c3 = c1 + 2;\n if (c3 < n) {\n const k3 = k[c3];\n const i3 = idx[c3];\n if (k3 < sK || (k3 === sK && i3 < sI)) {\n smallest = c3;\n sK = k3;\n sI = i3;\n sV = v[c3];\n }\n }\n\n const c4 = c1 + 3;\n if (c4 < n) {\n const k4 = k[c4];\n const i4 = idx[c4];\n if (k4 < sK || (k4 === sK && i4 < sI)) {\n smallest = c4;\n sK = k4;\n sI = i4;\n sV = v[c4];\n }\n }\n\n if (sK < nodeK || (sK === nodeK && sI < nodeI)) {\n k[i] = sK;\n v[i] = sV;\n idx[i] = sI;\n i = smallest;\n } else {\n break;\n }\n }\n\n k[i] = nodeK;\n v[i] = nodeV;\n idx[i] = nodeI;\n }\n}\n","import { FeatureCollection, LineString, Point, Feature, Position } from \"geojson\"; // Import GeoJSON types\nimport { haversineDistance } from \"./distance/haversine\"; // Great-circle distance function (default heuristic/edge weight)\nimport { createCheapRuler } from \"./distance/cheap-ruler\"; // Factory for faster planar distance (exported for consumers)\nimport { HeapConstructor } from \"./heap/heap\"; // Heap interface so users can plug custom heaps\nimport { FourAryHeap } from \"./heap/four-ary-heap\";\n\ninterface Router {\n buildRouteGraph(network: FeatureCollection<LineString>): void;\n expandRouteGraph(network: FeatureCollection<LineString>): void;\n getRoute(start: Feature<Point>, end: Feature<Point>): Feature<LineString> | null;\n}\n\nclass TerraRoute implements Router {\n private network: FeatureCollection<LineString> | null = null; // The last network used to build the graph\n private distanceMeasurement: (a: Position, b: Position) => number; // Distance function used for edges and heuristic\n private heapConstructor: HeapConstructor; // Heap class used by A*\n\n // Map from longitude → (map from latitude → index) to deduplicate coordinates and get node indices quickly\n private coordinateIndexMap: Map<number, Map<number, number>> = new Map(); // Nested map for exact coord lookup\n private coordinates: Position[] = []; // Array of all unique coordinates by index\n // Sparse adjacency list used during build and for any nodes added dynamically later\n private adjacencyList: Array<Array<{ node: number, distance: number }>> = []; // Per-node neighbor arrays used only pre-CSR or for dynamic nodes\n\n // Compressed Sparse Row adjacency representation for fast neighbor iteration in getRoute\n private csrOffsets: Int32Array | null = null; // Row pointer: length = nodeCount + 1, offsets into indices/distances\n private csrIndices: Int32Array | null = null; // Column indices: neighbor node IDs, length = totalEdges\n private csrDistances: Float64Array | null = null; // Edge weights aligned to csrIndices, length = totalEdges\n private csrNodeCount = 0; // Number of nodes captured in the CSR arrays\n\n // Reusable typed scratch buffers for A*\n private gScoreScratch: Float64Array | null = null; // gScore per node (cost from start)\n private cameFromScratch: Int32Array | null = null; // Predecessor per node for path reconstruction\n private visitedScratch: Uint8Array | null = null; // Visited set to avoid reprocessing\n private hScratch: Float64Array | null = null; // Per-node heuristic cache for the current query (lazy compute)\n // Reverse-direction scratch for bidirectional search\n private gScoreRevScratch: Float64Array | null = null; // gScore per node from the end\n private cameFromRevScratch: Int32Array | null = null; // Successor per node (next step toward the end)\n private visitedRevScratch: Uint8Array | null = null; // Visited set for reverse search\n private hRevScratch: Float64Array | null = null; // Heuristic cache for reverse direction per query\n private scratchCapacity = 0; // Current capacity of scratch arrays\n\n constructor(options?: {\n distanceMeasurement?: (a: Position, b: Position) => number; // Optional distance function override\n heap?: HeapConstructor; // Optional heap implementation override\n }) {\n this.distanceMeasurement = options?.distanceMeasurement ?? haversineDistance; // Default to haversine\n this.heapConstructor = options?.heap ?? FourAryHeap; // Default to MinHeap\n }\n\n /**\n * Builds a graph (CSR) from a LineString FeatureCollection.\n * Two-pass build: pass 1 assigns node indices and counts degrees; pass 2 fills CSR arrays.\n */\n public buildRouteGraph(network: FeatureCollection<LineString>): void {\n this.network = network; // Keep a reference to the network\n\n // Reset internal structures for a fresh build\n this.coordinateIndexMap = new Map(); // Clear coordinate index map\n this.coordinates = []; // Clear coordinates array\n this.adjacencyList = []; // Will not be populated during build; reserved for dynamic nodes post-build\n // Reset CSR structures (will rebuild below)\n this.csrOffsets = null;\n this.csrIndices = null;\n this.csrDistances = null;\n this.csrNodeCount = 0;\n\n // Hoist to locals for speed (avoid repeated property lookups in hot loops)\n const coordIndexMapLocal = this.coordinateIndexMap; // Local alias for coord map\n const coordsLocal = this.coordinates; // Local alias for coordinates array\n const measureDistance = this.distanceMeasurement; // Local alias for distance function\n\n // Pass 1: assign indices and count degrees per node\n const degree: number[] = []; // Dynamic degree array; grows as nodes are discovered\n this.forEachSegment(network, (a, b) => {\n const indexA = this.indexCoordinate(a, coordIndexMapLocal, coordsLocal);\n const indexB = this.indexCoordinate(b, coordIndexMapLocal, coordsLocal);\n\n degree[indexA] = (degree[indexA] ?? 0) + 1;\n degree[indexB] = (degree[indexB] ?? 0) + 1;\n });\n\n // Build CSR arrays from degree counts\n const nodeCount = this.coordinates.length; // Total nodes discovered\n this.csrNodeCount = nodeCount; // CSR covers all built nodes\n const offsets = new Int32Array(nodeCount + 1); // Row pointer array\n for (let i = 0; i < nodeCount; i++) {\n const deg = degree[i] ?? 0; // Degree of node i\n offsets[i + 1] = offsets[i] + deg; // Prefix sum\n }\n const totalEdges = offsets[nodeCount]; // Total adjacency entries\n const indices = new Int32Array(totalEdges); // Neighbor indices array\n const distances = new Float64Array(totalEdges); // Distances array aligned to indices\n\n // Pass 2: fill CSR arrays using a write cursor per node\n const cursor = offsets.slice(); // Current write positions per node\n this.forEachSegment(network, (a, b) => {\n // Read back indices (guaranteed to exist from pass 1)\n const indexA = this.coordinateIndexMap.get(a[0])!.get(a[1])!;\n const indexB = this.coordinateIndexMap.get(b[0])!.get(b[1])!;\n\n const segmentDistance = measureDistance(a, b); // Edge weight once\n\n let pos = cursor[indexA]++;\n indices[pos] = indexB;\n distances[pos] = segmentDistance;\n pos = cursor[indexB]++;\n indices[pos] = indexA;\n distances[pos] = segmentDistance;\n });\n\n // Commit CSR to instance\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n\n // Prepare sparse shell only for dynamically added nodes later (no prefilled neighbor arrays)\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Expands (merges) the existing graph with an additional LineString FeatureCollection.\n */\n public expandRouteGraph(network: FeatureCollection<LineString>): void {\n if (this.network === null) {\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Merge the feature arrays for reference/debugging. We avoid copying properties deeply.\n this.network = {\n type: \"FeatureCollection\",\n features: [...this.network.features, ...network.features],\n };\n\n const coordIndexMapLocal = this.coordinateIndexMap;\n const coordsLocal = this.coordinates;\n const measureDistance = this.distanceMeasurement;\n const adj = this.adjacencyList;\n\n // Ensure we have adjacency arrays for any existing CSR-only nodes.\n // (During buildRouteGraph, adjacencyList is sized but entries are undefined.)\n for (let i = 0; i < adj.length; i++) {\n if (adj[i] === undefined) adj[i] = [];\n }\n\n // Add new edges into the adjacency list (sparse), then rebuild CSR from adjacency.\n this.forEachSegment(network, (a, b) => {\n const indexA = this.indexCoordinate(a, coordIndexMapLocal, coordsLocal, (idx) => { adj[idx] = []; });\n const indexB = this.indexCoordinate(b, coordIndexMapLocal, coordsLocal, (idx) => { adj[idx] = []; });\n\n const segmentDistance = measureDistance(a, b);\n\n adj[indexA].push({ node: indexB, distance: segmentDistance });\n adj[indexB].push({ node: indexA, distance: segmentDistance });\n });\n\n this.rebuildCsrFromAdjacency();\n }\n\n /**\n * Rebuild CSR arrays for the full node set, using:\n * - Existing CSR edges (from the last build/expand)\n * - Any additional edges stored in `adjacencyList`\n */\n private rebuildCsrFromAdjacency(): void {\n const nodeCount = this.coordinates.length;\n const adj = this.adjacencyList;\n\n // Compute degree using CSR degree + adjacency degree\n const degree = new Int32Array(nodeCount);\n\n if (this.csrOffsets && this.csrIndices && this.csrDistances) {\n const csrOffsets = this.csrOffsets;\n const covered = Math.min(this.csrNodeCount, nodeCount);\n for (let i = 0; i < covered; i++) {\n degree[i] += (csrOffsets[i + 1] - csrOffsets[i]);\n }\n }\n\n for (let i = 0; i < nodeCount; i++) {\n const neighbors = adj[i];\n if (neighbors && neighbors.length) degree[i] += neighbors.length;\n }\n\n const offsets = new Int32Array(nodeCount + 1);\n for (let i = 0; i < nodeCount; i++) {\n offsets[i + 1] = offsets[i] + degree[i];\n }\n const totalEdges = offsets[nodeCount];\n const indices = new Int32Array(totalEdges);\n const distances = new Float64Array(totalEdges);\n const cursor = offsets.slice();\n\n // Copy existing CSR edges first\n if (this.csrOffsets && this.csrIndices && this.csrDistances) {\n const csrOffsets = this.csrOffsets;\n const csrIndices = this.csrIndices;\n const csrDistances = this.csrDistances;\n const covered = Math.min(this.csrNodeCount, nodeCount);\n for (let n = 0; n < covered; n++) {\n const startOff = csrOffsets[n];\n const endOff = csrOffsets[n + 1];\n let pos = cursor[n];\n for (let i = startOff; i < endOff; i++) {\n indices[pos] = csrIndices[i];\n distances[pos] = csrDistances[i];\n pos++;\n }\n cursor[n] = pos;\n }\n }\n\n // Append adjacency edges\n for (let n = 0; n < nodeCount; n++) {\n const neighbors = adj[n];\n if (!neighbors || neighbors.length === 0) continue;\n let pos = cursor[n];\n for (let i = 0, len = neighbors.length; i < len; i++) {\n const nb = neighbors[i];\n indices[pos] = nb.node;\n distances[pos] = nb.distance;\n pos++;\n }\n cursor[n] = pos;\n }\n\n // Commit and reset adjacency (we've absorbed edges into CSR)\n this.csrOffsets = offsets;\n this.csrIndices = indices;\n this.csrDistances = distances;\n this.csrNodeCount = nodeCount;\n\n // Keep adjacency list for *future* dynamic additions, but clear existing edges to avoid duplication.\n this.adjacencyList = new Array(nodeCount);\n }\n\n /**\n * Computes the shortest route between two points in the network using the A* algorithm.\n * \n * @param start - A GeoJSON Point Feature representing the start location.\n * @param end - A GeoJSON Point Feature representing the end location.\n * @returns A GeoJSON LineString Feature representing the shortest path, or null if no path is found.\n * \n * @throws Error if the network has not been built yet with buildRouteGraph(network).\n */\n public getRoute(\n start: Feature<Point>, // Start point feature\n end: Feature<Point> // End point feature\n ): Feature<LineString> | null {\n if (this.network === null) { // Guard: graph must be built first\n throw new Error(\"Network not built. Please call buildRouteGraph(network) first.\");\n }\n\n // Ensure start/end exist in index maps\n const startIndex = this.getOrCreateIndex(start.geometry.coordinates); // Get or insert start node index\n const endIndex = this.getOrCreateIndex(end.geometry.coordinates); // Get or insert end node index\n\n // Trivial case: same node\n if (startIndex === endIndex) {\n return null; // No path needed\n }\n\n // Local aliases\n const coords = this.coordinates; // Alias to coordinates array\n const adj = this.adjacencyList; // Alias to sparse adjacency list (for dynamic nodes)\n\n // Ensure and init scratch buffers\n const nodeCount = coords.length; // Current number of nodes (may be >= csrNodeCount if new nodes added)\n this.ensureScratch(nodeCount); // Allocate scratch arrays if needed\n\n // Non-null after ensure\n const gF = this.gScoreScratch!; // forward gScore (from start)\n const gR = this.gScoreRevScratch!; // reverse gScore (from end)\n const prevF = this.cameFromScratch!; // predecessor in forward search\n const nextR = this.cameFromRevScratch!; // successor in reverse search (toward end)\n const visF = this.visitedScratch!;\n const visR = this.visitedRevScratch!;\n const hF = this.hScratch!;\n const hR = this.hRevScratch!;\n\n gF.fill(Number.POSITIVE_INFINITY, 0, nodeCount);\n gR.fill(Number.POSITIVE_INFINITY, 0, nodeCount);\n prevF.fill(-1, 0, nodeCount);\n nextR.fill(-1, 0, nodeCount);\n visF.fill(0, 0, nodeCount);\n visR.fill(0, 0, nodeCount);\n hF.fill(-1, 0, nodeCount);\n hR.fill(-1, 0, nodeCount);\n\n const openF = new this.heapConstructor();\n const openR = new this.heapConstructor();\n\n const startCoord = coords[startIndex];\n const endCoord = coords[endIndex];\n\n gF[startIndex] = 0;\n gR[endIndex] = 0;\n\n // Bidirectional Dijkstra (A* with zero heuristic). This keeps correctness simple and matches the\n // reference pathfinder while still saving work by meeting in the middle.\n openF.insert(0, startIndex);\n openR.insert(0, endIndex);\n\n // Best meeting point found so far\n let bestPathCost = Number.POSITIVE_INFINITY;\n let meetingNode = -1;\n\n // Main bidirectional loop: expand alternately.\n // Without a heap peek, a safe and effective stopping rule is based on the last extracted keys:\n // once min_g_forward + min_g_reverse >= bestPathCost, no shorter path can still be found.\n let lastExtractedGForward = 0;\n let lastExtractedGReverse = 0;\n while (openF.size() > 0 && openR.size() > 0) {\n if (meetingNode >= 0 && (lastExtractedGForward + lastExtractedGReverse) >= bestPathCost) {\n break;\n }\n\n // Expand one step from each side, prioritizing the smaller frontier.\n const expandForward = openF.size() <= openR.size();\n\n if (expandForward) {\n const current = openF.extractMin()!;\n if (visF[current] !== 0) continue;\n lastExtractedGForward = gF[current];\n visF[current] = 1;\n\n // If reverse has finalized this node, we have a candidate meeting.\n if (visR[current] !== 0) {\n const total = gF[current] + gR[current];\n if (total < bestPathCost) {\n bestPathCost = total;\n meetingNode = current;\n }\n }\n\n // Relax neighbors and push newly improved ones\n if (this.csrOffsets && current < this.csrNodeCount) {\n const csrOffsets = this.csrOffsets!;\n const csrIndices = this.csrIndices!;\n const csrDistances = this.csrDistances!;\n for (let i = csrOffsets[current], endOff = csrOffsets[current + 1]; i < endOff; i++) {\n const nbNode = csrIndices[i];\n const tentativeG = gF[current] + csrDistances[i];\n if (tentativeG < gF[nbNode]) {\n gF[nbNode] = tentativeG;\n prevF[nbNode] = current;\n const otherG = gR[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openF.insert(tentativeG, nbNode);\n }\n }\n } else {\n const neighbors = adj[current];\n if (neighbors && neighbors.length) {\n for (let i = 0, n = neighbors.length; i < n; i++) {\n const nb = neighbors[i];\n const nbNode = nb.node;\n const tentativeG = gF[current] + nb.distance;\n if (tentativeG < gF[nbNode]) {\n gF[nbNode] = tentativeG;\n prevF[nbNode] = current;\n const otherG = gR[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openF.insert(tentativeG, nbNode);\n }\n }\n }\n }\n } else {\n const current = openR.extractMin()!;\n if (visR[current] !== 0) continue;\n lastExtractedGReverse = gR[current];\n visR[current] = 1;\n\n if (visF[current] !== 0) {\n const total = gF[current] + gR[current];\n if (total < bestPathCost) {\n bestPathCost = total;\n meetingNode = current;\n }\n }\n\n // Reverse direction: same neighbor iteration because graph is undirected.\n // Store successor pointer (next step toward end) i.e. nextR[neighbor] = current.\n if (this.csrOffsets && current < this.csrNodeCount) {\n const csrOffsets = this.csrOffsets!;\n const csrIndices = this.csrIndices!;\n const csrDistances = this.csrDistances!;\n for (let i = csrOffsets[current], endOff = csrOffsets[current + 1]; i < endOff; i++) {\n const nbNode = csrIndices[i];\n const tentativeG = gR[current] + csrDistances[i];\n if (tentativeG < gR[nbNode]) {\n gR[nbNode] = tentativeG;\n nextR[nbNode] = current;\n const otherG = gF[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openR.insert(tentativeG, nbNode);\n }\n }\n } else {\n const neighbors = adj[current];\n if (neighbors && neighbors.length) {\n for (let i = 0, n = neighbors.length; i < n; i++) {\n const nb = neighbors[i];\n const nbNode = nb.node;\n const tentativeG = gR[current] + nb.distance;\n if (tentativeG < gR[nbNode]) {\n gR[nbNode] = tentativeG;\n nextR[nbNode] = current;\n const otherG = gF[nbNode];\n if (otherG !== Number.POSITIVE_INFINITY) {\n const total = tentativeG + otherG;\n if (total < bestPathCost) { bestPathCost = total; meetingNode = nbNode; }\n }\n openR.insert(tentativeG, nbNode);\n }\n }\n }\n }\n }\n }\n\n if (meetingNode < 0) {\n return null;\n }\n\n // Reconstruct path: start -> meeting using prevF, then meeting -> end using nextR\n const path: Position[] = [];\n\n // Walk back from meeting to start, collecting nodes\n let cur = meetingNode;\n while (cur !== startIndex && cur >= 0) {\n path.push(coords[cur]);\n cur = prevF[cur];\n }\n if (cur !== startIndex) {\n // Forward tree doesn't connect start to meeting (shouldn't happen if meeting is valid)\n return null;\n }\n path.push(coords[startIndex]);\n path.reverse();\n\n // Walk from meeting to end (skip meeting node because it's already included)\n cur = meetingNode;\n while (cur !== endIndex) {\n cur = nextR[cur];\n if (cur < 0) {\n return null;\n }\n path.push(coords[cur]);\n }\n\n return {\n type: \"Feature\",\n geometry: { type: \"LineString\", coordinates: path },\n properties: {},\n };\n }\n\n /**\n * Helper to index start/end in getRoute.\n */\n private getOrCreateIndex(coord: Position): number { // Ensure a coordinate has a node index, creating if absent\n const lng = coord[0]; // Extract longitude\n const lat = coord[1]; // Extract latitude\n\n let latMap = this.coordinateIndexMap.get(lng); // Get lat→index map for this longitude\n if (latMap === undefined) { // Create if missing\n latMap = new Map<number, number>();\n this.coordinateIndexMap.set(lng, latMap);\n }\n\n let index = latMap.get(lat); // Lookup index by latitude\n if (index === undefined) { // If not found, append new node\n\n index = this.coordinates.length; // New index at end\n this.coordinates.push(coord); // Store coordinate\n latMap.set(lat, index); // Record mapping\n\n // Ensure sparse adjacency slot for dynamically added nodes\n this.adjacencyList[index] = []; // Init empty neighbor array\n\n // Extend CSR offsets to keep indices consistent (no neighbors for new node)\n if (this.csrOffsets) { // Only adjust if CSR already built\n\n // Only need to expand offsets by one; indices/distances remain unchanged\n const oldCount = this.csrNodeCount; // Nodes currently covered by CSR\n\n // Appending exactly one new node at the end\n if (index === oldCount) {\n const newOffsets = new Int32Array(oldCount + 2); // Allocate offsets for +1 node\n newOffsets.set(this.csrOffsets, 0); // Copy previous offsets\n\n // Last offset repeats to indicate zero neighbors\n newOffsets[oldCount + 1] = newOffsets[oldCount]; // Replicate last pointer\n this.csrOffsets = newOffsets; // Swap in new offsets\n this.csrNodeCount = oldCount + 1; // Increment CSR node count\n }\n }\n }\n\n return index;\n }\n\n // Ensure scratch arrays are allocated with at least `size` capacity.\n private ensureScratch(size: number): void {\n const ifAlreadyBigEnough = this.scratchCapacity >= size\n && this.gScoreScratch\n && this.cameFromScratch\n && this.visitedScratch\n && this.hScratch\n && this.gScoreRevScratch\n && this.cameFromRevScratch\n && this.visitedRevScratch\n && this.hRevScratch;\n\n if (ifAlreadyBigEnough) {\n return; // Nothing to do\n }\n const capacity = size | 0; // Ensure integer\n this.gScoreScratch = new Float64Array(capacity);\n this.cameFromScratch = new Int32Array(capacity);\n this.visitedScratch = new Uint8Array(capacity);\n this.hScratch = new Float64Array(capacity);\n this.gScoreRevScratch = new Float64Array(capacity);\n this.cameFromRevScratch = new Int32Array(capacity);\n this.visitedRevScratch = new Uint8Array(capacity);\n this.hRevScratch = new Float64Array(capacity);\n this.scratchCapacity = capacity;\n }\n\n\n // Iterate all consecutive segment pairs in a LineString FeatureCollection.\n // Kept as a simple loop helper to avoid repeating nested iteration logic.\n private forEachSegment(\n network: FeatureCollection<LineString>,\n fn: (a: Position, b: Position) => void,\n ): void {\n const features = network.features;\n for (let f = 0, fLen = features.length; f < fLen; f++) {\n const lineCoords = features[f].geometry.coordinates;\n for (let i = 0, len = lineCoords.length - 1; i < len; i++) {\n // GeoJSON coordinates are compatible with Position (number[]), and the project assumes [lng,lat]\n fn(lineCoords[i] as Position, lineCoords[i + 1] as Position);\n }\n }\n }\n\n // Hot-path coordinate indexer used by both build/expand.\n // Accepts explicit maps/arrays so callers can hoist them once.\n private indexCoordinate(\n coord: Position,\n coordIndexMapLocal: Map<number, Map<number, number>>,\n coordsLocal: Position[],\n onNewIndex?: (index: number) => void,\n ): number {\n const lng = coord[0];\n const lat = coord[1];\n\n let latMap = coordIndexMapLocal.get(lng);\n if (latMap === undefined) {\n latMap = new Map<number, number>();\n coordIndexMapLocal.set(lng, latMap);\n }\n\n let idx = latMap.get(lat);\n if (idx === undefined) {\n idx = coordsLocal.length;\n coordsLocal.push(coord);\n latMap.set(lat, idx);\n if (onNewIndex) onNewIndex(idx);\n }\n\n return idx;\n }\n\n}\n\nexport { TerraRoute, createCheapRuler, haversineDistance } ","import { Position } from \"geojson\";\n\n// This code is based on Mapbox's cheap-ruler library:\n\n// ISC License\n\n// Copyright (c) 2024, Mapbox\n\n// Permission to use, copy, modify, and/or distribute this software for any purpose\n// with or without fee is hereby granted, provided that the above copyright notice\n// and this permission notice appear in all copies.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n// THIS SOFTWARE.\n\n/**\n * Creates a function for fast geodesic distance approximation using local scaling constants\n * based on a reference latitude. Useful for city-scale distances.\n *\n * @param {number} lat - Reference latitude in degrees\n * @returns {(a: Position, b: Position) => number} - Function that computes distance between two points\n * \n * @example\n * const distance = createCheapRuler(50.5);\n * const d = distance([30.5, 50.5], [30.51, 50.49]);\n * \n */\nexport function createCheapRuler(lat: number): (a: Position, b: Position) => number {\n const RE = 6378.137; // Earth's equatorial radius in kilometers\n const FE = 1 / 298.257223563; // Earth's flattening\n const E2 = FE * (2 - FE);\n const RAD = Math.PI / 180;\n\n const cosLat = Math.cos(lat * RAD);\n const w2 = 1 / (1 - E2 * (1 - cosLat * cosLat));\n const w = Math.sqrt(w2);\n\n const m = RAD * RE;\n const kx = m * w * cosLat; // scale for longitude\n const ky = m * w * w2 * (1 - E2); // scale for latitude\n\n return function distance(a: Position, b: Position): number {\n let deltaLng = a[0] - b[0];\n\n while (deltaLng < -180) deltaLng += 360;\n while (deltaLng > 180) deltaLng -= 360;\n\n const dx = deltaLng * kx;\n const dy = (a[1] - b[1]) * ky;\n\n return Math.sqrt(dx * dx + dy * dy);\n };\n}"],"names":["haversineDistance","pointOne","pointTwo","toRadians","latOrLng","Math","PI","phiOne","lambdaOne","phiTwo","deltaPhi","deltalambda","a","sin","cos","atan2","sqrt","FourAryHeap","keys","this","values","idxs","length","insertCounter","_proto","prototype","insert","key","value","i","ck","cv","ci","p","pk","pi","extractMin","n","minValue","last","bubbleDown","size","k","v","idx","nodeK","nodeV","nodeI","c1","smallest","sK","sI","sV","c2","k2","i2","c3","k3","i3","c4","k4","i4","TerraRoute","options","_options$distanceMeas","_options$heap","network","distanceMeasurement","heapConstructor","coordinateIndexMap","Map","coordinates","adjacencyList","csrOffsets","csrIndices","csrDistances","csrNodeCount","gScoreScratch","cameFromScratch","visitedScratch","hScratch","gScoreRevScratch","cameFromRevScratch","visitedRevScratch","hRevScratch","scratchCapacity","heap","buildRouteGraph","_this","coordIndexMapLocal","coordsLocal","measureDistance","degree","forEachSegment","b","_degree$indexA","_degree$indexB","indexA","indexCoordinate","indexB","nodeCount","offsets","Int32Array","_degree$i","deg","totalEdges","indices","distances","Float64Array","cursor","slice","get","segmentDistance","pos","Array","expandRouteGraph","_this2","Error","type","features","concat","adj","undefined","push","node","distance","rebuildCsrFromAdjacency","covered","min","neighbors","endOff","len","nb","getRoute","start","end","startIndex","getOrCreateIndex","geometry","endIndex","coords","ensureScratch","gF","gR","prevF","nextR","visF","visR","hF","hR","fill","Number","POSITIVE_INFINITY","openF","openR","bestPathCost","meetingNode","lastExtractedGForward","lastExtractedGReverse","current","total","nbNode","tentativeG","otherG","path","cur","reverse","properties","coord","lng","lat","latMap","set","index","oldCount","newOffsets","capacity","Uint8Array","fn","f","fLen","lineCoords","onNewIndex","FE","E2","RAD","cosLat","w2","w","m","kx","ky","deltaLng","dx","dy"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terra-route",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "A library for routing along GeoJSON LineString networks",
5
5
  "scripts": {
6
6
  "docs": "typedoc",