@aeroswarm/weather-layer 0.1.1 → 0.1.2

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.
package/README.md CHANGED
@@ -50,6 +50,27 @@ sized in metres, so they scale with zoom and perspective. Drawn only when the ma
50
50
  (`EPSG:4978`) unless `only3D: false`. The RIA classes are passed in so this package never bundles
51
51
  its own copy of RIA.
52
52
 
53
+ ## C. Windy-style fields (flow particles + colour maps)
54
+
55
+ Wind particles that follow a gridded wind field, coloured by speed, plus colour maps for cloud cover,
56
+ rain or temperature. The grid can come from anywhere (see `@aeroswarm/weather`: `windGridFromStations`
57
+ for several METARs, `parseGrib2Json`, `parseGridJson`, `stationWindsFromModelPoints`).
58
+
59
+ ```ts
60
+ import { createFieldOverlay, createRiaUnproject, PRECIPITATION_RAMP } from "@aeroswarm/weather-layer";
61
+
62
+ const unproject = createRiaUnproject(map, { wgs84, createPoint, createTransformation });
63
+ const overlay = createFieldOverlay(map, { unproject });
64
+
65
+ overlay.setWind(windGrid); // particles
66
+ overlay.setScalar(cloudGrid); // cloud cover (default ramp)
67
+ overlay.setScalar(rainGrid, PRECIPITATION_RAMP); // or rain, temperature, ...
68
+ ```
69
+
70
+ The field is mapped onto a screen grid whenever the map moves (throttled), so the particles read a
71
+ small array instead of projecting every particle on every frame. Points that are not on the map (3D:
72
+ off the globe) have no wind.
73
+
53
74
  ## Limits
54
75
 
55
76
  METAR and TAF describe one point (the aerodrome). The animation is a representation around the
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { AnimationState } from '@aeroswarm/weather';
2
- export { M as MapLike, W as WeatherOverlay, a as WeatherOverlayOptions, b as WeatherRenderer, c as WeatherRendererOptions, d as WeatherTheme, e as createWeatherOverlay, m as mapScreenOffset } from './overlay-U9My4PH5.js';
1
+ import { AnimationState, ScalarGrid, WindGrid } from '@aeroswarm/weather';
2
+ import { M as MapLike } from './overlay-U9My4PH5.js';
3
+ export { W as WeatherOverlay, a as WeatherOverlayOptions, b as WeatherRenderer, c as WeatherRendererOptions, d as WeatherTheme, e as createWeatherOverlay, m as mapScreenOffset } from './overlay-U9My4PH5.js';
3
4
 
4
5
  /**
5
6
  * Kelas dan fungsi LuciadRIA yang dipakai layer awan. Diberikan oleh pemakai (injeksi) supaya package
@@ -113,6 +114,126 @@ declare function offsetToLonLat(centerLat: number, centerLon: number, eastM: num
113
114
  lon: number;
114
115
  };
115
116
 
117
+ /** Warna: [nilai, merah, hijau, biru, alfa 0-1]. Nilai harus naik. */
118
+ type RampStop = readonly [number, number, number, number, number?];
119
+ type Rgba = readonly [number, number, number, number];
120
+ /** Warna di `value` dengan interpolasi linear antar titik; di luar rentang dipakai titik ujung. */
121
+ declare function rampColor(stops: readonly RampStop[], value: number): Rgba;
122
+ declare const rgbaCss: ([r, g, b, a]: Rgba, alpha?: number) => string;
123
+ /** Kecepatan angin (m/s): dari biru dingin (tenang) ke merah (kencang), seperti peta angin umum. */
124
+ declare const WIND_RAMP: readonly RampStop[];
125
+ /** Tutupan awan (%): bening ke putih keabu-abuan. */
126
+ declare const CLOUD_RAMP: readonly RampStop[];
127
+ /** Curah hujan (mm/jam). */
128
+ declare const PRECIPITATION_RAMP: readonly RampStop[];
129
+ /** Suhu (°C). */
130
+ declare const TEMPERATURE_RAMP: readonly RampStop[];
131
+
132
+ /** Titik layar (piksel) ke koordinat geografis; null bila di luar peta (mis. di luar bola bumi). */
133
+ type Unproject = (x: number, y: number) => {
134
+ lon: number;
135
+ lat: number;
136
+ } | null;
137
+ /** Angin yang sudah dipetakan ke kisi layar: satu vektor arah layar dan kecepatan per sel. */
138
+ interface ScreenWindField {
139
+ cols: number;
140
+ rows: number;
141
+ cell: number;
142
+ /** Vektor satuan arah gerak di layar (x kanan, y bawah). */
143
+ dx: Float32Array;
144
+ dy: Float32Array;
145
+ /** Kecepatan (m/s); NaN = tanpa data. */
146
+ speed: Float32Array;
147
+ }
148
+ interface ScreenScalarField {
149
+ cols: number;
150
+ rows: number;
151
+ cell: number;
152
+ values: Float32Array;
153
+ }
154
+ /**
155
+ * Ubah grid angin geografis menjadi kisi layar. Dilakukan sekali tiap peta bergerak, supaya partikel
156
+ * tinggal membaca kisi (tanpa memproyeksikan ulang tiap partikel tiap frame).
157
+ */
158
+ declare function buildScreenWindField(grid: WindGrid, width: number, height: number, unproject: Unproject, screenOffsetDeg: number, cell?: number): ScreenWindField;
159
+ declare function buildScreenScalarField(grid: ScalarGrid, width: number, height: number, unproject: Unproject, cell?: number): ScreenScalarField;
160
+ /** Angin di titik layar (x, y) dari kisi; null di luar kisi atau tanpa data. */
161
+ declare function windAt(field: ScreenWindField, x: number, y: number): {
162
+ dx: number;
163
+ dy: number;
164
+ speed: number;
165
+ } | null;
166
+
167
+ interface FieldOverlayOptions {
168
+ /** Titik layar ke lon/lat; lihat `createRiaUnproject`. */
169
+ unproject: Unproject;
170
+ zIndex?: number;
171
+ maxFps?: number;
172
+ pixelsPerMs?: number;
173
+ getScreenOffset?: (map: MapLike) => number;
174
+ }
175
+ interface FieldOverlay {
176
+ /** Aliran partikel angin dari grid; null untuk menghapus. */
177
+ setWind(grid: WindGrid | null): void;
178
+ /** Peta warna dari grid skalar (awan, hujan, suhu); null untuk menghapus. */
179
+ setScalar(grid: ScalarGrid | null, ramp?: readonly RampStop[]): void;
180
+ setVisible(visible: boolean): void;
181
+ setOpacity(opacity: number): void;
182
+ destroy(): void;
183
+ }
184
+ /**
185
+ * Overlay medan cuaca ala Windy di atas peta: peta warna (di bawah) dan aliran partikel angin (di
186
+ * atas). Medan dipetakan ulang ke layar tiap peta bergerak (dibatasi ~10 kali per detik).
187
+ */
188
+ declare function createFieldOverlay(map: MapLike, options: FieldOverlayOptions): FieldOverlay;
189
+
190
+ interface FlowRendererOptions {
191
+ container: HTMLElement;
192
+ unproject: Unproject;
193
+ zIndex?: number;
194
+ maxFps?: number;
195
+ /** Piksel per detik untuk tiap m/s angin. Bawaan 9. */
196
+ pixelsPerMs?: number;
197
+ /** Jumlah partikel maksimum. Bawaan 3000. */
198
+ maxParticles?: number;
199
+ reducedMotion?: boolean;
200
+ }
201
+ /**
202
+ * Aliran angin ala Windy: partikel bergerak mengikuti medan angin dari grid, berwarna menurut
203
+ * kecepatan, dengan ekor yang memudar. Medan dipetakan ke kisi layar sekali tiap peta bergerak.
204
+ */
205
+ declare class FlowRenderer {
206
+ private readonly options;
207
+ private readonly canvas;
208
+ private readonly ctx;
209
+ private grid;
210
+ private field;
211
+ private particles;
212
+ private width;
213
+ private height;
214
+ private dpr;
215
+ private offset;
216
+ private raf;
217
+ private running;
218
+ private last;
219
+ private accumulated;
220
+ private readonly frameInterval;
221
+ private readonly reduceMotion;
222
+ constructor(options: FlowRendererOptions);
223
+ setWind(grid: WindGrid | null): void;
224
+ setScreenOffset(degrees: number): void;
225
+ /** Dipanggil saat peta bergerak/zoom: petakan ulang medan ke layar. */
226
+ refreshField(): void;
227
+ resize(): void;
228
+ setVisible(visible: boolean): void;
229
+ setOpacity(opacity: number): void;
230
+ start(): void;
231
+ stop(): void;
232
+ destroy(): void;
233
+ private spawn;
234
+ private step;
235
+ }
236
+
116
237
  /** Hitungan murni untuk renderer: tanpa DOM supaya mudah dites. */
117
238
  /**
118
239
  * Vektor gerak di layar (x ke kanan, y ke bawah) untuk arah `toDeg` (derajat dari utara, searah jarum
@@ -134,4 +255,58 @@ declare function gustPulse(gustiness: number, timeMs: number): number;
134
255
  /** Selang antar kilat (ms) dari laju kilat per menit, dengan sedikit acak (`random` 0-1). */
135
256
  declare function lightningInterval(perMinute: number, random: number): number;
136
257
 
137
- export { type CloudFeatureLayer, type CloudFieldOptions, type CloudLayer3D, type CloudLayerOptions, type CloudModel, type CloudPuff3D, type RiaCloudDeps, advanceCloudField, buildCloudField, clamp, createCloudLayer3D, gustPulse, lightningInterval, makeCloudSpriteUrl, offsetToLonLat, particleSpeed, rainCount, screenVector, seededRandom, windParticleCount };
258
+ /** Bagian RIA yang dipakai `createRiaUnproject`; diberikan oleh aplikasi (injeksi). */
259
+ interface RiaUnprojectDeps {
260
+ /** `getReference("EPSG:4326")`. */
261
+ wgs84: unknown;
262
+ createPoint: (reference: unknown, coordinates: number[]) => unknown;
263
+ createTransformation: (source: unknown, destination: unknown) => {
264
+ transform(point: unknown): {
265
+ x: number;
266
+ y: number;
267
+ };
268
+ };
269
+ }
270
+ interface UnprojectableMap {
271
+ readonly reference: unknown;
272
+ readonly viewToMapTransformation: {
273
+ transform(point: unknown): unknown;
274
+ };
275
+ }
276
+ /**
277
+ * Fungsi titik layar -> lon/lat untuk peta RIA 2D maupun 3D. Mengembalikan null untuk titik yang tidak
278
+ * jatuh pada peta (di 3D: di luar bola bumi), jadi aliran partikel berhenti di sana.
279
+ */
280
+ declare function createRiaUnproject(map: UnprojectableMap, deps: RiaUnprojectDeps): Unproject;
281
+
282
+ interface ScalarRendererOptions {
283
+ container: HTMLElement;
284
+ unproject: Unproject;
285
+ ramp?: readonly RampStop[];
286
+ zIndex?: number;
287
+ }
288
+ /**
289
+ * Peta warna dari grid skalar (tutupan awan, hujan, suhu): tiap sel layar diwarnai dari nilai grid,
290
+ * digambar halus. Digambar ulang hanya saat grid atau peta berubah, bukan tiap frame.
291
+ */
292
+ declare class ScalarRenderer {
293
+ private readonly options;
294
+ private readonly canvas;
295
+ private readonly ctx;
296
+ private readonly small;
297
+ private grid;
298
+ private ramp;
299
+ private width;
300
+ private height;
301
+ private dpr;
302
+ constructor(options: ScalarRendererOptions);
303
+ setGrid(grid: ScalarGrid | null, ramp?: readonly RampStop[]): void;
304
+ resize(): void;
305
+ /** Gambar ulang setelah peta bergerak atau di-zoom. */
306
+ draw(): void;
307
+ setVisible(visible: boolean): void;
308
+ setOpacity(opacity: number): void;
309
+ destroy(): void;
310
+ }
311
+
312
+ export { CLOUD_RAMP, type CloudFeatureLayer, type CloudFieldOptions, type CloudLayer3D, type CloudLayerOptions, type CloudModel, type CloudPuff3D, type FieldOverlay, type FieldOverlayOptions, FlowRenderer, type FlowRendererOptions, MapLike, PRECIPITATION_RAMP, type RampStop, type Rgba, type RiaCloudDeps, type RiaUnprojectDeps, ScalarRenderer, type ScalarRendererOptions, type ScreenScalarField, type ScreenWindField, TEMPERATURE_RAMP, type Unproject, WIND_RAMP, advanceCloudField, buildCloudField, buildScreenScalarField, buildScreenWindField, clamp, createCloudLayer3D, createFieldOverlay, createRiaUnproject, gustPulse, lightningInterval, makeCloudSpriteUrl, offsetToLonLat, particleSpeed, rainCount, rampColor, rgbaCss, screenVector, seededRandom, windAt, windParticleCount };
package/dist/index.js CHANGED
@@ -157,6 +157,70 @@ function createCloudLayer3D(ria, options) {
157
157
  };
158
158
  }
159
159
 
160
+ // src/colorRamp.ts
161
+ function rampColor(stops, value) {
162
+ const first = stops[0];
163
+ const last = stops[stops.length - 1];
164
+ if (!first || !last) return [0, 0, 0, 0];
165
+ if (Number.isNaN(value) || value <= first[0]) return [first[1], first[2], first[3], first[4] ?? 1];
166
+ if (value >= last[0]) return [last[1], last[2], last[3], last[4] ?? 1];
167
+ for (let i = 1; i < stops.length; i++) {
168
+ const hi = stops[i];
169
+ const lo = stops[i - 1];
170
+ if (value <= hi[0]) {
171
+ const t = (value - lo[0]) / (hi[0] - lo[0] || 1);
172
+ const mix = (a, b) => a + (b - a) * t;
173
+ return [mix(lo[1], hi[1]), mix(lo[2], hi[2]), mix(lo[3], hi[3]), mix(lo[4] ?? 1, hi[4] ?? 1)];
174
+ }
175
+ }
176
+ return [last[1], last[2], last[3], last[4] ?? 1];
177
+ }
178
+ var rgbaCss = ([r, g, b, a], alpha = 1) => `rgba(${Math.round(r)},${Math.round(g)},${Math.round(b)},${(a * alpha).toFixed(3)})`;
179
+ var WIND_RAMP = [
180
+ [0, 98, 113, 184],
181
+ [2, 61, 110, 163],
182
+ [4, 74, 148, 170],
183
+ [7, 74, 146, 115],
184
+ [10, 77, 142, 56],
185
+ [13, 160, 168, 50],
186
+ [17, 210, 168, 48],
187
+ [22, 220, 110, 40],
188
+ [28, 190, 50, 50],
189
+ [36, 150, 40, 120]
190
+ ];
191
+ var CLOUD_RAMP = [
192
+ [0, 255, 255, 255, 0],
193
+ [20, 235, 238, 242, 0.15],
194
+ [60, 205, 210, 218, 0.45],
195
+ [100, 150, 158, 170, 0.75]
196
+ ];
197
+ var PRECIPITATION_RAMP = [
198
+ [0, 120, 190, 230, 0],
199
+ [0.1, 120, 190, 230, 0.45],
200
+ [1, 70, 190, 200, 0.7],
201
+ [3, 80, 190, 110, 0.78],
202
+ [6, 210, 220, 60, 0.82],
203
+ [12, 240, 170, 40, 0.85],
204
+ [25, 225, 80, 50, 0.88],
205
+ [50, 170, 40, 150, 0.9]
206
+ ];
207
+ var TEMPERATURE_RAMP = [
208
+ [10, 80, 90, 200, 0.7],
209
+ [20, 70, 170, 200, 0.7],
210
+ [26, 120, 200, 110, 0.7],
211
+ [30, 235, 200, 70, 0.72],
212
+ [34, 235, 130, 50, 0.75],
213
+ [40, 200, 50, 50, 0.8]
214
+ ];
215
+
216
+ // src/screenField.ts
217
+ import {
218
+ directionFromUV,
219
+ sampleScalar,
220
+ sampleWind,
221
+ speedOf
222
+ } from "@aeroswarm/weather";
223
+
160
224
  // src/math.ts
