@smartnet360/svelte-components 0.0.54 → 0.0.56

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.
Files changed (80) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +2 -2
  3. package/dist/map-v2/core/controls/MapStyleControl.svelte +289 -0
  4. package/dist/map-v2/core/controls/MapStyleControl.svelte.d.ts +24 -0
  5. package/dist/{map → map-v2/core}/hooks/useMapbox.d.ts +1 -1
  6. package/dist/{map → map-v2/core}/hooks/useMapbox.js +1 -1
  7. package/dist/map-v2/core/index.d.ts +11 -0
  8. package/dist/map-v2/core/index.js +14 -0
  9. package/dist/map-v2/core/providers/MapboxProvider.svelte +140 -0
  10. package/dist/map-v2/core/providers/MapboxProvider.svelte.d.ts +33 -0
  11. package/dist/{map → map-v2/core}/stores/mapStore.d.ts +2 -2
  12. package/dist/{map → map-v2/core}/stores/mapStore.js +2 -2
  13. package/dist/map-v2/core/types.d.ts +13 -0
  14. package/dist/map-v2/core/types.js +7 -0
  15. package/dist/map-v2/demo/DemoMap.svelte +63 -0
  16. package/dist/{map → map-v2}/demo/DemoMap.svelte.d.ts +3 -4
  17. package/dist/map-v2/demo/demo-data.d.ts +8 -0
  18. package/dist/map-v2/demo/demo-data.js +128 -0
  19. package/dist/map-v2/demo/index.d.ts +7 -0
  20. package/dist/map-v2/demo/index.js +9 -0
  21. package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +147 -0
  22. package/dist/{map → map-v2/features/sites}/controls/SiteFilterControl.svelte.d.ts +4 -6
  23. package/dist/map-v2/features/sites/controls/SiteSizeSlider.svelte +236 -0
  24. package/dist/map-v2/features/sites/controls/SiteSizeSlider.svelte.d.ts +20 -0
  25. package/dist/map-v2/features/sites/index.d.ts +14 -0
  26. package/dist/map-v2/features/sites/index.js +16 -0
  27. package/dist/map-v2/features/sites/layers/SitesLayer.svelte +294 -0
  28. package/dist/map-v2/features/sites/layers/SitesLayer.svelte.d.ts +12 -0
  29. package/dist/map-v2/features/sites/stores/siteStore.d.ts +18 -0
  30. package/dist/map-v2/features/sites/stores/siteStore.js +36 -0
  31. package/dist/map-v2/features/sites/stores/siteStoreContext.svelte.d.ts +36 -0
  32. package/dist/map-v2/features/sites/stores/siteStoreContext.svelte.js +155 -0
  33. package/dist/map-v2/features/sites/types.d.ts +39 -0
  34. package/dist/map-v2/features/sites/types.js +4 -0
  35. package/dist/map-v2/features/sites/utils/siteGeoJSON.d.ts +33 -0
  36. package/dist/map-v2/features/sites/utils/siteGeoJSON.js +43 -0
  37. package/dist/map-v2/features/sites/utils/siteTreeUtils.d.ts +16 -0
  38. package/dist/{map → map-v2/features/sites}/utils/siteTreeUtils.js +9 -52
  39. package/dist/map-v2/index.d.ts +10 -0
  40. package/dist/map-v2/index.js +22 -0
  41. package/dist/{map → map-v2/shared}/controls/MapControl.svelte +1 -1
  42. package/dist/map-v2/shared/index.d.ts +7 -0
  43. package/dist/map-v2/shared/index.js +9 -0
  44. package/package.json +1 -1
  45. package/dist/map/controls/SiteFilterControl.svelte +0 -126
  46. package/dist/map/demo/DemoMap.svelte +0 -98
  47. package/dist/map/demo/demo-data.d.ts +0 -12
  48. package/dist/map/demo/demo-data.js +0 -220
  49. package/dist/map/hooks/useCellData.d.ts +0 -14
  50. package/dist/map/hooks/useCellData.js +0 -29
  51. package/dist/map/index.d.ts +0 -27
  52. package/dist/map/index.js +0 -47
  53. package/dist/map/layers/CellsLayer.svelte +0 -242
  54. package/dist/map/layers/CellsLayer.svelte.d.ts +0 -21
  55. package/dist/map/layers/CoverageLayer.svelte +0 -37
  56. package/dist/map/layers/CoverageLayer.svelte.d.ts +0 -9
  57. package/dist/map/layers/LayerBase.d.ts +0 -42
  58. package/dist/map/layers/LayerBase.js +0 -58
  59. package/dist/map/layers/SitesLayer.svelte +0 -282
  60. package/dist/map/layers/SitesLayer.svelte.d.ts +0 -19
  61. package/dist/map/providers/CellDataProvider.svelte +0 -43
  62. package/dist/map/providers/CellDataProvider.svelte.d.ts +0 -12
  63. package/dist/map/providers/MapboxProvider.svelte +0 -38
  64. package/dist/map/providers/MapboxProvider.svelte.d.ts +0 -9
  65. package/dist/map/providers/providerHelpers.d.ts +0 -17
  66. package/dist/map/providers/providerHelpers.js +0 -26
  67. package/dist/map/stores/cellDataStore.d.ts +0 -21
  68. package/dist/map/stores/cellDataStore.js +0 -53
  69. package/dist/map/stores/interactions.d.ts +0 -20
  70. package/dist/map/stores/interactions.js +0 -33
  71. package/dist/map/types.d.ts +0 -115
  72. package/dist/map/types.js +0 -10
  73. package/dist/map/utils/geojson.d.ts +0 -20
  74. package/dist/map/utils/geojson.js +0 -78
  75. package/dist/map/utils/math.d.ts +0 -40
  76. package/dist/map/utils/math.js +0 -95
  77. package/dist/map/utils/siteTreeUtils.d.ts +0 -27
  78. /package/dist/{map → map-v2/shared}/controls/MapControl.svelte.d.ts +0 -0
  79. /package/dist/{map → map-v2/shared}/utils/mapboxHelpers.d.ts +0 -0
  80. /package/dist/{map → map-v2/shared}/utils/mapboxHelpers.js +0 -0
