Setting the file. One moment.
Prep Cesium Path · Remotion Maps · remotion-dev/skills · Skills Docs
ContentsBack to the top of the page Bundled file TECHNIQUE
techniques/cesium/scripts/ prep-cesium-path.mjs
JavaScript · 161 lines · 5 KB
// ADAPT for a new location: change START (a point ON your centerline where the corridor opens),
14 // WINDOW_KM, and DAMP/SMOOTH below. Input must be a single LineString feature (features[0].geometry).
15
16 import {readFileSync, writeFileSync, mkdirSync} from 'fs' ;
17 import {dirname, resolve} from 'path' ;
18 import {fileURLToPath} from 'url' ;
19
20 const __dir = dirname ( fileURLToPath ( import . meta .url));
21 const IN = process.argv[ 2 ] || resolve (__dir, '../assets/sample-river.geojson' );
22 const OUT = process.argv[ 3 ] || resolve (__dir, '../assets/cesium-path.json' );
23 const havKm = ( a , b ) => {
24 const R = 6371 ,
25 r = Math. PI / 180 ,
26 dLat = (b[ 1 ] - a[ 1 ]) * r,
27 dLng = (b[ 0 ] - a[ 0 ]) * r;
28 const h =
29 Math. sin (dLat / 2 ) ** 2 +
30 Math. cos (a[ 1 ] * r) * Math. cos (b[ 1 ] * r) * Math. sin (dLng / 2 ) ** 2 ;
31 return 2 * R * Math. asin (Math. sqrt (h));
32 };
33
34 const gorge = JSON . parse ( readFileSync ( IN , 'utf8' )).features[ 0 ].geometry
35 .coordinates;
36
37 // ADAPT: clip ~24 km of river from the reach where the flythrough opens. START must be a point ON the
38 // centerline (the script snaps to the nearest vertex). The sample's opening is the Yarlung gorge:
39 const START = [ 94.968 , 29.757 ];
40 let s0 = 0 ,
41 best = Infinity ;
42 gorge. forEach (( p , i ) => {
43 const d = havKm (p, START );
44 if (d < best) {
45 best = d;
46 s0 = i;
47 }
48 });
49 const WINDOW_KM = 30 ; // clip to ~end of gorge data; smoothing+dampening shrink it to the usable corridor
50 const clip = [];
51 for ( let i = s0, acc = 0 ; i < gorge. length ; i ++ ) {
52 if (i > s0) acc += havKm (gorge[i - 1 ], gorge[i]);
53 if (acc > WINDOW_KM ) break ;
54 clip. push (gorge[i]);
55 }
56
57 // Resample to even arc-length spacing so curvature is distributed evenly along the path.
58 const STEP_KM = 0.1 ;
59 const resample = ( coords ) => {
60 const out = [coords[ 0 ]. slice ()];
61 let carry = 0 ,
62 from = coords[ 0 ];
63 for ( let i = 1 ; i < coords. length ; i ++ ) {
64 let segLen = havKm (from, coords[i]);
65 while (carry + segLen >= STEP_KM ) {
66 const t = ( STEP_KM - carry) / segLen;
67 const np = [
68 from[ 0 ] + (coords[i][ 0 ] - from[ 0 ]) * t,
69 from[ 1 ] + (coords[i][ 1 ] - from[ 1 ]) * t,
70 ];
71 out. push (np);
72 from = np;
73 segLen = havKm (from, coords[i]);
74 carry = 0 ;
75 }
76 carry += segLen;
77 from = coords[i];
78 }
79 return out;
80 };
81
82 // Moving-average smoothing — inherently continuous (no kinks). Window in points; repeat for extra glass.
83 const smoothMA = ( coords , w , passes ) => {
84 let c = coords;
85 for ( let p = 0 ; p < passes; p ++ ) {
86 c = c. map (( _ , i ) => {
87 let sx = 0 ,
88 sy = 0 ,
89 n = 0 ;
90 for (
91 let j = Math. max ( 0 , i - w);
92 j <= Math. min (c. length - 1 , i + w);
93 j ++
94 ) {
95 sx += c[j][ 0 ];
96 sy += c[j][ 1 ];
97 n ++ ;
98 }
99 return [sx / n, sy / n];
100 });
101 }
102 return c;
103 };
104
105 const SMOOTH_W = 28 ; // ±2.8 km window — turns meanders into smooth flowing curves
106 const SMOOTH_PASSES = 2 ;
107 const DAMP = 0.45 ; // keep 45% of the (already-smooth) deviation → gentle, continuous swerve
108
109 const even = resample (clip);
110 const sm = smoothMA (even, SMOOTH_W , SMOOTH_PASSES );
111
112 const lat0 = (sm[ 0 ][ 1 ] * Math. PI ) / 180 ;
113 const kx = 111.32 * Math. cos (lat0),
114 ky = 110.57 ;
115 const toXY = ( p ) => [(p[ 0 ] - sm[ 0 ][ 0 ]) * kx, (p[ 1 ] - sm[ 0 ][ 1 ]) * ky];
116 const toLL = ( xy ) => [sm[ 0 ][ 0 ] + xy[ 0 ] / kx, sm[ 0 ][ 1 ] + xy[ 1 ] / ky];
117 const A = toXY (sm[ 0 ]),
118 B = toXY (sm[sm. length - 1 ]);
119 const AB = [ B [ 0 ] - A [ 0 ], B [ 1 ] - A [ 1 ]],
120 len2 = AB [ 0 ] ** 2 + AB [ 1 ] ** 2 ;
121 const path = sm. map (( p ) => {
122 const P = toXY (p);
123 const t = (( P [ 0 ] - A [ 0 ]) * AB [ 0 ] + ( P [ 1 ] - A [ 1 ]) * AB [ 1 ]) / len2;
124 const proj = [ A [ 0 ] + t * AB [ 0 ], A [ 1 ] + t * AB [ 1 ]];
125 return toLL ([
126 proj[ 0 ] + ( P [ 0 ] - proj[ 0 ]) * DAMP ,
127 proj[ 1 ] + ( P [ 1 ] - proj[ 1 ]) * DAMP ,
128 ]);
129 });
130
131 mkdirSync ( dirname ( OUT ), {recursive: true });
132 writeFileSync ( OUT , JSON . stringify (path));
133
134 let len = 0 ;
135 for ( let i = 1 ; i < path. length ; i ++ ) len += havKm (path[i - 1 ], path[i]);
136 console. log (
137 `cesium-path: clip ${ clip . length } → resample ${ even . length } → smooth → ${ path . length } pts · ${ len . toFixed ( 1 ) } km` ,
138 );
139 const bear = ( a , b ) => {
140 const r = Math. PI / 180 ;
141 const y = Math. sin ((b[ 0 ] - a[ 0 ]) * r) * Math. cos (b[ 1 ] * r);
142 const x =
143 Math. cos (a[ 1 ] * r) * Math. sin (b[ 1 ] * r) -
144 Math. sin (a[ 1 ] * r) * Math. cos (b[ 1 ] * r) * Math. cos ((b[ 0 ] - a[ 0 ]) * r);
145 return (Math. atan2 (y, x) * 180 ) / Math. PI ;
146 };
147 // heading sampled every ~1.5 km — should change gradually & continuously (no big jumps = no corners)
148 const stepPts = Math. round ( 1.5 / STEP_KM );
149 let prev = null ,
150 hs = [];
151 for ( let i = 0 ; i + stepPts < path. length ; i += stepPts) {
152 const h = bear (path[i], path[i + stepPts]);
153 if (prev !== null ) {
154 let d = h - prev;
155 while (d > 180 ) d -= 360 ;
156 while (d < - 180 ) d += 360 ;
157 hs. push (d. toFixed ( 0 ));
158 }
159 prev = h;
160 }
161 console. log ( ` heading deltas every 1.5km (deg): ${ hs . join ( ', ' ) }` );