161
225
  var RAD = Math.PI / 180;
162
226
  function screenVector(toDeg, screenOffsetDeg = 0) {
@@ -183,6 +247,196 @@ function lightningInterval(perMinute, random) {
183
247
  return 6e4 / perMinute * (0.4 + random * 1.2);
184
248
  }
185
249
 
250
+ // src/screenField.ts
251
+ function buildScreenWindField(grid, width, height, unproject, screenOffsetDeg, cell = 24) {
252
+ const cols = Math.max(1, Math.ceil(width / cell));
253
+ const rows = Math.max(1, Math.ceil(height / cell));
254
+ const dx = new Float32Array(cols * rows);
255
+ const dy = new Float32Array(cols * rows);
256
+ const speed = new Float32Array(cols * rows).fill(Number.NaN);
257
+ for (let row = 0; row < rows; row++) {
258
+ for (let col = 0; col < cols; col++) {
259
+ const ll = unproject((col + 0.5) * cell, (row + 0.5) * cell);
260
+ const wind = ll ? sampleWind(grid, ll.lon, ll.lat) : null;
261
+ if (!wind) continue;
262
+ const index = row * cols + col;
263
+ const vector = screenVector((directionFromUV(wind.u, wind.v) + 180) % 360, screenOffsetDeg);
264
+ dx[index] = vector.x;
265
+ dy[index] = vector.y;
266
+ speed[index] = speedOf(wind.u, wind.v);
267
+ }
268
+ }
269
+ return { cols, rows, cell, dx, dy, speed };
270
+ }
271
+ function buildScreenScalarField(grid, width, height, unproject, cell = 8) {
272
+ const cols = Math.max(1, Math.ceil(width / cell));
273
+ const rows = Math.max(1, Math.ceil(height / cell));
274
+ const values = new Float32Array(cols * rows).fill(Number.NaN);
275
+ for (let row = 0; row < rows; row++) {
276
+ for (let col = 0; col < cols; col++) {
277
+ const ll = unproject((col + 0.5) * cell, (row + 0.5) * cell);
278
+ const value = ll ? sampleScalar(grid, ll.lon, ll.lat) : null;
279
+ if (value !== null) values[row * cols + col] = value;
280
+ }
281
+ }
282
+ return { cols, rows, cell, values };
283
+ }
284
+ function windAt(field, x, y) {
285
+ const col = Math.floor(x / field.cell);
286
+ const row = Math.floor(y / field.cell);
287
+ if (col < 0 || row < 0 || col >= field.cols || row >= field.rows) return null;
288
+ const index = row * field.cols + col;
289
+ const speed = field.speed[index];
290
+ if (Number.isNaN(speed)) return null;
291
+ return { dx: field.dx[index], dy: field.dy[index], speed };
292
+ }
293
+
294
+ // src/flowRenderer.ts
295
+ var FlowRenderer = class {
296
+ constructor(options) {
297
+ this.options = options;
298
+ this.frameInterval = 1e3 / (options.maxFps ?? 30);
299
+ this.reduceMotion = options.reducedMotion ?? (typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches);
300
+ this.canvas = document.createElement("canvas");
301
+ this.canvas.dataset.weatherLayer = "flow";
302
+ Object.assign(this.canvas.style, {
303
+ position: "absolute",
304
+ inset: "0",
305
+ width: "100%",
306
+ height: "100%",
307
+ pointerEvents: "none",
308
+ zIndex: String(options.zIndex ?? 4)
309
+ });
310
+ options.container.appendChild(this.canvas);
311
+ const ctx = this.canvas.getContext("2d");
312
+ if (!ctx) throw new Error("Canvas 2D tidak tersedia");
313
+ this.ctx = ctx;
314
+ this.resize();
315
+ }
316
+ options;
317
+ canvas;
318
+ ctx;
319
+ grid = null;
320
+ field = null;
321
+ particles = [];
322
+ width = 1;
323
+ height = 1;
324
+ dpr = 1;
325
+ offset = 0;
326
+ raf = 0;
327
+ running = false;
328
+ last = 0;
329
+ accumulated = 0;
330
+ frameInterval;
331
+ reduceMotion;
332
+ setWind(grid) {
333
+ this.grid = grid;
334
+ this.refreshField();
335
+ }
336
+ setScreenOffset(degrees) {
337
+ if (degrees === this.offset) return;
338
+ this.offset = degrees;
339
+ this.refreshField();
340
+ }
341
+ /** Dipanggil saat peta bergerak/zoom: petakan ulang medan ke layar. */
342
+ refreshField() {
343
+ this.field = this.grid ? buildScreenWindField(this.grid, this.width, this.height, this.options.unproject, this.offset) : null;
344
+ if (!this.field) this.ctx.clearRect(0, 0, this.width, this.height);
345
+ }
346
+ resize() {
347
+ const rect = this.options.container.getBoundingClientRect();
348
+ this.width = Math.max(1, Math.round(rect.width));
349
+ this.height = Math.max(1, Math.round(rect.height));
350
+ this.dpr = Math.min(2, window.devicePixelRatio || 1);
351
+ this.canvas.width = this.width * this.dpr;
352
+ this.canvas.height = this.height * this.dpr;
353
+ this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
354
+ const count = Math.min(this.options.maxParticles ?? 3e3, Math.round(this.width * this.height / 700));
355
+ this.particles = Array.from({ length: count }, () => this.spawn());
356
+ this.refreshField();
357
+ }
358
+ setVisible(visible) {
359
+ this.canvas.style.display = visible ? "block" : "none";
360
+ if (visible) this.start();
361
+ else this.stop();
362
+ }
363
+ setOpacity(opacity) {
364
+ this.canvas.style.opacity = String(Math.min(1, Math.max(0, opacity)));
365
+ }
366
+ start() {
367
+ if (this.running) return;
368
+ this.running = true;
369
+ this.last = performance.now();
370
+ const tick = (now) => {
371
+ if (!this.running) return;
372
+ this.accumulated += now - this.last;
373
+ this.last = now;
374
+ if (!document.hidden && this.accumulated >= this.frameInterval) {
375
+ this.step(Math.min(0.1, this.accumulated / 1e3));
376
+ this.accumulated = 0;
377
+ }
378
+ this.raf = requestAnimationFrame(tick);
379
+ };
380
+ this.raf = requestAnimationFrame(tick);
381
+ }
382
+ stop() {
383
+ this.running = false;
384
+ cancelAnimationFrame(this.raf);
385
+ }
386
+ destroy() {
387
+ this.stop();
388
+ this.canvas.remove();
389
+ }
390
+ spawn() {
391
+ return {
392
+ x: Math.random() * this.width,
393
+ y: Math.random() * this.height,
394
+ age: Math.floor(Math.random() * 60),
395
+ life: 50 + Math.random() * 80
396
+ };
397
+ }
398
+ step(dt) {
399
+ const ctx = this.ctx;
400
+ ctx.globalCompositeOperation = "destination-out";
401
+ ctx.fillStyle = `rgba(0,0,0,${this.reduceMotion ? 1 : 0.07})`;
402
+ ctx.fillRect(0, 0, this.width, this.height);
403
+ ctx.globalCompositeOperation = "source-over";
404
+ const field = this.field;
405
+ if (!field) return;
406
+ const pixelsPerMs = this.options.pixelsPerMs ?? 9;
407
+ ctx.lineWidth = 1.3;
408
+ ctx.lineCap = "round";
409
+ const motion = this.reduceMotion ? 0 : 1;
410
+ const buckets = /* @__PURE__ */ new Map();
411
+ for (const p of this.particles) {
412
+ const wind = windAt(field, p.x, p.y);
413
+ if (!wind || p.age > p.life) {
414
+ Object.assign(p, this.spawn(), { age: 0 });
415
+ continue;
416
+ }
417
+ const step = wind.speed * pixelsPerMs * dt * motion;
418
+ const nx = p.x + wind.dx * step;
419
+ const ny = p.y + wind.dy * step;
420
+ const key = Math.round(wind.speed / 1.5);
421
+ let bucket = buckets.get(key);
422
+ if (!bucket) {
423
+ bucket = { color: rgbaCss(rampColor(WIND_RAMP, key * 1.5), 0.85), path: new Path2D() };
424
+ buckets.set(key, bucket);
425
+ }
426
+ bucket.path.moveTo(p.x, p.y);
427
+ bucket.path.lineTo(nx, ny);
428
+ p.x = nx;
429
+ p.y = ny;
430
+ p.age++;
431
+ if (nx < 0 || ny < 0 || nx > this.width || ny > this.height) p.age = p.life + 1;
432
+ }
433
+ for (const { color, path } of buckets.values()) {
434
+ ctx.strokeStyle = color;
435
+ ctx.stroke(path);
436
+ }
437
+ }
438
+ };
439
+
186
440
  // src/renderer.ts
187
441
  var CALM = {
188
442
  wind: { fromDeg: null, toDeg: null, speedKt: 0, gustiness: 0, turbulent: false },
@@ -599,12 +853,170 @@ function createWeatherOverlay(map, options = {}) {
599
853
  }
600
854
  };
601
855
  }