@@ -0,0 +1,294 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SitesLayer - Visualizes cellular sites as circles on the map
4
+ *
5
+ * Reads from siteStore to get filtered sites and visual properties.
6
+ * Completely independent from cell data.
7
+ *
8
+ * Features:
9
+ * - Displays sites as circles with configurable size, color, opacity
10
+ * - Supports labels (optional)
11
+ * - Click to show popup
12
+ * - Hover effects
13
+ */
14
+ import { onMount, onDestroy } from 'svelte';
15
+ import mapboxgl from 'mapbox-gl';
16
+ import { useMapbox } from '../../../core/hooks/useMapbox';
17
+ import { sitesToGeoJSON } from '../utils/siteGeoJSON';
18
+ import {
19
+ addSourceIfMissing,
20
+ addLayerIfMissing,
21
+ updateGeoJSONSource,
22
+ removeLayerAndSource,
23
+ generateLayerId,
24
+ generateSourceId
25
+ } from '../../../shared/utils/mapboxHelpers';
26
+ import type { SiteStoreContext } from '../stores/siteStoreContext.svelte';
27
+
28
+ interface Props {
29
+ /** Site store instance */
30
+ store: SiteStoreContext;
31
+ /** Namespace for layer IDs (default: 'sites') */
32
+ namespace?: string;
33
+ /** Enable click to show popup (default: true) */
34
+ clickable?: boolean;
35
+ }
36
+
37
+ let {
38
+ store,
39
+ namespace = 'sites',
40
+ clickable = true
41
+ }: Props = $props();
42
+
43
+ const mapStore = useMapbox();
44
+
45
+ let map: mapboxgl.Map | null = null;
46
+ let popup: mapboxgl.Popup | null = null;
47
+ let hoveredSiteId: string | null = null;
48
+
49
+ const layerId = generateLayerId(namespace, 'circles');
50
+ const labelLayerId = generateLayerId(namespace, 'labels');
51
+ const sourceId = generateSourceId(namespace, 'source');
52
+
53
+ // Watch store changes and update layer
54
+ $effect(() => {
55
+ // Access store properties to track them
56
+ const filteredSites = store.filteredSites;
57
+ const size = store.size;
58
+ const color = store.color;
59
+ const opacity = store.opacity;
60
+ const showLabels = store.showLabels;
61
+ const labelSize = store.labelSize;
62
+ const labelColor = store.labelColor;
63
+ const labelOffset = store.labelOffset;
64
+ const labelProperty = store.labelProperty;
65
+ const groupColorMap = store.groupColorMap; // Track color map changes
66
+
67
+ if (map) {
68
+ updateLayer();
69
+ }
70
+ });
71
+
72
+ onMount(() => {
73
+ const mapUnsub = mapStore.subscribe((m: mapboxgl.Map | null) => {
74
+ if (!m) return;
75
+ map = m;
76
+ // Map is guaranteed to be ready (style loaded) by MapboxProvider
77
+ initializeLayer();
78
+
79
+ // Re-initialize layer when map style changes
80
+ // Mapbox removes all custom layers/sources when setStyle() is called
81
+ map.on('style.load', initializeLayer);
82
+ });
83
+
84
+ return () => {
85
+ mapUnsub();
86
+ cleanup();
87
+ };
88
+ });
89
+
90
+ onDestroy(() => {
91
+ cleanup();
92
+ });
93
+
94
+ function initializeLayer(): void {
95
+ if (!map) return;
96
+
97
+ // Add empty GeoJSON source
98
+ addSourceIfMissing(map, sourceId, {
99
+ type: 'geojson',
100
+ data: {
101
+ type: 'FeatureCollection',
102
+ features: []
103
+ }
104
+ });
105
+
106
+ // Add circle layer with current store values
107
+ addLayerIfMissing(map, {
108
+ id: layerId,
109
+ type: 'circle',
110
+ source: sourceId,
111
+ paint: {
112
+ 'circle-radius': store.size,
113
+ 'circle-color': [
114
+ 'coalesce',
115
+ ['get', 'groupColor'], // Use group-specific color if available
116
+ store.color // Fallback to global color
117
+ ],
118
+ 'circle-opacity': store.opacity,
119
+ 'circle-stroke-width': 2,
120
+ 'circle-stroke-color': [
121
+ 'coalesce',
122
+ ['get', 'groupColor'], // Use group-specific color if available
123
+ store.color // Fallback to global color
124
+ ],
125
+ 'circle-stroke-opacity': store.opacity
126
+ }
127
+ });
128
+
129
+ // Add label layer
130
+ addLayerIfMissing(map, {
131
+ id: labelLayerId,
132
+ type: 'symbol',
133
+ source: sourceId,
134
+ layout: {
135
+ 'text-field': ['get', store.labelProperty],
136
+ 'text-size': store.labelSize,
137
+ 'text-offset': [0, store.labelOffset],
138
+ 'text-anchor': 'top',
139
+ 'text-font': ['Open Sans Regular', 'Arial Unicode MS Regular']
140
+ },
141
+ paint: {
142
+ 'text-color': store.labelColor,
143
+ 'text-halo-color': '#fff',
144
+ 'text-halo-width': 1.5
145
+ }
146
+ });
147
+
148
+ // Set initial visibility
149
+ map.setLayoutProperty(labelLayerId, 'visibility', store.showLabels ? 'visible' : 'none');
150
+
151
+ // Add event handlers
152
+ if (clickable) {
153
+ map.on('click', layerId, handleClick);
154
+ map.on('mouseenter', layerId, handleMouseEnter);
155
+ map.on('mouseleave', layerId, handleMouseLeave);
156
+ }
157
+
158
+ // Initial data load
159
+ updateLayer();
160
+ }
161
+
162
+ function updateLayer(): void {
163
+ if (!map) return;
164
+
165
+ // Update data with color information
166
+ const geojson = sitesToGeoJSON(store.filteredSites, store.groupColorMap);
167
+ updateGeoJSONSource(map, sourceId, geojson);
168
+
169
+ // Update circle visual properties
170
+ map.setPaintProperty(layerId, 'circle-radius', store.size);
171
+ map.setPaintProperty(layerId, 'circle-color', [
172
+ 'coalesce',
173
+ ['get', 'groupColor'],
174
+ store.color
175
+ ]);
176
+ map.setPaintProperty(layerId, 'circle-opacity', store.opacity);
177
+ map.setPaintProperty(layerId, 'circle-stroke-opacity', store.opacity);
178
+
179
+ // Update label properties
180
+ map.setLayoutProperty(labelLayerId, 'text-field', ['get', store.labelProperty]);
181
+ map.setLayoutProperty(labelLayerId, 'text-size', store.labelSize);
182
+ map.setLayoutProperty(labelLayerId, 'text-offset', [0, store.labelOffset]);
183
+ map.setPaintProperty(labelLayerId, 'text-color', store.labelColor);
184
+ map.setLayoutProperty(labelLayerId, 'visibility', store.showLabels ? 'visible' : 'none');
185
+ }
186
+
187
+ function handleClick(e: mapboxgl.MapLayerMouseEvent): void {
188
+ if (!e.features || e.features.length === 0) return;
189
+ const feature = e.features[0];
190
+ const siteProps = feature.properties;
191
+ if (siteProps && e.lngLat) {
192
+ createSitePopup(siteProps, e.lngLat);
193
+ }
194
+ }
195
+
196
+ function createSitePopup(siteProps: any, lngLat: mapboxgl.LngLat): void {
197
+ if (!map) return;
198
+
199
+ // Remove existing popup
200
+ if (popup) {
201
+ popup.remove();
202
+ }
203
+
204
+ // Parse arrays if needed
205
+ const fbands = siteProps.fbands ? JSON.parse(siteProps.fbands) : [];
206
+ const cellNames = siteProps.cellNames ? JSON.parse(siteProps.cellNames) : [];
207
+
208
+ const fbandsHtml = fbands.length > 0
209
+ ? `<div><strong>Bands:</strong> ${fbands.join(', ')} MHz</div>`
210
+ : '';
211
+
212
+ const html = `
213
+ <div style="font-family: system-ui, -apple-system, sans-serif; font-size: 13px;">
214
+ <h6 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 600;">${siteProps.name}</h6>
215
+ <div style="color: #666; line-height: 1.6;">
216
+ <div><strong>ID:</strong> ${siteProps.id}</div>
217
+ <div><strong>Technology:</strong> <span class="badge bg-primary">${siteProps.technology}</span></div>
218
+ ${fbandsHtml}
219
+ <div><strong>Provider:</strong> ${siteProps.provider}</div>
220
+ <div><strong>Group:</strong> ${siteProps.featureGroup}</div>
221
+ <div><strong>Cells:</strong> ${cellNames.length}</div>
222
+ <div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #eee; font-size: 11px;">
223
+ <strong>Coordinates:</strong><br/>
224
+ ${Number(siteProps.latitude).toFixed(6)}, ${Number(siteProps.longitude).toFixed(6)}
225
+ </div>
226
+ </div>
227
+ </div>
228
+ `;
229
+
230
+ // Create and add popup
231
+ popup = new mapboxgl.Popup({
232
+ closeButton: true,
233
+ closeOnClick: false,
234
+ maxWidth: '300px'
235
+ })
236
+ .setLngLat(lngLat)
237
+ .setHTML(html)
238
+ .addTo(map);
239
+ }
240
+
241
+ function handleMouseEnter(e: mapboxgl.MapLayerMouseEvent): void {
242
+ if (!map) return;
243
+ map.getCanvas().style.cursor = 'pointer';
244
+
245
+ if (!e.features || e.features.length === 0) return;
246
+ const feature = e.features[0];
247
+ const siteId = feature.properties?.id;
248
+
249
+ if (siteId) {
250
+ hoveredSiteId = siteId;
251
+ // Optional: Add hover effect via feature state
252
+ map.setFeatureState(
253
+ { source: sourceId, id: siteId },
254
+ { hovered: true }
255
+ );
256
+ }
257
+ }
258
+
259
+ function handleMouseLeave(): void {
260
+ if (!map) return;
261
+ map.getCanvas().style.cursor = '';
262
+
263
+ if (hoveredSiteId) {
264
+ // Remove hover state
265
+ map.removeFeatureState(
266
+ { source: sourceId, id: hoveredSiteId },
267
+ 'hovered'
268
+ );
269
+ hoveredSiteId = null;
270
+ }
271
+ }
272
+
273
+ function cleanup(): void {
274
+ if (popup) {
275
+ popup.remove();
276
+ popup = null;
277
+ }
278
+
279
+ if (map) {
280
+ // Remove style.load listener
281
+ map.off('style.load', initializeLayer);
282
+
283
+ if (clickable) {
284
+ map.off('click', layerId, handleClick);
285
+ map.off('mouseenter', layerId, handleMouseEnter);
286
+ map.off('mouseleave', layerId, handleMouseLeave);
287
+ }
288
+
289
+ // Remove layers and source
290
+ removeLayerAndSource(map, labelLayerId, sourceId);
291
+ removeLayerAndSource(map, layerId, sourceId);
292
+ }
293
+ }
294
+ </script>
@@ -0,0 +1,12 @@
1
+ import type { SiteStoreContext } from '../stores/siteStoreContext.svelte';
2
+ interface Props {
3
+ /** Site store instance */
4
+ store: SiteStoreContext;
5
+ /** Namespace for layer IDs (default: 'sites') */
6
+ namespace?: string;
7
+ /** Enable click to show popup (default: true) */
8
+ clickable?: boolean;
9
+ }
10
+ declare const SitesLayer: import("svelte").Component<Props, {}, "">;
11
+ type SitesLayer = ReturnType<typeof SitesLayer>;
12
+ export default SitesLayer;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Site store - State management for site feature
3
+ */
4
+ import type { Writable } from 'svelte/store';
5
+ import type { Site, SiteStoreValue } from '../types';
6
+ /**
7
+ * Creates a site store with default values
8
+ */
9
+ export declare function createSiteStore(initialSites?: Site[]): Writable<SiteStoreValue> & {
10
+ setAllSites: (sites: Site[]) => void;
11
+ setFilteredSites: (sites: Site[]) => void;
12
+ setSize: (size: number) => void;
13
+ setColor: (color: string) => void;
14
+ setOpacity: (opacity: number) => void;
15
+ setShowLabels: (show: boolean) => void;
16
+ reset: () => void;
17
+ };
18
+ export type SiteStore = ReturnType<typeof createSiteStore>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Site store - State management for site feature
3
+ */
4
+ import { writable } from 'svelte/store';
5
+ /**
6
+ * Creates a site store with default values
7
+ */
8
+ export function createSiteStore(initialSites = []) {
9
+ const defaultState = {
10
+ allSites: initialSites,
11
+ filteredSites: initialSites,
12
+ size: 10,
13
+ color: '#3b82f6',
14
+ opacity: 1.0,
15
+ showLabels: false,
16
+ showOnHover: true,
17
+ strokeWidth: 2,
18
+ strokeColor: '#ffffff'
19
+ };
20
+ const { subscribe, update, set } = writable(defaultState);
21
+ return {
22
+ subscribe,
23
+ set,
24
+ update,
25
+ // Data setters
26
+ setAllSites: (sites) => update((s) => ({ ...s, allSites: sites })),
27
+ setFilteredSites: (sites) => update((s) => ({ ...s, filteredSites: sites })),
28
+ // Visual property setters
29
+ setSize: (size) => update((s) => ({ ...s, size })),
30
+ setColor: (color) => update((s) => ({ ...s, color })),
31
+ setOpacity: (opacity) => update((s) => ({ ...s, opacity })),
32
+ setShowLabels: (show) => update((s) => ({ ...s, showLabels: show })),
33
+ // Reset to defaults
34
+ reset: () => set({ ...defaultState, allSites: initialSites, filteredSites: initialSites })
35
+ };
36
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Site Store Context - Bindable reactive state for site feature
3
+ *
4
+ * Uses Svelte 5 runes ($state) to create a directly bindable reactive object.
5
+ * This allows components to use bind:value instead of manual event handlers.
6
+ * Persists visual settings to localStorage.
7
+ */
8
+ import type { Site, SiteStoreValue } from '../types';
9
+ export declare function createSiteStoreContext(initialSites?: Site[]): {
10
+ allSites: Site[];
11
+ filteredSites: Site[];
12
+ size: number;
13
+ color: string;
14
+ opacity: number;
15
+ showLabels: boolean;
16
+ showOnHover: boolean;
17
+ labelSize: number;
18
+ labelColor: string;
19
+ labelOffset: number;
20
+ labelProperty: string;
21
+ strokeWidth: number;
22
+ strokeColor: string;
23
+ groupColorMap: Map<string, string>;
24
+ setAllSites(sites: Site[]): void;
25
+ setFilteredSites(sites: Site[]): void;
26
+ setSize(size: number): void;
27
+ setColor(color: string): void;
28
+ setOpacity(opacity: number): void;
29
+ setShowLabels(show: boolean): void;
30
+ getGroupColor(groupKey: string): string | undefined;
31
+ setGroupColor(groupKey: string, color: string): void;
32
+ clearGroupColor(groupKey: string): void;
33
+ reset(): void;
34
+ getState(): SiteStoreValue;
35
+ };
36
+ export type SiteStoreContext = ReturnType<typeof createSiteStoreContext>;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Site Store Context - Bindable reactive state for site feature
3
+ *
4
+ * Uses Svelte 5 runes ($state) to create a directly bindable reactive object.
5
+ * This allows components to use bind:value instead of manual event handlers.
6
+ * Persists visual settings to localStorage.
7
+ */
8
+ const STORAGE_KEY = 'siteVisualSettings';
9
+ function loadSettings() {
10
+ if (typeof window === 'undefined')
11
+ return {};
12
+ try {
13
+ const saved = localStorage.getItem(STORAGE_KEY);
14
+ if (saved) {
15
+ return JSON.parse(saved);
16
+ }
17
+ }
18
+ catch (error) {
19
+ console.warn('Failed to load site settings from localStorage:', error);
20
+ }
21
+ return {};
22
+ }
23
+ function saveSettings(settings) {
24
+ if (typeof window === 'undefined')
25
+ return;
26
+ try {
27
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
28
+ }
29
+ catch (error) {
30
+ console.warn('Failed to save site settings to localStorage:', error);
31
+ }
32
+ }
33
+ export function createSiteStoreContext(initialSites = []) {
34
+ // Load persisted settings
35
+ const persistedSettings = loadSettings();
36
+ // Convert persisted groupColors object back to Map
37
+ const initialColorMap = new Map();
38
+ if (persistedSettings.groupColors) {
39
+ Object.entries(persistedSettings.groupColors).forEach(([key, value]) => {
40
+ initialColorMap.set(key, value);
41
+ });
42
+ }
43
+ // Internal reactive state
44
+ let state = $state({
45
+ allSites: initialSites,
46
+ filteredSites: initialSites,
47
+ size: persistedSettings.size ?? 10,
48
+ color: persistedSettings.color ?? '#3b82f6',
49
+ opacity: persistedSettings.opacity ?? 1.0,
50
+ showLabels: persistedSettings.showLabels ?? false,
51
+ showOnHover: true,
52
+ labelSize: persistedSettings.labelSize ?? 12,
53
+ labelColor: persistedSettings.labelColor ?? '#000000',
54
+ labelOffset: persistedSettings.labelOffset ?? 1.5,
55
+ labelProperty: persistedSettings.labelProperty ?? 'name',
56
+ groupColorMap: initialColorMap,
57
+ strokeWidth: persistedSettings.strokeWidth ?? 2,
58
+ strokeColor: persistedSettings.strokeColor ?? '#ffffff'
59
+ });
60
+ // Auto-save settings when they change
61
+ $effect(() => {
62
+ // Convert Map to plain object for serialization
63
+ const groupColorsObj = {};
64
+ state.groupColorMap.forEach((color, key) => {
65
+ groupColorsObj[key] = color;
66
+ });
67
+ const settings = {
68
+ size: state.size,
69
+ color: state.color,
70
+ opacity: state.opacity,
71
+ showLabels: state.showLabels,
72
+ labelSize: state.labelSize,
73
+ labelColor: state.labelColor,
74
+ labelOffset: state.labelOffset,
75
+ labelProperty: state.labelProperty,
76
+ strokeWidth: state.strokeWidth ?? 2,
77
+ strokeColor: state.strokeColor ?? '#ffffff',
78
+ groupColors: groupColorsObj
79
+ };
80
+ saveSettings(settings);
81
+ });
82
+ // Return object with getters/setters for direct binding
83
+ return {
84
+ // Bindable properties with getters/setters
85
+ get allSites() { return state.allSites; },
86
+ set allSites(value) { state.allSites = value; },
87
+ get filteredSites() { return state.filteredSites; },
88
+ set filteredSites(value) { state.filteredSites = value; },
89
+ get size() { return state.size; },
90
+ set size(value) { state.size = value; },
91
+ get color() { return state.color; },
92
+ set color(value) { state.color = value; },
93
+ get opacity() { return state.opacity; },
94
+ set opacity(value) { state.opacity = value; },
95
+ get showLabels() { return state.showLabels; },
96
+ set showLabels(value) { state.showLabels = value; },
97
+ get showOnHover() { return state.showOnHover; },
98
+ set showOnHover(value) { state.showOnHover = value; },
99
+ get labelSize() { return state.labelSize; },
100
+ set labelSize(value) { state.labelSize = value; },
101
+ get labelColor() { return state.labelColor; },
102
+ set labelColor(value) { state.labelColor = value; },
103
+ get labelOffset() { return state.labelOffset; },
104
+ set labelOffset(value) { state.labelOffset = value; },
105
+ get labelProperty() { return state.labelProperty; },
106
+ set labelProperty(value) { state.labelProperty = value; },
107
+ get strokeWidth() { return state.strokeWidth ?? 2; },
108
+ set strokeWidth(value) { state.strokeWidth = value; },
109
+ get strokeColor() { return state.strokeColor ?? '#ffffff'; },
110
+ set strokeColor(value) { state.strokeColor = value; },
111
+ get groupColorMap() { return state.groupColorMap; },
112
+ set groupColorMap(value) { state.groupColorMap = value; },
113
+ // Convenience methods (optional, but nice to have)
114
+ setAllSites(sites) { state.allSites = sites; },
115
+ setFilteredSites(sites) { state.filteredSites = sites; },
116
+ setSize(size) { state.size = size; },
117
+ setColor(color) { state.color = color; },
118
+ setOpacity(opacity) { state.opacity = opacity; },
119
+ setShowLabels(show) { state.showLabels = show; },
120
+ // Group color methods
121
+ getGroupColor(groupKey) {
122
+ return state.groupColorMap.get(groupKey);
123
+ },
124
+ setGroupColor(groupKey, color) {
125
+ state.groupColorMap.set(groupKey, color);
126
+ // Trigger reactivity by reassigning
127
+ state.groupColorMap = new Map(state.groupColorMap);
128
+ },
129
+ clearGroupColor(groupKey) {
130
+ state.groupColorMap.delete(groupKey);
131
+ state.groupColorMap = new Map(state.groupColorMap);
132
+ },
133
+ // Reset to defaults
134
+ reset() {
135
+ state.allSites = initialSites;
136
+ state.filteredSites = initialSites;
137
+ state.size = 10;
138
+ state.color = '#3b82f6';
139
+ state.opacity = 1.0;
140
+ state.showLabels = false;
141
+ state.showOnHover = true;
142
+ state.labelSize = 12;
143
+ state.labelColor = '#000000';
144
+ state.labelOffset = 1.5;
145
+ state.labelProperty = 'name';
146
+ state.strokeWidth = 2;
147
+ state.strokeColor = '#ffffff';
148
+ state.groupColorMap = new Map();
149
+ },
150
+ // Get snapshot of current state (useful for debugging)
151
+ getState() {
152
+ return { ...state };
153
+ }
154
+ };
155
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Site feature types
3
+ */
4
+ /**
5
+ * Cellular site/tower
6
+ */
7
+ export interface Site {
8
+ id: string;
9
+ name: string;
10
+ latitude: number;
11
+ longitude: number;
12
+ fbands?: string[];
13
+ technology: string;
14
+ properties: Record<string, any>;
15
+ cellNames: string[];
16
+ provider: string;
17
+ featureGroup: string;
18
+ }
19
+ /**
20
+ * Site store state - contains all site data and visual properties
21
+ */
22
+ export interface SiteStoreValue {
23
+ allSites: Site[];
24
+ filteredSites: Site[];
25
+ size: number;
26
+ color: string;
27
+ opacity: number;
28
+ showLabels: boolean;
29
+ showOnHover: boolean;
30
+ labelSize: number;
31
+ labelColor: string;
32
+ labelOffset: number;
33
+ labelProperty: string;
34
+ groupColorMap: Map<string, string>;
35
+ hoverColor?: string;
36
+ selectedColor?: string;
37
+ strokeWidth?: number;
38
+ strokeColor?: string;
39
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Site feature types
3
+ */
4
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Site GeoJSON utilities
3
+ */
4
+ import type { Site } from '../types';
5
+ /**
6
+ * GeoJSON Feature for a site (Point geometry)
7
+ */
8
+ export interface SiteFeature {
9
+ type: 'Feature';
10
+ geometry: {
11
+ type: 'Point';
12
+ coordinates: [number, number];
13
+ };
14
+ properties: Site;
15
+ }
16
+ /**
17
+ * GeoJSON FeatureCollection
18
+ */
19
+ export interface FeatureCollection<T> {
20
+ type: 'FeatureCollection';
21
+ features: T[];
22
+ }
23
+ /**
24
+ * Converts an array of sites to a GeoJSON FeatureCollection
25
+ * Sites are represented as Point features (circles will be drawn by Mapbox)
26
+ * @param sites - Array of sites to convert
27
+ * @param colorMap - Optional map of group keys (provider:featureGroup) to colors
28
+ */
29
+ export declare function sitesToGeoJSON(sites: Site[], colorMap?: Map<string, string>): FeatureCollection<SiteFeature>;
30
+ /**
31
+ * Converts a single site to a GeoJSON Feature
32
+ */
33
+ export declare function siteToFeature(site: Site): SiteFeature;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Site GeoJSON utilities
3
+ */
4
+ /**
5
+ * Converts an array of sites to a GeoJSON FeatureCollection
6
+ * Sites are represented as Point features (circles will be drawn by Mapbox)
7
+ * @param sites - Array of sites to convert
8
+ * @param colorMap - Optional map of group keys (provider:featureGroup) to colors
9
+ */
10
+ export function sitesToGeoJSON(sites, colorMap) {
11
+ const features = sites.map((site) => {
12
+ const groupKey = `${site.provider}:${site.featureGroup}`;
13
+ const groupColor = colorMap?.get(groupKey);
14
+ return {
15
+ type: 'Feature',
16
+ geometry: {
17
+ type: 'Point',
18
+ coordinates: [site.longitude, site.latitude]
19
+ },
20
+ properties: {
21
+ ...site,
22
+ groupColor // Add groupColor property for Mapbox styling
23
+ }
24
+ };
25
+ });
26
+ return {
27
+ type: 'FeatureCollection',
28
+ features
29
+ };
30
+ }
31
+ /**
32
+ * Converts a single site to a GeoJSON Feature
33
+ */
34
+ export function siteToFeature(site) {
35
+ return {
36
+ type: 'Feature',
37
+ geometry: {
38
+ type: 'Point',
39
+ coordinates: [site.longitude, site.latitude]
40
+ },
41
+ properties: site
42
+ };
43
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Utilities for building and managing site filter tree
3
+ */
4
+ import type { Site } from '../types';
5
+ import type { TreeNode } from '../../../../core/TreeView/tree.model';
6
+ /**
7
+ * Builds a hierarchical tree from flat site array
8
+ * Structure: All Sites -> Provider -> Feature Group
9
+ * @param sites - Array of sites to build tree from
10
+ * @param colorMap - Optional map of group keys (provider:featureGroup) to colors
11
+ */
12
+ export declare function buildSiteTree(sites: Site[], colorMap?: Map<string, string>): TreeNode;
13
+ /**
14
+ * Filters sites based on checked tree paths
15
+ */
16
+ export declare function getFilteredSites(checkedPaths: string[], allSites: Site[]): Site[];