@aguacerowx/mapsgl 0.0.58 → 0.0.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,148 +1,148 @@
1
- /**
2
- * GeoJSON markers for all NEXRAD sites (picker mode). Aligned with aguacero-frontend NexradSitesLayer (simplified).
3
- */
4
- const SOURCE_ID = 'aguacero-nexrad-sites-src';
5
- const CIRCLE_LAYER_ID = 'aguacero-nexrad-sites-circles';
6
-
7
- export class NexradSitesOverlay {
8
- constructor(map, options = {}) {
9
- this.map = map;
10
- /** Same path as aguacero-frontend: serve `public/data/nexrad.json` (e.g. Vite → `/data/nexrad.json`). */
11
- this.sitesUrl = options.nexradSitesUrl || '/data/nexrad.json';
12
- this._onClick = null;
13
- /** Coalesces overlapping `show()` calls (e.g. NEXRAD timestamp slider firing many state updates). */
14
- this._showPromise = null;
15
- this._destroyed = false;
16
- }
17
-
18
- _layersPresent() {
19
- try {
20
- return Boolean(this.map.getSource(SOURCE_ID) && this.map.getLayer(CIRCLE_LAYER_ID));
21
- } catch {
22
- return false;
23
- }
24
- }
25
-
26
- async show() {
27
- if (this._layersPresent()) {
28
- return;
29
- }
30
- if (this._showPromise) {
31
- return this._showPromise;
32
- }
33
-
34
- this._showPromise = this._loadAndMountSites();
35
- try {
36
- await this._showPromise;
37
- } finally {
38
- this._showPromise = null;
39
- }
40
- }
41
-
42
- async _loadAndMountSites() {
43
- if (this._layersPresent()) {
44
- return;
45
- }
46
- await this.hide();
47
- let res;
48
- try {
49
- res = await fetch(this.sitesUrl);
50
- } catch (e) {
51
- console.warn('[NexradSitesOverlay] Failed to load site list:', e);
52
- return;
53
- }
54
- if (!res.ok) {
55
- console.warn('[NexradSitesOverlay] HTTP', res.status, this.sitesUrl);
56
- return;
57
- }
58
- if (this._destroyed) {
59
- return;
60
- }
61
- const payload = await res.json();
62
- const raw = Array.isArray(payload)
63
- ? payload
64
- : payload?.features
65
- ? payload.features
66
- : payload?.sites
67
- ? payload.sites
68
- : [];
69
- const features = [];
70
- for (const item of raw) {
71
- if (!item) continue;
72
- const coords = item.geometry?.coordinates
73
- ? item.geometry.coordinates
74
- : [item.lon ?? item.lng ?? item.longitude, item.lat ?? item.latitude];
75
- const lon = Number(coords[0]);
76
- const lat = Number(coords[1]);
77
- const id = String(item.id ?? item.properties?.id ?? item.station ?? '').toUpperCase();
78
- if (!id || !Number.isFinite(lat) || !Number.isFinite(lon)) continue;
79
- features.push({
80
- type: 'Feature',
81
- geometry: { type: 'Point', coordinates: [lon, lat] },
82
- properties: { name: id },
83
- });
84
- }
85
- if (this._destroyed) {
86
- return;
87
- }
88
- this.map.addSource(SOURCE_ID, {
89
- type: 'geojson',
90
- data: { type: 'FeatureCollection', features },
91
- });
92
- this.map.addLayer({
93
- id: CIRCLE_LAYER_ID,
94
- type: 'circle',
95
- source: SOURCE_ID,
96
- paint: {
97
- 'circle-radius': 5,
98
- 'circle-color': '#3b82f6',
99
- 'circle-stroke-width': 1,
100
- 'circle-stroke-color': '#ffffff',
101
- },
102
- });
103
- }
104
-
105
- hide() {
106
- try {
107
- if (this.map.getLayer(CIRCLE_LAYER_ID)) this.map.removeLayer(CIRCLE_LAYER_ID);
108
- if (this.map.getSource(SOURCE_ID)) this.map.removeSource(SOURCE_ID);
109
- } catch {
110
- /* ignore */
111
- }
112
- }
113
-
114
- /**
115
- * @param {(siteId: string) => void} handler
116
- */
117
- bindClick(handler) {
118
- this.unbindClick();
119
- this._onClick = (e) => {
120
- const f = e.features?.[0];
121
- const id = f?.properties?.name;
122
- if (id) handler(String(id));
123
- };
124
- this.map.on('click', CIRCLE_LAYER_ID, this._onClick);
125
- this.map.on('mouseenter', CIRCLE_LAYER_ID, () => {
126
- this.map.getCanvas().style.cursor = 'pointer';
127
- });
128
- this.map.on('mouseleave', CIRCLE_LAYER_ID, () => {
129
- this.map.getCanvas().style.cursor = '';
130
- });
131
- }
132
-
133
- unbindClick() {
134
- if (!this._onClick) return;
135
- try {
136
- this.map.off('click', CIRCLE_LAYER_ID, this._onClick);
137
- } catch {
138
- /* ignore */
139
- }
140
- this._onClick = null;
141
- }
142
-
143
- destroy() {
144
- this._destroyed = true;
145
- this.unbindClick();
146
- this.hide();
147
- }
148
- }
1
+ /**
2
+ * GeoJSON markers for all NEXRAD sites (picker mode). Aligned with aguacero-frontend NexradSitesLayer (simplified).
3
+ */
4
+ const SOURCE_ID = 'aguacero-nexrad-sites-src';
5
+ const CIRCLE_LAYER_ID = 'aguacero-nexrad-sites-circles';
6
+
7
+ export class NexradSitesOverlay {
8
+ constructor(map, options = {}) {
9
+ this.map = map;
10
+ /** Same path as aguacero-frontend: serve `public/data/nexrad.json` (e.g. Vite → `/data/nexrad.json`). */
11
+ this.sitesUrl = options.nexradSitesUrl || '/data/nexrad.json';
12
+ this._onClick = null;
13
+ /** Coalesces overlapping `show()` calls (e.g. NEXRAD timestamp slider firing many state updates). */
14
+ this._showPromise = null;
15
+ this._destroyed = false;
16
+ }
17
+
18
+ _layersPresent() {
19
+ try {
20
+ return Boolean(this.map.getSource(SOURCE_ID) && this.map.getLayer(CIRCLE_LAYER_ID));
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+
26
+ async show() {
27
+ if (this._layersPresent()) {
28
+ return;
29
+ }
30
+ if (this._showPromise) {
31
+ return this._showPromise;
32
+ }
33
+
34
+ this._showPromise = this._loadAndMountSites();
35
+ try {
36
+ await this._showPromise;
37
+ } finally {
38
+ this._showPromise = null;
39
+ }
40
+ }
41
+
42
+ async _loadAndMountSites() {
43
+ if (this._layersPresent()) {
44
+ return;
45
+ }
46
+ await this.hide();
47
+ let res;
48
+ try {
49
+ res = await fetch(this.sitesUrl);
50
+ } catch (e) {
51
+ console.warn('[NexradSitesOverlay] Failed to load site list:', e);
52
+ return;
53
+ }
54
+ if (!res.ok) {
55
+ console.warn('[NexradSitesOverlay] HTTP', res.status, this.sitesUrl);
56
+ return;
57
+ }
58
+ if (this._destroyed) {
59
+ return;
60
+ }
61
+ const payload = await res.json();
62
+ const raw = Array.isArray(payload)
63
+ ? payload
64
+ : payload?.features
65
+ ? payload.features
66
+ : payload?.sites
67
+ ? payload.sites
68
+ : [];
69
+ const features = [];
70
+ for (const item of raw) {
71
+ if (!item) continue;
72
+ const coords = item.geometry?.coordinates
73
+ ? item.geometry.coordinates
74
+ : [item.lon ?? item.lng ?? item.longitude, item.lat ?? item.latitude];
75
+ const lon = Number(coords[0]);
76
+ const lat = Number(coords[1]);
77
+ const id = String(item.id ?? item.properties?.id ?? item.station ?? '').toUpperCase();
78
+ if (!id || !Number.isFinite(lat) || !Number.isFinite(lon)) continue;
79
+ features.push({
80
+ type: 'Feature',
81
+ geometry: { type: 'Point', coordinates: [lon, lat] },
82
+ properties: { name: id },
83
+ });
84
+ }
85
+ if (this._destroyed) {
86
+ return;
87
+ }
88
+ this.map.addSource(SOURCE_ID, {
89
+ type: 'geojson',
90
+ data: { type: 'FeatureCollection', features },
91
+ });
92
+ this.map.addLayer({
93
+ id: CIRCLE_LAYER_ID,
94
+ type: 'circle',
95
+ source: SOURCE_ID,
96
+ paint: {
97
+ 'circle-radius': 5,
98
+ 'circle-color': '#3b82f6',
99
+ 'circle-stroke-width': 1,
100
+ 'circle-stroke-color': '#ffffff',
101
+ },
102
+ });
103
+ }
104
+
105
+ hide() {
106
+ try {
107
+ if (this.map.getLayer(CIRCLE_LAYER_ID)) this.map.removeLayer(CIRCLE_LAYER_ID);
108
+ if (this.map.getSource(SOURCE_ID)) this.map.removeSource(SOURCE_ID);
109
+ } catch {
110
+ /* ignore */
111
+ }
112
+ }
113
+
114
+ /**
115
+ * @param {(siteId: string) => void} handler
116
+ */
117
+ bindClick(handler) {
118
+ this.unbindClick();
119
+ this._onClick = (e) => {
120
+ const f = e.features?.[0];
121
+ const id = f?.properties?.name;
122
+ if (id) handler(String(id));
123
+ };
124
+ this.map.on('click', CIRCLE_LAYER_ID, this._onClick);
125
+ this.map.on('mouseenter', CIRCLE_LAYER_ID, () => {
126
+ this.map.getCanvas().style.cursor = 'pointer';
127
+ });
128
+ this.map.on('mouseleave', CIRCLE_LAYER_ID, () => {
129
+ this.map.getCanvas().style.cursor = '';
130
+ });
131
+ }
132
+
133
+ unbindClick() {
134
+ if (!this._onClick) return;
135
+ try {
136
+ this.map.off('click', CIRCLE_LAYER_ID, this._onClick);
137
+ } catch {
138
+ /* ignore */
139
+ }
140
+ this._onClick = null;
141
+ }
142
+
143
+ destroy() {
144
+ this._destroyed = true;
145
+ this.unbindClick();
146
+ this.hide();
147
+ }
148
+ }