856
+
857
+ // src/scalarRenderer.ts
858
+ var ScalarRenderer = class {
859
+ constructor(options) {
860
+ this.options = options;
861
+ this.ramp = options.ramp ?? CLOUD_RAMP;
862
+ this.canvas = document.createElement("canvas");
863
+ this.canvas.dataset.weatherLayer = "scalar";
864
+ Object.assign(this.canvas.style, {
865
+ position: "absolute",
866
+ inset: "0",
867
+ width: "100%",
868
+ height: "100%",
869
+ pointerEvents: "none",
870
+ zIndex: String(options.zIndex ?? 3)
871
+ });
872
+ options.container.appendChild(this.canvas);
873
+ const ctx = this.canvas.getContext("2d");
874
+ if (!ctx) throw new Error("Canvas 2D tidak tersedia");
875
+ this.ctx = ctx;
876
+ this.resize();
877
+ }
878
+ options;
879
+ canvas;
880
+ ctx;
881
+ small = document.createElement("canvas");
882
+ grid = null;
883
+ ramp;
884
+ width = 1;
885
+ height = 1;
886
+ dpr = 1;
887
+ setGrid(grid, ramp) {
888
+ this.grid = grid;
889
+ if (ramp) this.ramp = ramp;
890
+ this.draw();
891
+ }
892
+ resize() {
893
+ const rect = this.options.container.getBoundingClientRect();
894
+ this.width = Math.max(1, Math.round(rect.width));
895
+ this.height = Math.max(1, Math.round(rect.height));
896
+ this.dpr = Math.min(2, window.devicePixelRatio || 1);
897
+ this.canvas.width = this.width * this.dpr;
898
+ this.canvas.height = this.height * this.dpr;
899
+ this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
900
+ this.draw();
901
+ }
902
+ /** Gambar ulang setelah peta bergerak atau di-zoom. */
903
+ draw() {
904
+ const ctx = this.ctx;
905
+ ctx.clearRect(0, 0, this.width, this.height);
906
+ if (!this.grid) return;
907
+ const field = buildScreenScalarField(this.grid, this.width, this.height, this.options.unproject);
908
+ this.small.width = field.cols;
909
+ this.small.height = field.rows;
910
+ const small = this.small.getContext("2d");
911
+ if (!small) return;
912
+ const image = small.createImageData(field.cols, field.rows);
913
+ for (let i = 0; i < field.values.length; i++) {
914
+ const value = field.values[i];
915
+ if (Number.isNaN(value)) continue;
916
+ const [r, g, b, a] = rampColor(this.ramp, value);
917
+ image.data[i * 4] = r;
918
+ image.data[i * 4 + 1] = g;
919
+ image.data[i * 4 + 2] = b;
920
+ image.data[i * 4 + 3] = Math.round(a * 255);
921
+ }
922
+ small.putImageData(image, 0, 0);
923
+ ctx.imageSmoothingEnabled = true;
924
+ ctx.imageSmoothingQuality = "high";
925
+ ctx.drawImage(this.small, 0, 0, field.cols * field.cell, field.rows * field.cell);
926
+ }
927
+ setVisible(visible) {
928
+ this.canvas.style.display = visible ? "block" : "none";
929
+ }
930
+ setOpacity(opacity) {
931
+ this.canvas.style.opacity = String(Math.min(1, Math.max(0, opacity)));
932
+ }
933
+ destroy() {
934
+ this.canvas.remove();
935
+ }
936
+ };
937
+
938
+ // src/fieldOverlay.ts
939
+ function createFieldOverlay(map, options) {
940
+ const container = map.domNode;
941
+ if (getComputedStyle(container).position === "static") container.style.position = "relative";
942
+ const offsetOf = options.getScreenOffset ?? mapScreenOffset;
943
+ const scalar = new ScalarRenderer({ container, unproject: options.unproject, zIndex: options.zIndex });
944
+ const flow = new FlowRenderer({
945
+ container,
946
+ unproject: options.unproject,
947
+ zIndex: (options.zIndex ?? 3) + 1,
948
+ maxFps: options.maxFps,
949
+ pixelsPerMs: options.pixelsPerMs
950
+ });
951
+ let width = container.clientWidth;
952
+ let height = container.clientHeight;
953
+ let pending = null;
954
+ const refresh = () => {
955
+ pending = null;
956
+ if (container.clientWidth !== width || container.clientHeight !== height) {
957
+ width = container.clientWidth;
958
+ height = container.clientHeight;
959
+ scalar.resize();
960
+ flow.resize();
961
+ }
962
+ flow.setScreenOffset(offsetOf(map));
963
+ flow.refreshField();
964
+ scalar.draw();
965
+ };
966
+ const handle = map.on("MapChange", () => {
967
+ if (pending === null) pending = setTimeout(refresh, 100);
968
+ });
969
+ flow.setScreenOffset(offsetOf(map));
970
+ flow.start();
971
+ return {
972
+ setWind: (grid) => flow.setWind(grid),
973
+ setScalar: (grid, ramp) => scalar.setGrid(grid, ramp),
974
+ setVisible(visible) {
975
+ flow.setVisible(visible);
976
+ scalar.setVisible(visible);
977
+ },
978
+ setOpacity(opacity) {
979
+ flow.setOpacity(opacity);
980
+ scalar.setOpacity(opacity);
981
+ },
982
+ destroy() {
983
+ if (pending !== null) clearTimeout(pending);
984
+ handle.remove();
985
+ flow.destroy();
986
+ scalar.destroy();
987
+ }
988
+ };
989
+ }
990
+
991
+ // src/riaUnproject.ts
992
+ function createRiaUnproject(map, deps) {
993
+ const toWgs84 = deps.createTransformation(map.reference, deps.wgs84);
994
+ return (x, y) => {
995
+ try {
996
+ const mapPoint = map.viewToMapTransformation.transform(deps.createPoint(null, [x, y, 0]));
997
+ const point = toWgs84.transform(mapPoint);
998
+ return Number.isFinite(point.x) && Number.isFinite(point.y) ? { lon: point.x, lat: point.y } : null;
999
+ } catch {
1000
+ return null;
1001
+ }
1002
+ };
1003
+ }
602
1004
  export {
1005
+ CLOUD_RAMP,
1006
+ FlowRenderer,
1007
+ PRECIPITATION_RAMP,
1008
+ ScalarRenderer,
1009
+ TEMPERATURE_RAMP,
1010
+ WIND_RAMP,
603
1011
  WeatherRenderer,
604
1012
  advanceCloudField,
605
1013
  buildCloudField,
1014
+ buildScreenScalarField,
1015
+ buildScreenWindField,
606
1016
  clamp,
607
1017
  createCloudLayer3D,
1018
+ createFieldOverlay,
1019
+ createRiaUnproject,
608
1020
  createWeatherOverlay,
609
1021
  gustPulse,
610
1022
  lightningInterval,
@@ -613,8 +1025,11 @@ export {
613
1025
  offsetToLonLat,
614
1026
  particleSpeed,
615
1027
  rainCount,
1028
+ rampColor,
1029
+ rgbaCss,
616
1030
  screenVector,
617
1031
  seededRandom,
1032
+ windAt,
618
1033
  windParticleCount
619
1034
  };
620
1035
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/clouds3d.ts","../src/cloudLayer.ts","../src/math.ts","../src/renderer.ts","../src/overlay.ts"],"sourcesContent":["import type { AnimationState } from \"@aeroswarm/weather\";\n\n/** Satu gumpalan awan di sekitar bandara, dalam meter dari titik pusat. */\nexport interface CloudPuff3D {\n id: string;\n eastM: number;\n northM: number;\n /** Ketinggian di atas permukaan laut (m): elevasi bandara + tinggi dasar awan (AGL). */\n altM: number;\n sizeM: number;\n opacity: number;\n convective: boolean;\n}\n\nexport interface CloudFieldOptions {\n /** Radius sebaran awan dari bandara (m). Bawaan 12.000. */\n radiusM?: number;\n /** Elevasi bandara (m di atas laut). METAR memberi tinggi awan AGL, jadi ini ditambahkan. */\n elevationM?: number;\n /** Biji acak supaya letak awan konsisten antar render. */\n seed?: number;\n}\n\nconst FT_TO_M = 0.3048;\nconst KT_TO_MS = 0.514_444;\nconst DEFAULT_BASE_M = 600;\nconst MAX_PER_LAYER = 14;\n\n/** Pembangkit acak kecil (mulberry32) agar medan awan deterministik. */\nexport function seededRandom(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a = (a + 0x6d2b79f5) >>> 0;\n let t = a;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Sebar gumpalan awan di sekitar bandara dari lapisan awan METAR/TAF. Banyaknya menurut tutupan,\n * ketinggiannya = elevasi bandara + tinggi dasar (AGL). Awan konvektif lebih besar dan lebih padat.\n */\nexport function buildCloudField(\n state: AnimationState,\n options: CloudFieldOptions = {},\n): CloudPuff3D[] {\n const radius = options.radiusM ?? 12_000;\n const elevation = options.elevationM ?? 0;\n const random = seededRandom(options.seed ?? 1);\n const field: CloudPuff3D[] = [];\n\n state.clouds.layers.forEach((layer, layerIndex) => {\n const count = Math.max(1, Math.round(layer.coverage * MAX_PER_LAYER));\n const baseM = layer.baseFt === null ? DEFAULT_BASE_M : layer.baseFt * FT_TO_M;\n for (let i = 0; i < count; i++) {\n const angle = random() * Math.PI * 2;\n const distance = Math.sqrt(random()) * radius;\n const size = (layer.convective ? 1400 : 800) * (0.6 + random() * 0.9);\n field.push({\n id: `cloud-${layerIndex}-${i}`,\n eastM: Math.sin(angle) * distance,\n northM: Math.cos(angle) * distance,\n altM: elevation + baseM + random() * (layer.convective ? 400 : 120),\n sizeM: size,\n opacity: Math.min(0.9, 0.3 + layer.coverage * 0.5) * (layer.convective ? 1.1 : 1),\n convective: layer.convective,\n });\n }\n });\n return field;\n}\n\n/**\n * Geser awan searah angin (`toDeg`, derajat dari utara). `timeScale` mempercepat gerak supaya\n * terlihat (1 = kecepatan nyata, yang nyaris tidak terlihat dari ketinggian peta). Awan yang\n * keluar dari radius muncul lagi di sisi seberang.\n */\nexport function advanceCloudField(\n field: readonly CloudPuff3D[],\n wind: { toDeg: number | null; speedKt: number },\n dtSeconds: number,\n options: { radiusM?: number; timeScale?: number } = {},\n): CloudPuff3D[] {\n if (wind.toDeg === null || wind.speedKt <= 0) return field.map((c) => ({ ...c }));\n const radius = options.radiusM ?? 12_000;\n const meters = wind.speedKt * KT_TO_MS * dtSeconds * (options.timeScale ?? 12);\n const east = Math.sin((wind.toDeg * Math.PI) / 180) * meters;\n const north = Math.cos((wind.toDeg * Math.PI) / 180) * meters;\n\n return field.map((cloud) => {\n let eastM = cloud.eastM + east;\n let northM = cloud.northM + north;\n if (Math.hypot(eastM, northM) > radius) {\n // Muncul lagi di sisi seberang lingkaran.\n eastM = -eastM * 0.98;\n northM = -northM * 0.98;\n }\n return { ...cloud, eastM, northM };\n });\n}\n\n/** Titik lat/lon dari selisih meter timur/utara terhadap pusat (pendekatan datar, cukup untuk ~20 km). */\nexport function offsetToLonLat(\n centerLat: number,\n centerLon: number,\n eastM: number,\n northM: number,\n): { lat: number; lon: number } {\n const metersPerDegLat = 111_320;\n const metersPerDegLon = metersPerDegLat * Math.cos((centerLat * Math.PI) / 180);\n return {\n lat: centerLat + northM / metersPerDegLat,\n lon: centerLon + eastM / metersPerDegLon,\n };\n}\n","import type { AnimationState } from \"@aeroswarm/weather\";\nimport {\n advanceCloudField,\n buildCloudField,\n type CloudPuff3D,\n offsetToLonLat,\n} from \"./clouds3d.js\";\n\n/**\n * Kelas dan fungsi LuciadRIA yang dipakai layer awan. Diberikan oleh pemakai (injeksi) supaya package\n * ini tidak punya dependensi ke RIA dan memakai salinan RIA milik aplikasi yang sama dengan petanya.\n */\nexport interface RiaCloudDeps {\n /** Referensi EPSG:4326, mis. `getReference(\"EPSG:4326\")`. */\n reference: unknown;\n Feature: new (\n shape: unknown,\n properties: Record<string, unknown>,\n id?: string,\n ) => unknown;\n FeatureModel: new (store: unknown, options: { reference: unknown }) => CloudModel;\n MemoryStore: new () => unknown;\n FeatureLayer: new (model: CloudModel, options: Record<string, unknown>) => CloudFeatureLayer;\n FeaturePainter: new () => CloudPainter;\n createPoint: (reference: unknown, coordinates: number[]) => unknown;\n}\n\nexport interface CloudModel {\n put(feature: unknown): unknown;\n remove(id: string): unknown;\n}\n\nexport interface CloudFeatureLayer {\n visible: boolean;\n}\n\ninterface CloudPainter {\n getDetailLevelScales?: () => number[];\n paintBody: (\n geoCanvas: { drawIcon(shape: unknown, style: Record<string, unknown>): void },\n feature: { properties: Record<string, unknown> },\n shape: unknown,\n layer: unknown,\n map: { reference: { identifier: string } },\n ) => void;\n}\n\nexport interface CloudLayerOptions {\n /** Pusat sebaran awan (bandara). */\n center: { lat: number; lon: number };\n /** Elevasi bandara (m di atas laut), ditambahkan ke tinggi dasar awan AGL. Bawaan 0. */\n elevationM?: number;\n radiusM?: number;\n /** Awan hanya digambar di peta 3D (EPSG:4978). Bawaan true. */\n only3D?: boolean;\n /** Percepatan gerak awan. Bawaan 12. */\n timeScale?: number;\n /** Selang pembaruan posisi (ms). Bawaan 500. */\n updateIntervalMs?: number;\n id?: string;\n}\n\nexport interface CloudLayer3D {\n readonly layer: CloudFeatureLayer;\n /** Ganti kondisi cuaca: awan dibangun ulang dari lapisan awan METAR/TAF. */\n setState(state: AnimationState): void;\n setVisible(visible: boolean): void;\n destroy(): void;\n}\n\n/** Sprite gumpalan awan sebagai data URL (gradien radial lembut). */\nexport function makeCloudSpriteUrl(): string {\n const canvas = document.createElement(\"canvas\");\n canvas.width = canvas.height = 128;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return \"\";\n const gradient = ctx.createRadialGradient(64, 64, 4, 64, 64, 62);\n gradient.addColorStop(0, \"rgba(255,255,255,0.95)\");\n gradient.addColorStop(0.55, \"rgba(255,255,255,0.45)\");\n gradient.addColorStop(1, \"rgba(255,255,255,0)\");\n ctx.fillStyle = gradient;\n ctx.fillRect(0, 0, 128, 128);\n return canvas.toDataURL(\"image/png\");\n}\n\n/**\n * Layer RIA berisi gumpalan awan di ketinggian sebenarnya (elevasi bandara + tinggi dasar AGL),\n * berukuran dalam meter sehingga ikut zoom dan perspektif 3D. Tambahkan ke peta dengan\n * `map.layerTree.addChild(cloud.layer)`.\n */\nexport function createCloudLayer3D(\n ria: RiaCloudDeps,\n options: CloudLayerOptions,\n): CloudLayer3D {\n const model = new ria.FeatureModel(new ria.MemoryStore(), { reference: ria.reference });\n const painter = new ria.FeaturePainter();\n const spriteUrl = makeCloudSpriteUrl();\n const only3D = options.only3D ?? true;\n\n painter.getDetailLevelScales = () => [1 / 100000, 1 / 20000, 1 / 5000];\n painter.paintBody = (geoCanvas, feature, shape, _layer, map) => {\n if (only3D && map.reference.identifier !== \"EPSG:4978\") return;\n const size = Number(feature.properties.sizeM) || 800;\n geoCanvas.drawIcon(shape, {\n url: spriteUrl,\n width: `${size}m`,\n height: `${size}m`,\n opacity: Number(feature.properties.opacity) || 0.5,\n draped: false,\n modulationColor: feature.properties.convective === true ? \"#8f98a3\" : \"#ffffff\",\n });\n };\n\n const layer = new ria.FeatureLayer(model, {\n id: options.id ?? \"weather-clouds\",\n label: \"Awan\",\n painter,\n selectable: false,\n visible: true,\n });\n\n let wind: { toDeg: number | null; speedKt: number } = { toDeg: null, speedKt: 0 };\n let field: CloudPuff3D[] = [];\n let knownIds = new Set<string>();\n\n const render = (): void => {\n const next = new Set<string>();\n for (const cloud of field) {\n const { lat, lon } = offsetToLonLat(\n options.center.lat,\n options.center.lon,\n cloud.eastM,\n cloud.northM,\n );\n model.put(\n new ria.Feature(\n ria.createPoint(ria.reference, [lon, lat, cloud.altM]),\n { sizeM: cloud.sizeM, opacity: cloud.opacity, convective: cloud.convective },\n cloud.id,\n ),\n );\n next.add(cloud.id);\n }\n for (const id of knownIds) if (!next.has(id)) model.remove(id);\n knownIds = next;\n };\n\n const interval = options.updateIntervalMs ?? 500;\n const timer = setInterval(() => {\n if (!layer.visible || field.length === 0) return;\n field = advanceCloudField(field, wind, interval / 1000, {\n radiusM: options.radiusM,\n timeScale: options.timeScale,\n });\n render();\n }, interval);\n\n return {\n layer,\n setState(state) {\n wind = { toDeg: state.wind.toDeg, speedKt: state.wind.speedKt };\n field = buildCloudField(state, {\n radiusM: options.radiusM,\n elevationM: options.elevationM,\n });\n render();\n },\n setVisible(visible) {\n layer.visible = visible;\n },\n destroy() {\n clearInterval(timer);\n for (const id of knownIds) model.remove(id);\n knownIds = new Set();\n field = [];\n },\n };\n}\n","/** Hitungan murni untuk renderer: tanpa DOM supaya mudah dites. */\n\nconst RAD = Math.PI / 180;\n\n/**\n * Vektor gerak di layar (x ke kanan, y ke bawah) untuk arah `toDeg` (derajat dari utara, searah jarum\n * jam). `screenOffsetDeg` = putaran peta: arah di layar = arah geografis + offset.\n */\nexport function screenVector(\n toDeg: number,\n screenOffsetDeg = 0,\n): { x: number; y: number } {\n const angle = (toDeg + screenOffsetDeg) * RAD;\n return { x: Math.sin(angle), y: -Math.cos(angle) };\n}\n\nexport const clamp = (value: number, min = 0, max = 1): number =>\n Math.min(max, Math.max(min, value));\n\n/** Jumlah partikel angin menurut luas layar, dibatasi `max`. */\nexport function windParticleCount(width: number, height: number, max = 520): number {\n return Math.min(max, Math.round((width * height) / 2600));\n}\n\n/** Jumlah garis hujan yang digambar dari `pool` tetes, menurut intensitas 0-1. */\nexport function rainCount(pool: number, intensity: number): number {\n return Math.round(pool * clamp(intensity));\n}\n\n/** Kecepatan visual partikel (px/detik) dari kecepatan angin (kt) dan hembusan sesaat. */\nexport function particleSpeed(speedKt: number, gustKt = 0): number {\n return (speedKt + gustKt) * 7;\n}\n\n/** Hembusan sesaat (kt tambahan) yang berdenyut halus sesuai `gustiness` 0-1. */\nexport function gustPulse(gustiness: number, timeMs: number): number {\n if (gustiness <= 0) return 0;\n const wave = 0.5 + 0.5 * Math.sin(timeMs / 700) * Math.sin(timeMs / 1900 + 1);\n return wave * gustiness * 20;\n}\n\n/** Selang antar kilat (ms) dari laju kilat per menit, dengan sedikit acak (`random` 0-1). */\nexport function lightningInterval(perMinute: number, random: number): number {\n if (perMinute <= 0) return Number.POSITIVE_INFINITY;\n return (60_000 / perMinute) * (0.4 + random * 1.2);\n}\n","import type { AnimationState } from \"@aeroswarm/weather\";\nimport {\n clamp,\n gustPulse,\n lightningInterval,\n particleSpeed,\n rainCount,\n screenVector,\n windParticleCount,\n} from \"./math.js\";\n\nexport type WeatherTheme = \"light\" | \"dark\";\n\nexport interface WeatherRendererOptions {\n /** Elemen tempat canvas dipasang (mis. `map.domNode`). Harus berposisi non-static. */\n container: HTMLElement;\n theme?: WeatherTheme;\n /** Batas frame per detik. Bawaan 30. */\n maxFps?: number;\n /** Gambar diam (tanpa gerak) bila pengguna meminta mengurangi animasi. Bawaan: ikuti OS. */\n reducedMotion?: boolean;\n zIndex?: number;\n}\n\ninterface Particle {\n x: number;\n y: number;\n age: number;\n life: number;\n}\ninterface Puff {\n dx: number;\n dy: number;\n r: number;\n}\ninterface Cloud {\n x: number;\n y: number;\n puffs: Puff[];\n layer: number;\n threshold: number;\n}\n\nconst CALM: AnimationState = {\n wind: { fromDeg: null, toDeg: null, speedKt: 0, gustiness: 0, turbulent: false },\n clouds: { layers: [], total: 0 },\n visibility: { meters: null, haze: 0 },\n obscuration: null,\n precipitation: null,\n thunderstorm: null,\n};\n\nconst DROP_POOL = 700;\n\nfunction makePuffSprite(): HTMLCanvasElement {\n const sprite = document.createElement(\"canvas\");\n sprite.width = sprite.height = 128;\n const ctx = sprite.getContext(\"2d\");\n if (ctx) {\n const gradient = ctx.createRadialGradient(64, 64, 4, 64, 64, 62);\n gradient.addColorStop(0, \"rgba(255,255,255,0.95)\");\n gradient.addColorStop(0.55, \"rgba(255,255,255,0.45)\");\n gradient.addColorStop(1, \"rgba(255,255,255,0)\");\n ctx.fillStyle = gradient;\n ctx.fillRect(0, 0, 128, 128);\n }\n return sprite;\n}\n\n/**\n * Menggambar angin, awan, hujan, kabut, dan petir ke dua canvas transparan di atas `container`.\n * Tidak mengenal peta: arah di layar diatur lewat `setScreenOffset` (putaran peta).\n */\nexport class WeatherRenderer {\n private readonly windCanvas: HTMLCanvasElement;\n private readonly fxCanvas: HTMLCanvasElement;\n private readonly wind: CanvasRenderingContext2D;\n private readonly fx: CanvasRenderingContext2D;\n private readonly puff = makePuffSprite();\n private readonly drops = Array.from({ length: DROP_POOL }, () => ({\n x: Math.random(),\n y: Math.random(),\n len: 0.6 + Math.random() * 0.8,\n speed: 0.7 + Math.random() * 0.6,\n }));\n\n private state: AnimationState = CALM;\n private theme: WeatherTheme;\n private screenOffset = 0;\n private width = 1;\n private height = 1;\n private dpr = 1;\n private particles: Particle[] = [];\n private clouds: Cloud[] = [];\n private raf = 0;\n private lastFrame = 0;\n private accumulated = 0;\n private flash = 0;\n private bolt: [number, number][] | null = null;\n private nextBolt = 0;\n private readonly frameInterval: number;\n private readonly reduceMotion: boolean;\n private running = false;\n\n constructor(private readonly options: WeatherRendererOptions) {\n this.theme = options.theme ?? \"light\";\n this.frameInterval = 1000 / (options.maxFps ?? 30);\n this.reduceMotion =\n options.reducedMotion ??\n (typeof matchMedia === \"function\" &&\n matchMedia(\"(prefers-reduced-motion: reduce)\").matches);\n\n this.windCanvas = this.createCanvas(\"wind\");\n this.fxCanvas = this.createCanvas(\"fx\");\n const wind = this.windCanvas.getContext(\"2d\");\n const fx = this.fxCanvas.getContext(\"2d\");\n if (!wind || !fx) throw new Error(\"Canvas 2D tidak tersedia\");\n this.wind = wind;\n this.fx = fx;\n this.resize();\n }\n\n setState(state: AnimationState): void {\n this.state = state;\n if (this.reduceMotion) this.renderFrame(performance.now(), 0);\n }\n\n /** Putaran peta (derajat) yang ditambahkan ke arah geografis angin. */\n setScreenOffset(degrees: number): void {\n this.screenOffset = degrees;\n }\n\n setTheme(theme: WeatherTheme): void {\n this.theme = theme;\n }\n\n setVisible(visible: boolean): void {\n for (const canvas of [this.windCanvas, this.fxCanvas]) {\n canvas.style.display = visible ? \"block\" : \"none\";\n }\n if (visible) this.start();\n else this.stop();\n }\n\n setOpacity(opacity: number): void {\n for (const canvas of [this.windCanvas, this.fxCanvas]) {\n canvas.style.opacity = String(clamp(opacity));\n }\n }\n\n resize(): void {\n const rect = this.options.container.getBoundingClientRect();\n this.width = Math.max(1, Math.round(rect.width));\n this.height = Math.max(1, Math.round(rect.height));\n this.dpr = Math.min(2, window.devicePixelRatio || 1);\n for (const canvas of [this.windCanvas, this.fxCanvas]) {\n canvas.width = this.width * this.dpr;\n canvas.height = this.height * this.dpr;\n }\n this.wind.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);\n this.fx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);\n this.seedParticles();\n this.seedClouds();\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n this.lastFrame = performance.now();\n const tick = (now: number): void => {\n if (!this.running) return;\n const dt = (now - this.lastFrame) / 1000;\n this.lastFrame = now;\n this.accumulated += dt * 1000;\n if (!document.hidden && this.accumulated >= this.frameInterval) {\n this.renderFrame(now, Math.min(0.1, this.accumulated / 1000));\n this.accumulated = 0;\n }\n this.raf = requestAnimationFrame(tick);\n };\n this.raf = requestAnimationFrame(tick);\n }\n\n stop(): void {\n this.running = false;\n cancelAnimationFrame(this.raf);\n }\n\n destroy(): void {\n this.stop();\n this.windCanvas.remove();\n this.fxCanvas.remove();\n }\n\n // ---- internal --------------------------------------------------------\n\n private createCanvas(name: string): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\");\n canvas.dataset.weatherLayer = name;\n Object.assign(canvas.style, {\n position: \"absolute\",\n inset: \"0\",\n width: \"100%\",\n height: \"100%\",\n pointerEvents: \"none\",\n zIndex: String(this.options.zIndex ?? 5),\n });\n this.options.container.appendChild(canvas);\n return canvas;\n }\n\n private seedParticles(): void {\n const count = windParticleCount(this.width, this.height);\n this.particles = Array.from({ length: count }, () => this.newParticle());\n this.wind.clearRect(0, 0, this.width, this.height);\n }\n\n private newParticle(): Particle {\n return {\n x: Math.random() * this.width,\n y: Math.random() * this.height,\n age: Math.floor(Math.random() * 80),\n life: 60 + Math.random() * 90,\n };\n }\n\n private seedClouds(): void {\n this.clouds = Array.from({ length: 16 }, (_, i) => {\n const size = 90 + Math.random() * 120;\n const count = 5 + Math.floor(Math.random() * 4);\n return {\n x: Math.random() * (this.width + 300) - 150,\n y: Math.random() * (this.height + 100) - 50,\n layer: 0.6 + Math.random() * 0.8,\n threshold: i / 16,\n puffs: Array.from({ length: count }, () => ({\n dx: (Math.random() - 0.5) * size * 1.4,\n dy: (Math.random() - 0.5) * size * 0.5,\n r: size * (0.5 + Math.random() * 0.5),\n })),\n };\n });\n }\n\n private renderFrame(now: number, dt: number): void {\n const { wind, thunderstorm } = this.state;\n const motion = this.reduceMotion ? 0 : 1;\n const toDeg = wind.toDeg;\n const vector = toDeg === null ? null : screenVector(toDeg, this.screenOffset);\n const gust = gustPulse(wind.gustiness, now);\n const speed = particleSpeed(wind.speedKt, gust);\n\n this.drawWind(now, dt, vector, wind.turbulent, speed, motion);\n\n const fx = this.fx;\n fx.clearRect(0, 0, this.width, this.height);\n this.drawClouds(dt, vector, wind.speedKt, motion);\n this.drawPrecipitation(dt, vector, wind.speedKt, motion);\n this.drawHaze(now, motion);\n this.drawLightning(now, dt, thunderstorm?.lightningPerMinute ?? 0);\n }\n\n private drawWind(\n now: number,\n dt: number,\n vector: { x: number; y: number } | null,\n turbulent: boolean,\n speed: number,\n motion: number,\n ): void {\n const ctx = this.wind;\n ctx.globalCompositeOperation = \"destination-out\";\n ctx.fillStyle = `rgba(0,0,0,${this.reduceMotion ? 1 : 0.1})`;\n ctx.fillRect(0, 0, this.width, this.height);\n ctx.globalCompositeOperation = \"source-over\";\n if (this.state.wind.speedKt <= 0) return;\n\n const alpha = Math.min(0.75, 0.22 + this.state.wind.speedKt / 50);\n ctx.lineWidth = 1.2;\n ctx.lineCap = \"round\";\n ctx.strokeStyle =\n this.theme === \"dark\"\n ? `rgba(120,200,220,${alpha})`\n : `rgba(31,122,140,${alpha})`;\n ctx.beginPath();\n for (const p of this.particles) {\n const fromX = p.x;\n const fromY = p.y;\n let vx: number;\n let vy: number;\n if (turbulent || vector === null) {\n vx = Math.cos(p.age / 12 + p.x) * 0.7;\n vy = Math.sin(p.age / 14 + p.y) * 0.7;\n } else {\n const wobble =\n Math.sin(p.y / 55 + now / 1400) * 0.35 + Math.cos(p.x / 70 - now / 1700) * 0.25;\n vx = vector.x - wobble * 0.25 * vector.y;\n vy = vector.y + wobble * 0.25 * vector.x;\n }\n p.x += vx * speed * dt * motion;\n p.y += vy * speed * dt * motion;\n p.age++;\n ctx.moveTo(fromX, fromY);\n ctx.lineTo(p.x, p.y);\n if (p.x < 0 || p.x > this.width || p.y < 0 || p.y > this.height || p.age > p.life) {\n Object.assign(p, this.newParticle(), { age: 0 });\n }\n }\n ctx.stroke();\n }\n\n private drawClouds(\n dt: number,\n vector: { x: number; y: number } | null,\n speedKt: number,\n motion: number,\n ): void {\n const ctx = this.fx;\n const cover = this.state.clouds.total;\n const precipitation = this.state.precipitation;\n const dark = clamp(\n (precipitation?.intensity ?? 0) * 0.6 + (this.state.thunderstorm?.overhead ? 0.4 : 0),\n );\n if (cover <= 0) return;\n\n for (const cloud of this.clouds) {\n if (vector) {\n cloud.x += vector.x * speedKt * 1.4 * cloud.layer * dt * motion;\n cloud.y += vector.y * speedKt * 1.4 * cloud.layer * dt * motion;\n }\n if (cloud.x < -250) cloud.x = this.width + 250;\n if (cloud.x > this.width + 250) cloud.x = -250;\n if (cloud.y < -150) cloud.y = this.height + 150;\n if (cloud.y > this.height + 150) cloud.y = -150;\n const visible = clamp((cover * 1.15 - cloud.threshold) * 4);\n if (visible <= 0) continue;\n ctx.globalAlpha = visible * (0.38 + 0.3 * cover) * (1 - dark * 0.15);\n for (const puff of cloud.puffs) {\n ctx.drawImage(\n this.puff,\n cloud.x + puff.dx - puff.r,\n cloud.y + puff.dy - puff.r,\n puff.r * 2,\n puff.r * 2,\n );\n }\n }\n ctx.globalAlpha = 1;\n if (dark > 0 || cover > 0.6) {\n ctx.fillStyle = `rgba(38,48,62,${(cover * 0.2 + dark * 0.32).toFixed(3)})`;\n ctx.fillRect(0, 0, this.width, this.height);\n }\n }\n\n private drawPrecipitation(\n dt: number,\n vector: { x: number; y: number } | null,\n speedKt: number,\n motion: number,\n ): void {\n const precipitation = this.state.precipitation;\n if (!precipitation) return;\n const ctx = this.fx;\n const count = rainCount(this.drops.length, precipitation.intensity);\n const slant = (vector?.x ?? 0) * Math.min(1, speedKt / 25) * 0.5;\n const snow = precipitation.kind === \"snow\";\n const fall = snow ? 0.25 : 1.6;\n\n if (snow) ctx.fillStyle = \"rgba(255,255,255,0.8)\";\n else {\n ctx.strokeStyle = `rgba(150,185,225,${(0.3 + 0.3 * precipitation.intensity).toFixed(2)})`;\n ctx.lineWidth = precipitation.kind === \"drizzle\" ? 0.7 : 1.1;\n ctx.beginPath();\n }\n for (let i = 0; i < count; i++) {\n const drop = this.drops[i];\n if (!drop) continue;\n drop.y += drop.speed * dt * fall * motion;\n drop.x += slant * dt * fall * motion;\n if (drop.y > 1) {\n drop.y = -0.05;\n drop.x = Math.random();\n }\n if (drop.x > 1) drop.x = 0;\n if (drop.x < 0) drop.x = 1;\n const x = drop.x * this.width;\n const y = drop.y * this.height;\n if (snow) ctx.fillRect(x, y, 2, 2);\n else {\n const length = 12 * drop.len;\n ctx.moveTo(x, y);\n ctx.lineTo(x - slant * length, y - length);\n }\n }\n if (!snow) ctx.stroke();\n }\n\n private drawHaze(now: number, motion: number): void {\n const { visibility, obscuration } = this.state;\n const haze = Math.max(visibility.haze, (obscuration?.density ?? 0) * 0.7);\n if (haze <= 0.01) return;\n const ctx = this.fx;\n const dark = this.theme === \"dark\";\n ctx.fillStyle = `${dark ? \"rgba(150,160,168,\" : \"rgba(214,219,223,\"}${(haze * 0.78).toFixed(3)})`;\n ctx.fillRect(0, 0, this.width, this.height);\n if (obscuration?.kind === \"fog\" || (visibility.meters ?? 10_000) < 1500) {\n for (let i = 0; i < 3; i++) {\n const cx = (((now / 40) * (i + 1) * 0.8 * motion) % (this.width * 1.3)) + i * this.width * 0.2;\n const cy = this.height * (0.3 + i * 0.25);\n const gradient = ctx.createRadialGradient(cx, cy, 10, cx, cy, this.width * 0.5);\n gradient.addColorStop(0, dark ? \"rgba(170,180,188,0.35)\" : \"rgba(235,238,240,0.5)\");\n gradient.addColorStop(1, \"rgba(0,0,0,0)\");\n ctx.fillStyle = gradient;\n ctx.fillRect(0, 0, this.width, this.height);\n }\n }\n }\n\n private drawLightning(now: number, dt: number, perMinute: number): void {\n if (this.reduceMotion) return;\n const ctx = this.fx;\n if (perMinute > 0 && now > this.nextBolt) {\n this.flash = 1;\n this.bolt = this.makeBolt();\n this.nextBolt = now + lightningInterval(perMinute, Math.random());\n }\n if (this.flash <= 0) return;\n ctx.fillStyle = `rgba(255,255,255,${(this.flash * 0.35).toFixed(3)})`;\n ctx.fillRect(0, 0, this.width, this.height);\n if (this.bolt && this.flash > 0.35) {\n ctx.strokeStyle = `rgba(255,255,255,${this.flash.toFixed(2)})`;\n ctx.lineWidth = 2.2;\n ctx.beginPath();\n this.bolt.forEach(([x, y], i) => {\n if (i === 0) ctx.moveTo(x, y);\n else ctx.lineTo(x, y);\n });\n ctx.stroke();\n }\n this.flash -= dt * 3.2;\n }\n\n private makeBolt(): [number, number][] {\n let x = this.width * (0.2 + Math.random() * 0.6);\n let y = 0;\n const points: [number, number][] = [[x, y]];\n const reach = this.height * (0.5 + Math.random() * 0.3);\n while (y < reach) {\n x += (Math.random() - 0.5) * 36;\n y += 14 + Math.random() * 22;\n points.push([x, y]);\n }\n return points;\n }\n}\n","import type { AnimationState } from \"@aeroswarm/weather\";\nimport { WeatherRenderer, type WeatherTheme } from \"./renderer.js\";\n\n/** Bagian peta LuciadRIA yang dipakai overlay. Sengaja struktural supaya tidak bergantung pada RIA. */\nexport interface MapLike {\n readonly domNode: HTMLElement;\n readonly reference?: { readonly identifier: string };\n readonly camera?: {\n asLook2D?: () => { rotation: number };\n asLookAt?: (distance: number) => { yaw: number };\n };\n on(event: \"MapChange\", callback: () => void): { remove(): void };\n}\n\nexport interface WeatherOverlayOptions {\n /** `auto` mengikuti `data-theme` pada <html>, lalu `prefers-color-scheme`. */\n theme?: WeatherTheme | \"auto\";\n zIndex?: number;\n maxFps?: number;\n /** Ganti cara menghitung putaran peta (derajat) bila peta Anda berbeda. */\n getScreenOffset?: (map: MapLike) => number;\n}\n\nexport interface WeatherOverlay {\n readonly renderer: WeatherRenderer;\n setState(state: AnimationState): void;\n setVisible(visible: boolean): void;\n setOpacity(opacity: number): void;\n destroy(): void;\n}\n\n/**\n * Putaran peta terhadap utara, supaya arah angin di layar tetap benar: 2D memakai rotasi kamera,\n * 3D memakai yaw kamera. Keduanya dibalik karena peta berputar berlawanan dengan kamera.\n * Tanda ini perlu dicek di peta sungguhan (putar peta, lihat arah partikel).\n */\nexport function mapScreenOffset(map: MapLike): number {\n const camera = map.camera;\n if (!camera) return 0;\n try {\n if (map.reference?.identifier === \"EPSG:4978\") {\n return -(camera.asLookAt?.(1000).yaw ?? 0);\n }\n return -(camera.asLook2D?.().rotation ?? 0);\n } catch {\n return 0;\n }\n}\n\nfunction detectTheme(): WeatherTheme {\n const explicit = document.documentElement.dataset.theme;\n if (explicit === \"dark\" || explicit === \"light\") return explicit;\n return matchMedia?.(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\n/** Pasang overlay cuaca (canvas transparan) di atas peta; hapus dengan `destroy()`. */\nexport function createWeatherOverlay(\n map: MapLike,\n options: WeatherOverlayOptions = {},\n): WeatherOverlay {\n const container = map.domNode;\n if (getComputedStyle(container).position === \"static\") {\n container.style.position = \"relative\";\n }\n\n const theme = options.theme ?? \"auto\";\n const renderer = new WeatherRenderer({\n container,\n theme: theme === \"auto\" ? detectTheme() : theme,\n zIndex: options.zIndex,\n maxFps: options.maxFps,\n });\n const offsetOf = options.getScreenOffset ?? mapScreenOffset;\n\n let width = container.clientWidth;\n let height = container.clientHeight;\n const handle = map.on(\"MapChange\", () => {\n renderer.setScreenOffset(offsetOf(map));\n if (container.clientWidth !== width || container.clientHeight !== height) {\n width = container.clientWidth;\n height = container.clientHeight;\n renderer.resize();\n }\n });\n\n const themeObserver =\n theme === \"auto\"\n ? new MutationObserver(() => renderer.setTheme(detectTheme()))\n : null;\n themeObserver?.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-theme\"],\n });\n\n renderer.setScreenOffset(offsetOf(map));\n renderer.start();\n\n return {\n renderer,\n setState: (state) => renderer.setState(state),\n setVisible: (visible) => renderer.setVisible(visible),\n setOpacity: (opacity) => renderer.setOpacity(opacity),\n destroy() {\n handle.remove();\n themeObserver?.disconnect();\n renderer.destroy();\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/clouds3d.ts","../src/cloudLayer.ts","../src/colorRamp.ts","../src/screenField.ts","../src/math.ts","../src/flowRenderer.ts","../src/renderer.ts","../src/overlay.ts","../src/scalarRenderer.ts","../src/fieldOverlay.ts","../src/riaUnproject.ts"],"sourcesContent":["import type { AnimationState } from \"@aeroswarm/weather\";\n\n/** Satu gumpalan awan di sekitar bandara, dalam meter dari titik pusat. */\nexport interface CloudPuff3D {\n id: string;\n eastM: number;\n northM: number;\n /** Ketinggian di atas permukaan laut (m): elevasi bandara + tinggi dasar awan (AGL). */\n altM: number;\n sizeM: number;\n opacity: number;\n convective: boolean;\n}\n\nexport interface CloudFieldOptions {\n /** Radius sebaran awan dari bandara (m). Bawaan 12.000. */\n radiusM?: number;\n /** Elevasi bandara (m di atas laut). METAR memberi tinggi awan AGL, jadi ini ditambahkan. */\n elevationM?: number;\n /** Biji acak supaya letak awan konsisten antar render. */\n seed?: number;\n}\n\nconst FT_TO_M = 0.3048;\nconst KT_TO_MS = 0.514_444;\nconst DEFAULT_BASE_M = 600;\nconst MAX_PER_LAYER = 14;\n\n/** Pembangkit acak kecil (mulberry32) agar medan awan deterministik. */\nexport function seededRandom(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a = (a + 0x6d2b79f5) >>> 0;\n let t = a;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Sebar gumpalan awan di sekitar bandara dari lapisan awan METAR/TAF. Banyaknya menurut tutupan,\n * ketinggiannya = elevasi bandara + tinggi dasar (AGL). Awan konvektif lebih besar dan lebih padat.\n */\nexport function buildCloudField(\n state: AnimationState,\n options: CloudFieldOptions = {},\n): CloudPuff3D[] {\n const radius = options.radiusM ?? 12_000;\n const elevation = options.elevationM ?? 0;\n const random = seededRandom(options.seed ?? 1);\n const field: CloudPuff3D[] = [];\n\n state.clouds.layers.forEach((layer, layerIndex) => {\n const count = Math.max(1, Math.round(layer.coverage * MAX_PER_LAYER));\n const baseM = layer.baseFt === null ? DEFAULT_BASE_M : layer.baseFt * FT_TO_M;\n for (let i = 0; i < count; i++) {\n const angle = random() * Math.PI * 2;\n const distance = Math.sqrt(random()) * radius;\n const size = (layer.convective ? 1400 : 800) * (0.6 + random() * 0.9);\n field.push({\n id: `cloud-${layerIndex}-${i}`,\n eastM: Math.sin(angle) * distance,\n northM: Math.cos(angle) * distance,\n altM: elevation + baseM + random() * (layer.convective ? 400 : 120),\n sizeM: size,\n opacity: Math.min(0.9, 0.3 + layer.coverage * 0.5) * (layer.convective ? 1.1 : 1),\n convective: layer.convective,\n });\n }\n });\n return field;\n}\n\n/**\n * Geser awan searah angin (`toDeg`, derajat dari utara). `timeScale` mempercepat gerak supaya\n * terlihat (1 = kecepatan nyata, yang nyaris tidak terlihat dari ketinggian peta). Awan yang\n * keluar dari radius muncul lagi di sisi seberang.\n */\nexport function advanceCloudField(\n field: readonly CloudPuff3D[],\n wind: { toDeg: number | null; speedKt: number },\n dtSeconds: number,\n options: { radiusM?: number; timeScale?: number } = {},\n): CloudPuff3D[] {\n if (wind.toDeg === null || wind.speedKt <= 0) return field.map((c) => ({ ...c }));\n const radius = options.radiusM ?? 12_000;\n const meters = wind.speedKt * KT_TO_MS * dtSeconds * (options.timeScale ?? 12);\n const east = Math.sin((wind.toDeg * Math.PI) / 180) * meters;\n const north = Math.cos((wind.toDeg * Math.PI) / 180) * meters;\n\n return field.map((cloud) => {\n let eastM = cloud.eastM + east;\n let northM = cloud.northM + north;\n if (Math.hypot(eastM, northM) > radius) {\n // Muncul lagi di sisi seberang lingkaran.\n eastM = -eastM * 0.98;\n northM = -northM * 0.98;\n }\n return { ...cloud, eastM, northM };\n });\n}\n\n/** Titik lat/lon dari selisih meter timur/utara terhadap pusat (pendekatan datar, cukup untuk ~20 km). */\nexport function offsetToLonLat(\n centerLat: number,\n centerLon: number,\n eastM: number,\n northM: number,\n): { lat: number; lon: number } {\n const metersPerDegLat = 111_320;\n const metersPerDegLon = metersPerDegLat * Math.cos((centerLat * Math.PI) / 180);\n return {\n lat: centerLat + northM / metersPerDegLat,\n lon: centerLon + eastM / metersPerDegLon,\n };\n}\n","import type { AnimationState } from \"@aeroswarm/weather\";\nimport {\n advanceCloudField,\n buildCloudField,\n type CloudPuff3D,\n offsetToLonLat,\n} from \"./clouds3d.js\";\n\n/**\n * Kelas dan fungsi LuciadRIA yang dipakai layer awan. Diberikan oleh pemakai (injeksi) supaya package\n * ini tidak punya dependensi ke RIA dan memakai salinan RIA milik aplikasi yang sama dengan petanya.\n */\nexport interface RiaCloudDeps {\n /** Referensi EPSG:4326, mis. `getReference(\"EPSG:4326\")`. */\n reference: unknown;\n Feature: new (\n shape: unknown,\n properties: Record<string, unknown>,\n id?: string,\n ) => unknown;\n FeatureModel: new (store: unknown, options: { reference: unknown }) => CloudModel;\n MemoryStore: new () => unknown;\n FeatureLayer: new (model: CloudModel, options: Record<string, unknown>) => CloudFeatureLayer;\n FeaturePainter: new () => CloudPainter;\n createPoint: (reference: unknown, coordinates: number[]) => unknown;\n}\n\nexport interface CloudModel {\n put(feature: unknown): unknown;\n remove(id: string): unknown;\n}\n\nexport interface CloudFeatureLayer {\n visible: boolean;\n}\n\ninterface CloudPainter {\n getDetailLevelScales?: () => number[];\n paintBody: (\n geoCanvas: { drawIcon(shape: unknown, style: Record<string, unknown>): void },\n feature: { properties: Record<string, unknown> },\n shape: unknown,\n layer: unknown,\n map: { reference: { identifier: string } },\n ) => void;\n}\n\nexport interface CloudLayerOptions {\n /** Pusat sebaran awan (bandara). */\n center: { lat: number; lon: number };\n /** Elevasi bandara (m di atas laut), ditambahkan ke tinggi dasar awan AGL. Bawaan 0. */\n elevationM?: number;\n radiusM?: number;\n /** Awan hanya digambar di peta 3D (EPSG:4978). Bawaan true. */\n only3D?: boolean;\n /** Percepatan gerak awan. Bawaan 12. */\n timeScale?: number;\n /** Selang pembaruan posisi (ms). Bawaan 500. */\n updateIntervalMs?: number;\n id?: string;\n}\n\nexport interface CloudLayer3D {\n readonly layer: CloudFeatureLayer;\n /** Ganti kondisi cuaca: awan dibangun ulang dari lapisan awan METAR/TAF. */\n setState(state: AnimationState): void;\n setVisible(visible: boolean): void;\n destroy(): void;\n}\n\n/** Sprite gumpalan awan sebagai data URL (gradien radial lembut). */\nexport function makeCloudSpriteUrl(): string {\n const canvas = document.createElement(\"canvas\");\n canvas.width = canvas.height = 128;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return \"\";\n const gradient = ctx.createRadialGradient(64, 64, 4, 64, 64, 62);\n gradient.addColorStop(0, \"rgba(255,255,255,0.95)\");\n gradient.addColorStop(0.55, \"rgba(255,255,255,0.45)\");\n gradient.addColorStop(1, \"rgba(255,255,255,0)\");\n ctx.fillStyle = gradient;\n ctx.fillRect(0, 0, 128, 128);\n return canvas.toDataURL(\"image/png\");\n}\n\n/**\n * Layer RIA berisi gumpalan awan di ketinggian sebenarnya (elevasi bandara + tinggi dasar AGL),\n * berukuran dalam meter sehingga ikut zoom dan perspektif 3D. Tambahkan ke peta dengan\n * `map.layerTree.addChild(cloud.layer)`.\n */\nexport function createCloudLayer3D(\n ria: RiaCloudDeps,\n options: CloudLayerOptions,\n): CloudLayer3D {\n const model = new ria.FeatureModel(new ria.MemoryStore(), { reference: ria.reference });\n const painter = new ria.FeaturePainter();\n const spriteUrl = makeCloudSpriteUrl();\n const only3D = options.only3D ?? true;\n\n painter.getDetailLevelScales = () => [1 / 100000, 1 / 20000, 1 / 5000];\n painter.paintBody = (geoCanvas, feature, shape, _layer, map) => {\n if (only3D && map.reference.identifier !== \"EPSG:4978\") return;\n const size = Number(feature.properties.sizeM) || 800;\n geoCanvas.drawIcon(shape, {\n url: spriteUrl,\n width: `${size}m`,\n height: `${size}m`,\n opacity: Number(feature.properties.opacity) || 0.5,\n draped: false,\n modulationColor: feature.properties.convective === true ? \"#8f98a3\" : \"#ffffff\",\n });\n };\n\n const layer = new ria.FeatureLayer(model, {\n id: options.id ?? \"weather-clouds\",\n label: \"Awan\",\n painter,\n selectable: false,\n visible: true,\n });\n\n let wind: { toDeg: number | null; speedKt: number } = { toDeg: null, speedKt: 0 };\n let field: CloudPuff3D[] = [];\n let knownIds = new Set<string>();\n\n const render = (): void => {\n const next = new Set<string>();\n for (const cloud of field) {\n const { lat, lon } = offsetToLonLat(\n options.center.lat,\n options.center.lon,\n cloud.eastM,\n cloud.northM,\n );\n model.put(\n new ria.Feature(\n ria.createPoint(ria.reference, [lon, lat, cloud.altM]),\n { sizeM: cloud.sizeM, opacity: cloud.opacity, convective: cloud.convective },\n cloud.id,\n ),\n );\n next.add(cloud.id);\n }\n for (const id of knownIds) if (!next.has(id)) model.remove(id);\n knownIds = next;\n };\n\n const interval = options.updateIntervalMs ?? 500;\n const timer = setInterval(() => {\n if (!layer.visible || field.length === 0) return;\n field = advanceCloudField(field, wind, interval / 1000, {\n radiusM: options.radiusM,\n timeScale: options.timeScale,\n });\n render();\n }, interval);\n\n return {\n layer,\n setState(state) {\n wind = { toDeg: state.wind.toDeg, speedKt: state.wind.speedKt };\n field = buildCloudField(state, {\n radiusM: options.radiusM,\n elevationM: options.elevationM,\n });\n render();\n },\n setVisible(visible) {\n layer.visible = visible;\n },\n destroy() {\n clearInterval(timer);\n for (const id of knownIds) model.remove(id);\n knownIds = new Set();\n field = [];\n },\n };\n}\n","/** Warna: [nilai, merah, hijau, biru, alfa 0-1]. Nilai harus naik. */\nexport type RampStop = readonly [number, number, number, number, number?];\nexport type Rgba = readonly [number, number, number, number];\n\n/** Warna di `value` dengan interpolasi linear antar titik; di luar rentang dipakai titik ujung. */\nexport function rampColor(stops: readonly RampStop[], value: number): Rgba {\n const first = stops[0];\n const last = stops[stops.length - 1];\n if (!first || !last) return [0, 0, 0, 0];\n if (Number.isNaN(value) || value <= first[0]) return [first[1], first[2], first[3], first[4] ?? 1];\n if (value >= last[0]) return [last[1], last[2], last[3], last[4] ?? 1];\n for (let i = 1; i < stops.length; i++) {\n const hi = stops[i] as RampStop;\n const lo = stops[i - 1] as RampStop;\n if (value <= hi[0]) {\n const t = (value - lo[0]) / (hi[0] - lo[0] || 1);\n const mix = (a: number, b: number): number => a + (b - a) * t;\n return [mix(lo[1], hi[1]), mix(lo[2], hi[2]), mix(lo[3], hi[3]), mix(lo[4] ?? 1, hi[4] ?? 1)];\n }\n }\n return [last[1], last[2], last[3], last[4] ?? 1];\n}\n\nexport const rgbaCss = ([r, g, b, a]: Rgba, alpha = 1): string =>\n `rgba(${Math.round(r)},${Math.round(g)},${Math.round(b)},${(a * alpha).toFixed(3)})`;\n\n/** Kecepatan angin (m/s): dari biru dingin (tenang) ke merah (kencang), seperti peta angin umum. */\nexport const WIND_RAMP: readonly RampStop[] = [\n [0, 98, 113, 184],\n [2, 61, 110, 163],\n [4, 74, 148, 170],\n [7, 74, 146, 115],\n [10, 77, 142, 56],\n [13, 160, 168, 50],\n [17, 210, 168, 48],\n [22, 220, 110, 40],\n [28, 190, 50, 50],\n [36, 150, 40, 120],\n];\n\n/** Tutupan awan (%): bening ke putih keabu-abuan. */\nexport const CLOUD_RAMP: readonly RampStop[] = [\n [0, 255, 255, 255, 0],\n [20, 235, 238, 242, 0.15],\n [60, 205, 210, 218, 0.45],\n [100, 150, 158, 170, 0.75],\n];\n\n/** Curah hujan (mm/jam). */\nexport const PRECIPITATION_RAMP: readonly RampStop[] = [\n [0, 120, 190, 230, 0],\n [0.1, 120, 190, 230, 0.45],\n [1, 70, 190, 200, 0.7],\n [3, 80, 190, 110, 0.78],\n [6, 210, 220, 60, 0.82],\n [12, 240, 170, 40, 0.85],\n [25, 225, 80, 50, 0.88],\n [50, 170, 40, 150, 0.9],\n];\n\n/** Suhu (°C). */\nexport const TEMPERATURE_RAMP: readonly RampStop[] = [\n [10, 80, 90, 200, 0.7],\n [20, 70, 170, 200, 0.7],\n [26, 120, 200, 110, 0.7],\n [30, 235, 200, 70, 0.72],\n [34, 235, 130, 50, 0.75],\n [40, 200, 50, 50, 0.8],\n];\n","import {\n directionFromUV,\n sampleScalar,\n sampleWind,\n type ScalarGrid,\n speedOf,\n type WindGrid,\n} from \"@aeroswarm/weather\";\nimport { screenVector } from \"./math.js\";\n\n/** Titik layar (piksel) ke koordinat geografis; null bila di luar peta (mis. di luar bola bumi). */\nexport type Unproject = (x: number, y: number) => { lon: number; lat: number } | null;\n\n/** Angin yang sudah dipetakan ke kisi layar: satu vektor arah layar dan kecepatan per sel. */\nexport interface ScreenWindField {\n cols: number;\n rows: number;\n cell: number;\n /** Vektor satuan arah gerak di layar (x kanan, y bawah). */\n dx: Float32Array;\n dy: Float32Array;\n /** Kecepatan (m/s); NaN = tanpa data. */\n speed: Float32Array;\n}\n\nexport interface ScreenScalarField {\n cols: number;\n rows: number;\n cell: number;\n values: Float32Array;\n}\n\n/**\n * Ubah grid angin geografis menjadi kisi layar. Dilakukan sekali tiap peta bergerak, supaya partikel\n * tinggal membaca kisi (tanpa memproyeksikan ulang tiap partikel tiap frame).\n */\nexport function buildScreenWindField(\n grid: WindGrid,\n width: number,\n height: number,\n unproject: Unproject,\n screenOffsetDeg: number,\n cell = 24,\n): ScreenWindField {\n const cols = Math.max(1, Math.ceil(width / cell));\n const rows = Math.max(1, Math.ceil(height / cell));\n const dx = new Float32Array(cols * rows);\n const dy = new Float32Array(cols * rows);\n const speed = new Float32Array(cols * rows).fill(Number.NaN);\n\n for (let row = 0; row < rows; row++) {\n for (let col = 0; col < cols; col++) {\n const ll = unproject((col + 0.5) * cell, (row + 0.5) * cell);\n const wind = ll ? sampleWind(grid, ll.lon, ll.lat) : null;\n if (!wind) continue;\n const index = row * cols + col;\n const vector = screenVector((directionFromUV(wind.u, wind.v) + 180) % 360, screenOffsetDeg);\n dx[index] = vector.x;\n dy[index] = vector.y;\n speed[index] = speedOf(wind.u, wind.v);\n }\n }\n return { cols, rows, cell, dx, dy, speed };\n}\n\nexport function buildScreenScalarField(\n grid: ScalarGrid,\n width: number,\n height: number,\n unproject: Unproject,\n cell = 8,\n): ScreenScalarField {\n const cols = Math.max(1, Math.ceil(width / cell));\n const rows = Math.max(1, Math.ceil(height / cell));\n const values = new Float32Array(cols * rows).fill(Number.NaN);\n for (let row = 0; row < rows; row++) {\n for (let col = 0; col < cols; col++) {\n const ll = unproject((col + 0.5) * cell, (row + 0.5) * cell);\n const value = ll ? sampleScalar(grid, ll.lon, ll.lat) : null;\n if (value !== null) values[row * cols + col] = value;\n }\n }\n return { cols, rows, cell, values };\n}\n\n/** Angin di titik layar (x, y) dari kisi; null di luar kisi atau tanpa data. */\nexport function windAt(\n field: ScreenWindField,\n x: number,\n y: number,\n): { dx: number; dy: number; speed: number } | null {\n const col = Math.floor(x / field.cell);\n const row = Math.floor(y / field.cell);\n if (col < 0 || row < 0 || col >= field.cols || row >= field.rows) return null;\n const index = row * field.cols + col;\n const speed = field.speed[index] as number;\n if (Number.isNaN(speed)) return null;\n return { dx: field.dx[index] as number, dy: field.dy[index] as number, speed };\n}\n","/** Hitungan murni untuk renderer: tanpa DOM supaya mudah dites. */\n\nconst RAD = Math.PI / 180;\n\n/**\n * Vektor gerak di layar (x ke kanan, y ke bawah) untuk arah `toDeg` (derajat dari utara, searah jarum\n * jam). `screenOffsetDeg` = putaran peta: arah di layar = arah geografis + offset.\n */\nexport function screenVector(\n toDeg: number,\n screenOffsetDeg = 0,\n): { x: number; y: number } {\n const angle = (toDeg + screenOffsetDeg) * RAD;\n return { x: Math.sin(angle), y: -Math.cos(angle) };\n}\n\nexport const clamp = (value: number, min = 0, max = 1): number =>\n Math.min(max, Math.max(min, value));\n\n/** Jumlah partikel angin menurut luas layar, dibatasi `max`. */\nexport function windParticleCount(width: number, height: number, max = 520): number {\n return Math.min(max, Math.round((width * height) / 2600));\n}\n\n/** Jumlah garis hujan yang digambar dari `pool` tetes, menurut intensitas 0-1. */\nexport function rainCount(pool: number, intensity: number): number {\n return Math.round(pool * clamp(intensity));\n}\n\n/** Kecepatan visual partikel (px/detik) dari kecepatan angin (kt) dan hembusan sesaat. */\nexport function particleSpeed(speedKt: number, gustKt = 0): number {\n return (speedKt + gustKt) * 7;\n}\n\n/** Hembusan sesaat (kt tambahan) yang berdenyut halus sesuai `gustiness` 0-1. */\nexport function gustPulse(gustiness: number, timeMs: number): number {\n if (gustiness <= 0) return 0;\n const wave = 0.5 + 0.5 * Math.sin(timeMs / 700) * Math.sin(timeMs / 1900 + 1);\n return wave * gustiness * 20;\n}\n\n/** Selang antar kilat (ms) dari laju kilat per menit, dengan sedikit acak (`random` 0-1). */\nexport function lightningInterval(perMinute: number, random: number): number {\n if (perMinute <= 0) return Number.POSITIVE_INFINITY;\n return (60_000 / perMinute) * (0.4 + random * 1.2);\n}\n","import type { WindGrid } from \"@aeroswarm/weather\";\nimport { rampColor, rgbaCss, WIND_RAMP } from \"./colorRamp.js\";\nimport { buildScreenWindField, type ScreenWindField, type Unproject, windAt } from \"./screenField.js\";\n\nexport interface FlowRendererOptions {\n container: HTMLElement;\n unproject: Unproject;\n zIndex?: number;\n maxFps?: number;\n /** Piksel per detik untuk tiap m/s angin. Bawaan 9. */\n pixelsPerMs?: number;\n /** Jumlah partikel maksimum. Bawaan 3000. */\n maxParticles?: number;\n reducedMotion?: boolean;\n}\n\ninterface Particle {\n x: number;\n y: number;\n age: number;\n life: number;\n}\n\n/**\n * Aliran angin ala Windy: partikel bergerak mengikuti medan angin dari grid, berwarna menurut\n * kecepatan, dengan ekor yang memudar. Medan dipetakan ke kisi layar sekali tiap peta bergerak.\n */\nexport class FlowRenderer {\n private readonly canvas: HTMLCanvasElement;\n private readonly ctx: CanvasRenderingContext2D;\n private grid: WindGrid | null = null;\n private field: ScreenWindField | null = null;\n private particles: Particle[] = [];\n private width = 1;\n private height = 1;\n private dpr = 1;\n private offset = 0;\n private raf = 0;\n private running = false;\n private last = 0;\n private accumulated = 0;\n private readonly frameInterval: number;\n private readonly reduceMotion: boolean;\n\n constructor(private readonly options: FlowRendererOptions) {\n this.frameInterval = 1000 / (options.maxFps ?? 30);\n this.reduceMotion =\n options.reducedMotion ??\n (typeof matchMedia === \"function\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches);\n this.canvas = document.createElement(\"canvas\");\n this.canvas.dataset.weatherLayer = \"flow\";\n Object.assign(this.canvas.style, {\n position: \"absolute\",\n inset: \"0\",\n width: \"100%\",\n height: \"100%\",\n pointerEvents: \"none\",\n zIndex: String(options.zIndex ?? 4),\n });\n options.container.appendChild(this.canvas);\n const ctx = this.canvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Canvas 2D tidak tersedia\");\n this.ctx = ctx;\n this.resize();\n }\n\n setWind(grid: WindGrid | null): void {\n this.grid = grid;\n this.refreshField();\n }\n\n setScreenOffset(degrees: number): void {\n if (degrees === this.offset) return;\n this.offset = degrees;\n this.refreshField();\n }\n\n /** Dipanggil saat peta bergerak/zoom: petakan ulang medan ke layar. */\n refreshField(): void {\n this.field = this.grid\n ? buildScreenWindField(this.grid, this.width, this.height, this.options.unproject, this.offset)\n : null;\n if (!this.field) this.ctx.clearRect(0, 0, this.width, this.height);\n }\n\n resize(): void {\n const rect = this.options.container.getBoundingClientRect();\n this.width = Math.max(1, Math.round(rect.width));\n this.height = Math.max(1, Math.round(rect.height));\n this.dpr = Math.min(2, window.devicePixelRatio || 1);\n this.canvas.width = this.width * this.dpr;\n this.canvas.height = this.height * this.dpr;\n this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);\n const count = Math.min(this.options.maxParticles ?? 3000, Math.round((this.width * this.height) / 700));\n this.particles = Array.from({ length: count }, () => this.spawn());\n this.refreshField();\n }\n\n setVisible(visible: boolean): void {\n this.canvas.style.display = visible ? \"block\" : \"none\";\n if (visible) this.start();\n else this.stop();\n }\n\n setOpacity(opacity: number): void {\n this.canvas.style.opacity = String(Math.min(1, Math.max(0, opacity)));\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n this.last = performance.now();\n const tick = (now: number): void => {\n if (!this.running) return;\n this.accumulated += now - this.last;\n this.last = now;\n if (!document.hidden && this.accumulated >= this.frameInterval) {\n this.step(Math.min(0.1, this.accumulated / 1000));\n this.accumulated = 0;\n }\n this.raf = requestAnimationFrame(tick);\n };\n this.raf = requestAnimationFrame(tick);\n }\n\n stop(): void {\n this.running = false;\n cancelAnimationFrame(this.raf);\n }\n\n destroy(): void {\n this.stop();\n this.canvas.remove();\n }\n\n private spawn(): Particle {\n return {\n x: Math.random() * this.width,\n y: Math.random() * this.height,\n age: Math.floor(Math.random() * 60),\n life: 50 + Math.random() * 80,\n };\n }\n\n private step(dt: number): void {\n const ctx = this.ctx;\n ctx.globalCompositeOperation = \"destination-out\";\n ctx.fillStyle = `rgba(0,0,0,${this.reduceMotion ? 1 : 0.07})`;\n ctx.fillRect(0, 0, this.width, this.height);\n ctx.globalCompositeOperation = \"source-over\";\n const field = this.field;\n if (!field) return;\n\n const pixelsPerMs = this.options.pixelsPerMs ?? 9;\n ctx.lineWidth = 1.3;\n ctx.lineCap = \"round\";\n const motion = this.reduceMotion ? 0 : 1;\n\n // Kelompokkan garis per warna (dibulatkan) agar tidak ribuan stroke berbeda per frame.\n const buckets = new Map<number, { color: string; path: Path2D }>();\n for (const p of this.particles) {\n const wind = windAt(field, p.x, p.y);\n if (!wind || p.age > p.life) {\n Object.assign(p, this.spawn(), { age: 0 });\n continue;\n }\n const step = wind.speed * pixelsPerMs * dt * motion;\n const nx = p.x + wind.dx * step;\n const ny = p.y + wind.dy * step;\n const key = Math.round(wind.speed / 1.5);\n let bucket = buckets.get(key);\n if (!bucket) {\n bucket = { color: rgbaCss(rampColor(WIND_RAMP, key * 1.5), 0.85), path: new Path2D() };\n buckets.set(key, bucket);\n }\n bucket.path.moveTo(p.x, p.y);\n bucket.path.lineTo(nx, ny);\n p.x = nx;\n p.y = ny;\n p.age++;\n if (nx < 0 || ny < 0 || nx > this.width || ny > this.height) p.age = p.life + 1;\n }\n for (const { color, path } of buckets.values()) {\n ctx.strokeStyle = color;\n ctx.stroke(path);\n }\n }\n}\n","import type { AnimationState } from \"@aeroswarm/weather\";\nimport {\n clamp,\n gustPulse,\n lightningInterval,\n particleSpeed,\n rainCount,\n screenVector,\n windParticleCount,\n} from \"./math.js\";\n\nexport type WeatherTheme = \"light\" | \"dark\";\n\nexport interface WeatherRendererOptions {\n /** Elemen tempat canvas dipasang (mis. `map.domNode`). Harus berposisi non-static. */\n container: HTMLElement;\n theme?: WeatherTheme;\n /** Batas frame per detik. Bawaan 30. */\n maxFps?: number;\n /** Gambar diam (tanpa gerak) bila pengguna meminta mengurangi animasi. Bawaan: ikuti OS. */\n reducedMotion?: boolean;\n zIndex?: number;\n}\n\ninterface Particle {\n x: number;\n y: number;\n age: number;\n life: number;\n}\ninterface Puff {\n dx: number;\n dy: number;\n r: number;\n}\ninterface Cloud {\n x: number;\n y: number;\n puffs: Puff[];\n layer: number;\n threshold: number;\n}\n\nconst CALM: AnimationState = {\n wind: { fromDeg: null, toDeg: null, speedKt: 0, gustiness: 0, turbulent: false },\n clouds: { layers: [], total: 0 },\n visibility: { meters: null, haze: 0 },\n obscuration: null,\n precipitation: null,\n thunderstorm: null,\n};\n\nconst DROP_POOL = 700;\n\nfunction makePuffSprite(): HTMLCanvasElement {\n const sprite = document.createElement(\"canvas\");\n sprite.width = sprite.height = 128;\n const ctx = sprite.getContext(\"2d\");\n if (ctx) {\n const gradient = ctx.createRadialGradient(64, 64, 4, 64, 64, 62);\n gradient.addColorStop(0, \"rgba(255,255,255,0.95)\");\n gradient.addColorStop(0.55, \"rgba(255,255,255,0.45)\");\n gradient.addColorStop(1, \"rgba(255,255,255,0)\");\n ctx.fillStyle = gradient;\n ctx.fillRect(0, 0, 128, 128);\n }\n return sprite;\n}\n\n/**\n * Menggambar angin, awan, hujan, kabut, dan petir ke dua canvas transparan di atas `container`.\n * Tidak mengenal peta: arah di layar diatur lewat `setScreenOffset` (putaran peta).\n */\nexport class WeatherRenderer {\n private readonly windCanvas: HTMLCanvasElement;\n private readonly fxCanvas: HTMLCanvasElement;\n private readonly wind: CanvasRenderingContext2D;\n private readonly fx: CanvasRenderingContext2D;\n private readonly puff = makePuffSprite();\n private readonly drops = Array.from({ length: DROP_POOL }, () => ({\n x: Math.random(),\n y: Math.random(),\n len: 0.6 + Math.random() * 0.8,\n speed: 0.7 + Math.random() * 0.6,\n }));\n\n private state: AnimationState = CALM;\n private theme: WeatherTheme;\n private screenOffset = 0;\n private width = 1;\n private height = 1;\n private dpr = 1;\n private particles: Particle[] = [];\n private clouds: Cloud[] = [];\n private raf = 0;\n private lastFrame = 0;\n private accumulated = 0;\n private flash = 0;\n private bolt: [number, number][] | null = null;\n private nextBolt = 0;\n private readonly frameInterval: number;\n private readonly reduceMotion: boolean;\n private running = false;\n\n constructor(private readonly options: WeatherRendererOptions) {\n this.theme = options.theme ?? \"light\";\n this.frameInterval = 1000 / (options.maxFps ?? 30);\n this.reduceMotion =\n options.reducedMotion ??\n (typeof matchMedia === \"function\" &&\n matchMedia(\"(prefers-reduced-motion: reduce)\").matches);\n\n this.windCanvas = this.createCanvas(\"wind\");\n this.fxCanvas = this.createCanvas(\"fx\");\n const wind = this.windCanvas.getContext(\"2d\");\n const fx = this.fxCanvas.getContext(\"2d\");\n if (!wind || !fx) throw new Error(\"Canvas 2D tidak tersedia\");\n this.wind = wind;\n this.fx = fx;\n this.resize();\n }\n\n setState(state: AnimationState): void {\n this.state = state;\n if (this.reduceMotion) this.renderFrame(performance.now(), 0);\n }\n\n /** Putaran peta (derajat) yang ditambahkan ke arah geografis angin. */\n setScreenOffset(degrees: number): void {\n this.screenOffset = degrees;\n }\n\n setTheme(theme: WeatherTheme): void {\n this.theme = theme;\n }\n\n setVisible(visible: boolean): void {\n for (const canvas of [this.windCanvas, this.fxCanvas]) {\n canvas.style.display = visible ? \"block\" : \"none\";\n }\n if (visible) this.start();\n else this.stop();\n }\n\n setOpacity(opacity: number): void {\n for (const canvas of [this.windCanvas, this.fxCanvas]) {\n canvas.style.opacity = String(clamp(opacity));\n }\n }\n\n resize(): void {\n const rect = this.options.container.getBoundingClientRect();\n this.width = Math.max(1, Math.round(rect.width));\n this.height = Math.max(1, Math.round(rect.height));\n this.dpr = Math.min(2, window.devicePixelRatio || 1);\n for (const canvas of [this.windCanvas, this.fxCanvas]) {\n canvas.width = this.width * this.dpr;\n canvas.height = this.height * this.dpr;\n }\n this.wind.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);\n this.fx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);\n this.seedParticles();\n this.seedClouds();\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n this.lastFrame = performance.now();\n const tick = (now: number): void => {\n if (!this.running) return;\n const dt = (now - this.lastFrame) / 1000;\n this.lastFrame = now;\n this.accumulated += dt * 1000;\n if (!document.hidden && this.accumulated >= this.frameInterval) {\n this.renderFrame(now, Math.min(0.1, this.accumulated / 1000));\n this.accumulated = 0;\n }\n this.raf = requestAnimationFrame(tick);\n };\n this.raf = requestAnimationFrame(tick);\n }\n\n stop(): void {\n this.running = false;\n cancelAnimationFrame(this.raf);\n }\n\n destroy(): void {\n this.stop();\n this.windCanvas.remove();\n this.fxCanvas.remove();\n }\n\n // ---- internal --------------------------------------------------------\n\n private createCanvas(name: string): HTMLCanvasElement {\n const canvas = document.createElement(\"canvas\");\n canvas.dataset.weatherLayer = name;\n Object.assign(canvas.style, {\n position: \"absolute\",\n inset: \"0\",\n width: \"100%\",\n height: \"100%\",\n pointerEvents: \"none\",\n zIndex: String(this.options.zIndex ?? 5),\n });\n this.options.container.appendChild(canvas);\n return canvas;\n }\n\n private seedParticles(): void {\n const count = windParticleCount(this.width, this.height);\n this.particles = Array.from({ length: count }, () => this.newParticle());\n this.wind.clearRect(0, 0, this.width, this.height);\n }\n\n private newParticle(): Particle {\n return {\n x: Math.random() * this.width,\n y: Math.random() * this.height,\n age: Math.floor(Math.random() * 80),\n life: 60 + Math.random() * 90,\n };\n }\n\n private seedClouds(): void {\n this.clouds = Array.from({ length: 16 }, (_, i) => {\n const size = 90 + Math.random() * 120;\n const count = 5 + Math.floor(Math.random() * 4);\n return {\n x: Math.random() * (this.width + 300) - 150,\n y: Math.random() * (this.height + 100) - 50,\n layer: 0.6 + Math.random() * 0.8,\n threshold: i / 16,\n puffs: Array.from({ length: count }, () => ({\n dx: (Math.random() - 0.5) * size * 1.4,\n dy: (Math.random() - 0.5) * size * 0.5,\n r: size * (0.5 + Math.random() * 0.5),\n })),\n };\n });\n }\n\n private renderFrame(now: number, dt: number): void {\n const { wind, thunderstorm } = this.state;\n const motion = this.reduceMotion ? 0 : 1;\n const toDeg = wind.toDeg;\n const vector = toDeg === null ? null : screenVector(toDeg, this.screenOffset);\n const gust = gustPulse(wind.gustiness, now);\n const speed = particleSpeed(wind.speedKt, gust);\n\n this.drawWind(now, dt, vector, wind.turbulent, speed, motion);\n\n const fx = this.fx;\n fx.clearRect(0, 0, this.width, this.height);\n this.drawClouds(dt, vector, wind.speedKt, motion);\n this.drawPrecipitation(dt, vector, wind.speedKt, motion);\n this.drawHaze(now, motion);\n this.drawLightning(now, dt, thunderstorm?.lightningPerMinute ?? 0);\n }\n\n private drawWind(\n now: number,\n dt: number,\n vector: { x: number; y: number } | null,\n turbulent: boolean,\n speed: number,\n motion: number,\n ): void {\n const ctx = this.wind;\n ctx.globalCompositeOperation = \"destination-out\";\n ctx.fillStyle = `rgba(0,0,0,${this.reduceMotion ? 1 : 0.1})`;\n ctx.fillRect(0, 0, this.width, this.height);\n ctx.globalCompositeOperation = \"source-over\";\n if (this.state.wind.speedKt <= 0) return;\n\n const alpha = Math.min(0.75, 0.22 + this.state.wind.speedKt / 50);\n ctx.lineWidth = 1.2;\n ctx.lineCap = \"round\";\n ctx.strokeStyle =\n this.theme === \"dark\"\n ? `rgba(120,200,220,${alpha})`\n : `rgba(31,122,140,${alpha})`;\n ctx.beginPath();\n for (const p of this.particles) {\n const fromX = p.x;\n const fromY = p.y;\n let vx: number;\n let vy: number;\n if (turbulent || vector === null) {\n vx = Math.cos(p.age / 12 + p.x) * 0.7;\n vy = Math.sin(p.age / 14 + p.y) * 0.7;\n } else {\n const wobble =\n Math.sin(p.y / 55 + now / 1400) * 0.35 + Math.cos(p.x / 70 - now / 1700) * 0.25;\n vx = vector.x - wobble * 0.25 * vector.y;\n vy = vector.y + wobble * 0.25 * vector.x;\n }\n p.x += vx * speed * dt * motion;\n p.y += vy * speed * dt * motion;\n p.age++;\n ctx.moveTo(fromX, fromY);\n ctx.lineTo(p.x, p.y);\n if (p.x < 0 || p.x > this.width || p.y < 0 || p.y > this.height || p.age > p.life) {\n Object.assign(p, this.newParticle(), { age: 0 });\n }\n }\n ctx.stroke();\n }\n\n private drawClouds(\n dt: number,\n vector: { x: number; y: number } | null,\n speedKt: number,\n motion: number,\n ): void {\n const ctx = this.fx;\n const cover = this.state.clouds.total;\n const precipitation = this.state.precipitation;\n const dark = clamp(\n (precipitation?.intensity ?? 0) * 0.6 + (this.state.thunderstorm?.overhead ? 0.4 : 0),\n );\n if (cover <= 0) return;\n\n for (const cloud of this.clouds) {\n if (vector) {\n cloud.x += vector.x * speedKt * 1.4 * cloud.layer * dt * motion;\n cloud.y += vector.y * speedKt * 1.4 * cloud.layer * dt * motion;\n }\n if (cloud.x < -250) cloud.x = this.width + 250;\n if (cloud.x > this.width + 250) cloud.x = -250;\n if (cloud.y < -150) cloud.y = this.height + 150;\n if (cloud.y > this.height + 150) cloud.y = -150;\n const visible = clamp((cover * 1.15 - cloud.threshold) * 4);\n if (visible <= 0) continue;\n ctx.globalAlpha = visible * (0.38 + 0.3 * cover) * (1 - dark * 0.15);\n for (const puff of cloud.puffs) {\n ctx.drawImage(\n this.puff,\n cloud.x + puff.dx - puff.r,\n cloud.y + puff.dy - puff.r,\n puff.r * 2,\n puff.r * 2,\n );\n }\n }\n ctx.globalAlpha = 1;\n if (dark > 0 || cover > 0.6) {\n ctx.fillStyle = `rgba(38,48,62,${(cover * 0.2 + dark * 0.32).toFixed(3)})`;\n ctx.fillRect(0, 0, this.width, this.height);\n }\n }\n\n private drawPrecipitation(\n dt: number,\n vector: { x: number; y: number } | null,\n speedKt: number,\n motion: number,\n ): void {\n const precipitation = this.state.precipitation;\n if (!precipitation) return;\n const ctx = this.fx;\n const count = rainCount(this.drops.length, precipitation.intensity);\n const slant = (vector?.x ?? 0) * Math.min(1, speedKt / 25) * 0.5;\n const snow = precipitation.kind === \"snow\";\n const fall = snow ? 0.25 : 1.6;\n\n if (snow) ctx.fillStyle = \"rgba(255,255,255,0.8)\";\n else {\n ctx.strokeStyle = `rgba(150,185,225,${(0.3 + 0.3 * precipitation.intensity).toFixed(2)})`;\n ctx.lineWidth = precipitation.kind === \"drizzle\" ? 0.7 : 1.1;\n ctx.beginPath();\n }\n for (let i = 0; i < count; i++) {\n const drop = this.drops[i];\n if (!drop) continue;\n drop.y += drop.speed * dt * fall * motion;\n drop.x += slant * dt * fall * motion;\n if (drop.y > 1) {\n drop.y = -0.05;\n drop.x = Math.random();\n }\n if (drop.x > 1) drop.x = 0;\n if (drop.x < 0) drop.x = 1;\n const x = drop.x * this.width;\n const y = drop.y * this.height;\n if (snow) ctx.fillRect(x, y, 2, 2);\n else {\n const length = 12 * drop.len;\n ctx.moveTo(x, y);\n ctx.lineTo(x - slant * length, y - length);\n }\n }\n if (!snow) ctx.stroke();\n }\n\n private drawHaze(now: number, motion: number): void {\n const { visibility, obscuration } = this.state;\n const haze = Math.max(visibility.haze, (obscuration?.density ?? 0) * 0.7);\n if (haze <= 0.01) return;\n const ctx = this.fx;\n const dark = this.theme === \"dark\";\n ctx.fillStyle = `${dark ? \"rgba(150,160,168,\" : \"rgba(214,219,223,\"}${(haze * 0.78).toFixed(3)})`;\n ctx.fillRect(0, 0, this.width, this.height);\n if (obscuration?.kind === \"fog\" || (visibility.meters ?? 10_000) < 1500) {\n for (let i = 0; i < 3; i++) {\n const cx = (((now / 40) * (i + 1) * 0.8 * motion) % (this.width * 1.3)) + i * this.width * 0.2;\n const cy = this.height * (0.3 + i * 0.25);\n const gradient = ctx.createRadialGradient(cx, cy, 10, cx, cy, this.width * 0.5);\n gradient.addColorStop(0, dark ? \"rgba(170,180,188,0.35)\" : \"rgba(235,238,240,0.5)\");\n gradient.addColorStop(1, \"rgba(0,0,0,0)\");\n ctx.fillStyle = gradient;\n ctx.fillRect(0, 0, this.width, this.height);\n }\n }\n }\n\n private drawLightning(now: number, dt: number, perMinute: number): void {\n if (this.reduceMotion) return;\n const ctx = this.fx;\n if (perMinute > 0 && now > this.nextBolt) {\n this.flash = 1;\n this.bolt = this.makeBolt();\n this.nextBolt = now + lightningInterval(perMinute, Math.random());\n }\n if (this.flash <= 0) return;\n ctx.fillStyle = `rgba(255,255,255,${(this.flash * 0.35).toFixed(3)})`;\n ctx.fillRect(0, 0, this.width, this.height);\n if (this.bolt && this.flash > 0.35) {\n ctx.strokeStyle = `rgba(255,255,255,${this.flash.toFixed(2)})`;\n ctx.lineWidth = 2.2;\n ctx.beginPath();\n this.bolt.forEach(([x, y], i) => {\n if (i === 0) ctx.moveTo(x, y);\n else ctx.lineTo(x, y);\n });\n ctx.stroke();\n }\n this.flash -= dt * 3.2;\n }\n\n private makeBolt(): [number, number][] {\n let x = this.width * (0.2 + Math.random() * 0.6);\n let y = 0;\n const points: [number, number][] = [[x, y]];\n const reach = this.height * (0.5 + Math.random() * 0.3);\n while (y < reach) {\n x += (Math.random() - 0.5) * 36;\n y += 14 + Math.random() * 22;\n points.push([x, y]);\n }\n return points;\n }\n}\n","import type { AnimationState } from \"@aeroswarm/weather\";\nimport { WeatherRenderer, type WeatherTheme } from \"./renderer.js\";\n\n/** Bagian peta LuciadRIA yang dipakai overlay. Sengaja struktural supaya tidak bergantung pada RIA. */\nexport interface MapLike {\n readonly domNode: HTMLElement;\n readonly reference?: { readonly identifier: string };\n readonly camera?: {\n asLook2D?: () => { rotation: number };\n asLookAt?: (distance: number) => { yaw: number };\n };\n on(event: \"MapChange\", callback: () => void): { remove(): void };\n}\n\nexport interface WeatherOverlayOptions {\n /** `auto` mengikuti `data-theme` pada <html>, lalu `prefers-color-scheme`. */\n theme?: WeatherTheme | \"auto\";\n zIndex?: number;\n maxFps?: number;\n /** Ganti cara menghitung putaran peta (derajat) bila peta Anda berbeda. */\n getScreenOffset?: (map: MapLike) => number;\n}\n\nexport interface WeatherOverlay {\n readonly renderer: WeatherRenderer;\n setState(state: AnimationState): void;\n setVisible(visible: boolean): void;\n setOpacity(opacity: number): void;\n destroy(): void;\n}\n\n/**\n * Putaran peta terhadap utara, supaya arah angin di layar tetap benar: 2D memakai rotasi kamera,\n * 3D memakai yaw kamera. Keduanya dibalik karena peta berputar berlawanan dengan kamera.\n * Tanda ini perlu dicek di peta sungguhan (putar peta, lihat arah partikel).\n */\nexport function mapScreenOffset(map: MapLike): number {\n const camera = map.camera;\n if (!camera) return 0;\n try {\n if (map.reference?.identifier === \"EPSG:4978\") {\n return -(camera.asLookAt?.(1000).yaw ?? 0);\n }\n return -(camera.asLook2D?.().rotation ?? 0);\n } catch {\n return 0;\n }\n}\n\nfunction detectTheme(): WeatherTheme {\n const explicit = document.documentElement.dataset.theme;\n if (explicit === \"dark\" || explicit === \"light\") return explicit;\n return matchMedia?.(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\n/** Pasang overlay cuaca (canvas transparan) di atas peta; hapus dengan `destroy()`. */\nexport function createWeatherOverlay(\n map: MapLike,\n options: WeatherOverlayOptions = {},\n): WeatherOverlay {\n const container = map.domNode;\n if (getComputedStyle(container).position === \"static\") {\n container.style.position = \"relative\";\n }\n\n const theme = options.theme ?? \"auto\";\n const renderer = new WeatherRenderer({\n container,\n theme: theme === \"auto\" ? detectTheme() : theme,\n zIndex: options.zIndex,\n maxFps: options.maxFps,\n });\n const offsetOf = options.getScreenOffset ?? mapScreenOffset;\n\n let width = container.clientWidth;\n let height = container.clientHeight;\n const handle = map.on(\"MapChange\", () => {\n renderer.setScreenOffset(offsetOf(map));\n if (container.clientWidth !== width || container.clientHeight !== height) {\n width = container.clientWidth;\n height = container.clientHeight;\n renderer.resize();\n }\n });\n\n const themeObserver =\n theme === \"auto\"\n ? new MutationObserver(() => renderer.setTheme(detectTheme()))\n : null;\n themeObserver?.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-theme\"],\n });\n\n renderer.setScreenOffset(offsetOf(map));\n renderer.start();\n\n return {\n renderer,\n setState: (state) => renderer.setState(state),\n setVisible: (visible) => renderer.setVisible(visible),\n setOpacity: (opacity) => renderer.setOpacity(opacity),\n destroy() {\n handle.remove();\n themeObserver?.disconnect();\n renderer.destroy();\n },\n };\n}\n","import type { ScalarGrid } from \"@aeroswarm/weather\";\nimport { CLOUD_RAMP, type RampStop, rampColor } from \"./colorRamp.js\";\nimport { buildScreenScalarField, type Unproject } from \"./screenField.js\";\n\nexport interface ScalarRendererOptions {\n container: HTMLElement;\n unproject: Unproject;\n ramp?: readonly RampStop[];\n zIndex?: number;\n}\n\n/**\n * Peta warna dari grid skalar (tutupan awan, hujan, suhu): tiap sel layar diwarnai dari nilai grid,\n * digambar halus. Digambar ulang hanya saat grid atau peta berubah, bukan tiap frame.\n */\nexport class ScalarRenderer {\n private readonly canvas: HTMLCanvasElement;\n private readonly ctx: CanvasRenderingContext2D;\n private readonly small = document.createElement(\"canvas\");\n private grid: ScalarGrid | null = null;\n private ramp: readonly RampStop[];\n private width = 1;\n private height = 1;\n private dpr = 1;\n\n constructor(private readonly options: ScalarRendererOptions) {\n this.ramp = options.ramp ?? CLOUD_RAMP;\n this.canvas = document.createElement(\"canvas\");\n this.canvas.dataset.weatherLayer = \"scalar\";\n Object.assign(this.canvas.style, {\n position: \"absolute\",\n inset: \"0\",\n width: \"100%\",\n height: \"100%\",\n pointerEvents: \"none\",\n zIndex: String(options.zIndex ?? 3),\n });\n options.container.appendChild(this.canvas);\n const ctx = this.canvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Canvas 2D tidak tersedia\");\n this.ctx = ctx;\n this.resize();\n }\n\n setGrid(grid: ScalarGrid | null, ramp?: readonly RampStop[]): void {\n this.grid = grid;\n if (ramp) this.ramp = ramp;\n this.draw();\n }\n\n resize(): void {\n const rect = this.options.container.getBoundingClientRect();\n this.width = Math.max(1, Math.round(rect.width));\n this.height = Math.max(1, Math.round(rect.height));\n this.dpr = Math.min(2, window.devicePixelRatio || 1);\n this.canvas.width = this.width * this.dpr;\n this.canvas.height = this.height * this.dpr;\n this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);\n this.draw();\n }\n\n /** Gambar ulang setelah peta bergerak atau di-zoom. */\n draw(): void {\n const ctx = this.ctx;\n ctx.clearRect(0, 0, this.width, this.height);\n if (!this.grid) return;\n const field = buildScreenScalarField(this.grid, this.width, this.height, this.options.unproject);\n this.small.width = field.cols;\n this.small.height = field.rows;\n const small = this.small.getContext(\"2d\");\n if (!small) return;\n const image = small.createImageData(field.cols, field.rows);\n for (let i = 0; i < field.values.length; i++) {\n const value = field.values[i] as number;\n if (Number.isNaN(value)) continue;\n const [r, g, b, a] = rampColor(this.ramp, value);\n image.data[i * 4] = r;\n image.data[i * 4 + 1] = g;\n image.data[i * 4 + 2] = b;\n image.data[i * 4 + 3] = Math.round(a * 255);\n }\n small.putImageData(image, 0, 0);\n ctx.imageSmoothingEnabled = true;\n ctx.imageSmoothingQuality = \"high\";\n ctx.drawImage(this.small, 0, 0, field.cols * field.cell, field.rows * field.cell);\n }\n\n setVisible(visible: boolean): void {\n this.canvas.style.display = visible ? \"block\" : \"none\";\n }\n\n setOpacity(opacity: number): void {\n this.canvas.style.opacity = String(Math.min(1, Math.max(0, opacity)));\n }\n\n destroy(): void {\n this.canvas.remove();\n }\n}\n","import type { ScalarGrid, WindGrid } from \"@aeroswarm/weather\";\nimport type { RampStop } from \"./colorRamp.js\";\nimport { FlowRenderer } from \"./flowRenderer.js\";\nimport { type MapLike, mapScreenOffset } from \"./overlay.js\";\nimport { ScalarRenderer } from \"./scalarRenderer.js\";\nimport type { Unproject } from \"./screenField.js\";\n\nexport interface FieldOverlayOptions {\n /** Titik layar ke lon/lat; lihat `createRiaUnproject`. */\n unproject: Unproject;\n zIndex?: number;\n maxFps?: number;\n pixelsPerMs?: number;\n getScreenOffset?: (map: MapLike) => number;\n}\n\nexport interface FieldOverlay {\n /** Aliran partikel angin dari grid; null untuk menghapus. */\n setWind(grid: WindGrid | null): void;\n /** Peta warna dari grid skalar (awan, hujan, suhu); null untuk menghapus. */\n setScalar(grid: ScalarGrid | null, ramp?: readonly RampStop[]): void;\n setVisible(visible: boolean): void;\n setOpacity(opacity: number): void;\n destroy(): void;\n}\n\n/**\n * Overlay medan cuaca ala Windy di atas peta: peta warna (di bawah) dan aliran partikel angin (di\n * atas). Medan dipetakan ulang ke layar tiap peta bergerak (dibatasi ~10 kali per detik).\n */\nexport function createFieldOverlay(map: MapLike, options: FieldOverlayOptions): FieldOverlay {\n const container = map.domNode;\n if (getComputedStyle(container).position === \"static\") container.style.position = \"relative\";\n const offsetOf = options.getScreenOffset ?? mapScreenOffset;\n\n const scalar = new ScalarRenderer({ container, unproject: options.unproject, zIndex: options.zIndex });\n const flow = new FlowRenderer({\n container,\n unproject: options.unproject,\n zIndex: (options.zIndex ?? 3) + 1,\n maxFps: options.maxFps,\n pixelsPerMs: options.pixelsPerMs,\n });\n\n let width = container.clientWidth;\n let height = container.clientHeight;\n let pending: ReturnType<typeof setTimeout> | null = null;\n const refresh = (): void => {\n pending = null;\n if (container.clientWidth !== width || container.clientHeight !== height) {\n width = container.clientWidth;\n height = container.clientHeight;\n scalar.resize();\n flow.resize();\n }\n flow.setScreenOffset(offsetOf(map));\n flow.refreshField();\n scalar.draw();\n };\n const handle = map.on(\"MapChange\", () => {\n if (pending === null) pending = setTimeout(refresh, 100);\n });\n\n flow.setScreenOffset(offsetOf(map));\n flow.start();\n\n return {\n setWind: (grid) => flow.setWind(grid),\n setScalar: (grid, ramp) => scalar.setGrid(grid, ramp),\n setVisible(visible) {\n flow.setVisible(visible);\n scalar.setVisible(visible);\n },\n setOpacity(opacity) {\n flow.setOpacity(opacity);\n scalar.setOpacity(opacity);\n },\n destroy() {\n if (pending !== null) clearTimeout(pending);\n handle.remove();\n flow.destroy();\n scalar.destroy();\n },\n };\n}\n","import type { Unproject } from \"./screenField.js\";\n\n/** Bagian RIA yang dipakai `createRiaUnproject`; diberikan oleh aplikasi (injeksi). */\nexport interface RiaUnprojectDeps {\n /** `getReference(\"EPSG:4326\")`. */\n wgs84: unknown;\n createPoint: (reference: unknown, coordinates: number[]) => unknown;\n createTransformation: (source: unknown, destination: unknown) => {\n transform(point: unknown): { x: number; y: number };\n };\n}\n\ninterface UnprojectableMap {\n readonly reference: unknown;\n readonly viewToMapTransformation: { transform(point: unknown): unknown };\n}\n\n/**\n * Fungsi titik layar -> lon/lat untuk peta RIA 2D maupun 3D. Mengembalikan null untuk titik yang tidak\n * jatuh pada peta (di 3D: di luar bola bumi), jadi aliran partikel berhenti di sana.\n */\nexport function createRiaUnproject(map: UnprojectableMap, deps: RiaUnprojectDeps): Unproject {\n const toWgs84 = deps.createTransformation(map.reference, deps.wgs84);\n return (x, y) => {\n try {\n const mapPoint = map.viewToMapTransformation.transform(deps.createPoint(null, [x, y, 0]));\n const point = toWgs84.transform(mapPoint);\n return Number.isFinite(point.x) && Number.isFinite(point.y) ? { lon: point.x, lat: point.y } : null;\n } catch {\n return null;\n }\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeroswarm/weather-layer",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Animated weather overlay (wind, clouds, rain, fog, lightning) for LuciadRIA maps, driven by @aeroswarm/weather.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "release:major": "release-it major --no-git.requireCleanWorkingDir"
37
37
  },
38
38
  "dependencies": {
39
- "@aeroswarm/weather": "^0.1.0"
39
+ "@aeroswarm/weather": "^0.1.2"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "react": ">=18"