@aeroswarm/weather-layer 0.0.0-stage → 0.1.1
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 +55 -2
- package/dist/index.d.ts +137 -0
- package/dist/index.js +620 -0
- package/dist/index.js.map +1 -0
- package/dist/overlay-U9My4PH5.d.ts +109 -0
- package/dist/react/index.d.ts +15 -0
- package/dist/react/index.js +471 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +55 -6
package/README.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @aeroswarm/weather-layer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Animated weather on a LuciadRIA map: wind particles, clouds, rain/snow, fog and lightning. It takes
|
|
4
|
+
the `AnimationState` produced by [`@aeroswarm/weather`](../../server/weather) (decoded METAR/SPECI/TAF)
|
|
5
|
+
and draws it. No network access.
|
|
6
|
+
|
|
7
|
+
## A. Flat overlay (2D and 3D)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { animationSnapshot } from "@aeroswarm/weather";
|
|
11
|
+
import { createWeatherOverlay } from "@aeroswarm/weather-layer";
|
|
12
|
+
|
|
13
|
+
const overlay = createWeatherOverlay(map); // transparent canvases on map.domNode
|
|
14
|
+
const snapshot = animationSnapshot({ observation: metar, forecast: taf });
|
|
15
|
+
if (snapshot) overlay.setState(snapshot.state);
|
|
16
|
+
|
|
17
|
+
overlay.setVisible(false); // toggle from your layer panel
|
|
18
|
+
overlay.setOpacity(0.8);
|
|
19
|
+
overlay.destroy();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
React:
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { useWeatherOverlay } from "@aeroswarm/weather-layer/react";
|
|
26
|
+
|
|
27
|
+
useWeatherOverlay(map, snapshot?.state, { visible, opacity });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Wind direction follows the map rotation (`mapScreenOffset`; override with `getScreenOffset`).
|
|
31
|
+
The overlay is a canvas above the map, so it is not part of `map.layerTree`: wire its `visible` and
|
|
32
|
+
`opacity` to your own layer panel. Honours `prefers-reduced-motion` (draws a still frame).
|
|
33
|
+
|
|
34
|
+
## B. 3D clouds at real altitude
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { FeatureLayer } from "@mid/ria/view/feature/FeatureLayer";
|
|
38
|
+
import { createCloudLayer3D } from "@aeroswarm/weather-layer";
|
|
39
|
+
|
|
40
|
+
const clouds = createCloudLayer3D(
|
|
41
|
+
{ reference, Feature, FeatureModel, MemoryStore, FeatureLayer, FeaturePainter, createPoint },
|
|
42
|
+
{ center: { lat: -7.3798, lon: 112.7869 }, elevationM: 3 },
|
|
43
|
+
);
|
|
44
|
+
map.layerTree.addChild(clouds.layer);
|
|
45
|
+
clouds.setState(snapshot.state);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Cloud height = aerodrome elevation + cloud base above ground level (METAR reports AGL). Sprites are
|
|
49
|
+
sized in metres, so they scale with zoom and perspective. Drawn only when the map is 3D
|
|
50
|
+
(`EPSG:4978`) unless `only3D: false`. The RIA classes are passed in so this package never bundles
|
|
51
|
+
its own copy of RIA.
|
|
52
|
+
|
|
53
|
+
## Limits
|
|
54
|
+
|
|
55
|
+
METAR and TAF describe one point (the aerodrome). The animation is a representation around the
|
|
56
|
+
airport, not the real position of clouds or rain. There is no terrain or land mask yet.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
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';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Kelas dan fungsi LuciadRIA yang dipakai layer awan. Diberikan oleh pemakai (injeksi) supaya package
|
|
6
|
+
* ini tidak punya dependensi ke RIA dan memakai salinan RIA milik aplikasi yang sama dengan petanya.
|
|
7
|
+
*/
|
|
8
|
+
interface RiaCloudDeps {
|
|
9
|
+
/** Referensi EPSG:4326, mis. `getReference("EPSG:4326")`. */
|
|
10
|
+
reference: unknown;
|
|
11
|
+
Feature: new (shape: unknown, properties: Record<string, unknown>, id?: string) => unknown;
|
|
12
|
+
FeatureModel: new (store: unknown, options: {
|
|
13
|
+
reference: unknown;
|
|
14
|
+
}) => CloudModel;
|
|
15
|
+
MemoryStore: new () => unknown;
|
|
16
|
+
FeatureLayer: new (model: CloudModel, options: Record<string, unknown>) => CloudFeatureLayer;
|
|
17
|
+
FeaturePainter: new () => CloudPainter;
|
|
18
|
+
createPoint: (reference: unknown, coordinates: number[]) => unknown;
|
|
19
|
+
}
|
|
20
|
+
interface CloudModel {
|
|
21
|
+
put(feature: unknown): unknown;
|
|
22
|
+
remove(id: string): unknown;
|
|
23
|
+
}
|
|
24
|
+
interface CloudFeatureLayer {
|
|
25
|
+
visible: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface CloudPainter {
|
|
28
|
+
getDetailLevelScales?: () => number[];
|
|
29
|
+
paintBody: (geoCanvas: {
|
|
30
|
+
drawIcon(shape: unknown, style: Record<string, unknown>): void;
|
|
31
|
+
}, feature: {
|
|
32
|
+
properties: Record<string, unknown>;
|
|
33
|
+
}, shape: unknown, layer: unknown, map: {
|
|
34
|
+
reference: {
|
|
35
|
+
identifier: string;
|
|
36
|
+
};
|
|
37
|
+
}) => void;
|
|
38
|
+
}
|
|
39
|
+
interface CloudLayerOptions {
|
|
40
|
+
/** Pusat sebaran awan (bandara). */
|
|
41
|
+
center: {
|
|
42
|
+
lat: number;
|
|
43
|
+
lon: number;
|
|
44
|
+
};
|
|
45
|
+
/** Elevasi bandara (m di atas laut), ditambahkan ke tinggi dasar awan AGL. Bawaan 0. */
|
|
46
|
+
elevationM?: number;
|
|
47
|
+
radiusM?: number;
|
|
48
|
+
/** Awan hanya digambar di peta 3D (EPSG:4978). Bawaan true. */
|
|
49
|
+
only3D?: boolean;
|
|
50
|
+
/** Percepatan gerak awan. Bawaan 12. */
|
|
51
|
+
timeScale?: number;
|
|
52
|
+
/** Selang pembaruan posisi (ms). Bawaan 500. */
|
|
53
|
+
updateIntervalMs?: number;
|
|
54
|
+
id?: string;
|
|
55
|
+
}
|
|
56
|
+
interface CloudLayer3D {
|
|
57
|
+
readonly layer: CloudFeatureLayer;
|
|
58
|
+
/** Ganti kondisi cuaca: awan dibangun ulang dari lapisan awan METAR/TAF. */
|
|
59
|
+
setState(state: AnimationState): void;
|
|
60
|
+
setVisible(visible: boolean): void;
|
|
61
|
+
destroy(): void;
|
|
62
|
+
}
|
|
63
|
+
/** Sprite gumpalan awan sebagai data URL (gradien radial lembut). */
|
|
64
|
+
declare function makeCloudSpriteUrl(): string;
|
|
65
|
+
/**
|
|
66
|
+
* Layer RIA berisi gumpalan awan di ketinggian sebenarnya (elevasi bandara + tinggi dasar AGL),
|
|
67
|
+
* berukuran dalam meter sehingga ikut zoom dan perspektif 3D. Tambahkan ke peta dengan
|
|
68
|
+
* `map.layerTree.addChild(cloud.layer)`.
|
|
69
|
+
*/
|
|
70
|
+
declare function createCloudLayer3D(ria: RiaCloudDeps, options: CloudLayerOptions): CloudLayer3D;
|
|
71
|
+
|
|
72
|
+
/** Satu gumpalan awan di sekitar bandara, dalam meter dari titik pusat. */
|
|
73
|
+
interface CloudPuff3D {
|
|
74
|
+
id: string;
|
|
75
|
+
eastM: number;
|
|
76
|
+
northM: number;
|
|
77
|
+
/** Ketinggian di atas permukaan laut (m): elevasi bandara + tinggi dasar awan (AGL). */
|
|
78
|
+
altM: number;
|
|
79
|
+
sizeM: number;
|
|
80
|
+
opacity: number;
|
|
81
|
+
convective: boolean;
|
|
82
|
+
}
|
|
83
|
+
interface CloudFieldOptions {
|
|
84
|
+
/** Radius sebaran awan dari bandara (m). Bawaan 12.000. */
|
|
85
|
+
radiusM?: number;
|
|
86
|
+
/** Elevasi bandara (m di atas laut). METAR memberi tinggi awan AGL, jadi ini ditambahkan. */
|
|
87
|
+
elevationM?: number;
|
|
88
|
+
/** Biji acak supaya letak awan konsisten antar render. */
|
|
89
|
+
seed?: number;
|
|
90
|
+
}
|
|
91
|
+
/** Pembangkit acak kecil (mulberry32) agar medan awan deterministik. */
|
|
92
|
+
declare function seededRandom(seed: number): () => number;
|
|
93
|
+
/**
|
|
94
|
+
* Sebar gumpalan awan di sekitar bandara dari lapisan awan METAR/TAF. Banyaknya menurut tutupan,
|
|
95
|
+
* ketinggiannya = elevasi bandara + tinggi dasar (AGL). Awan konvektif lebih besar dan lebih padat.
|
|
96
|
+
*/
|
|
97
|
+
declare function buildCloudField(state: AnimationState, options?: CloudFieldOptions): CloudPuff3D[];
|
|
98
|
+
/**
|
|
99
|
+
* Geser awan searah angin (`toDeg`, derajat dari utara). `timeScale` mempercepat gerak supaya
|
|
100
|
+
* terlihat (1 = kecepatan nyata, yang nyaris tidak terlihat dari ketinggian peta). Awan yang
|
|
101
|
+
* keluar dari radius muncul lagi di sisi seberang.
|
|
102
|
+
*/
|
|
103
|
+
declare function advanceCloudField(field: readonly CloudPuff3D[], wind: {
|
|
104
|
+
toDeg: number | null;
|
|
105
|
+
speedKt: number;
|
|
106
|
+
}, dtSeconds: number, options?: {
|
|
107
|
+
radiusM?: number;
|
|
108
|
+
timeScale?: number;
|
|
109
|
+
}): CloudPuff3D[];
|
|
110
|
+
/** Titik lat/lon dari selisih meter timur/utara terhadap pusat (pendekatan datar, cukup untuk ~20 km). */
|
|
111
|
+
declare function offsetToLonLat(centerLat: number, centerLon: number, eastM: number, northM: number): {
|
|
112
|
+
lat: number;
|
|
113
|
+
lon: number;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** Hitungan murni untuk renderer: tanpa DOM supaya mudah dites. */
|
|
117
|
+
/**
|
|
118
|
+
* Vektor gerak di layar (x ke kanan, y ke bawah) untuk arah `toDeg` (derajat dari utara, searah jarum
|
|
119
|
+
* jam). `screenOffsetDeg` = putaran peta: arah di layar = arah geografis + offset.
|
|
120
|
+
*/
|
|
121
|
+
declare function screenVector(toDeg: number, screenOffsetDeg?: number): {
|
|
122
|
+
x: number;
|
|
123
|
+
y: number;
|
|
124
|
+
};
|
|
125
|
+
declare const clamp: (value: number, min?: number, max?: number) => number;
|
|
126
|
+
/** Jumlah partikel angin menurut luas layar, dibatasi `max`. */
|
|
127
|
+
declare function windParticleCount(width: number, height: number, max?: number): number;
|
|
128
|
+
/** Jumlah garis hujan yang digambar dari `pool` tetes, menurut intensitas 0-1. */
|
|
129
|
+
declare function rainCount(pool: number, intensity: number): number;
|
|
130
|
+
/** Kecepatan visual partikel (px/detik) dari kecepatan angin (kt) dan hembusan sesaat. */
|
|
131
|
+
declare function particleSpeed(speedKt: number, gustKt?: number): number;
|
|
132
|
+
/** Hembusan sesaat (kt tambahan) yang berdenyut halus sesuai `gustiness` 0-1. */
|
|
133
|
+
declare function gustPulse(gustiness: number, timeMs: number): number;
|
|
134
|
+
/** Selang antar kilat (ms) dari laju kilat per menit, dengan sedikit acak (`random` 0-1). */
|
|
135
|
+
declare function lightningInterval(perMinute: number, random: number): number;
|
|
136
|
+
|
|
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 };
|