@smartnet360/svelte-components 0.0.85 → 0.0.87

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 (115) hide show
  1. package/dist/apps/antenna-pattern/components/AntennaControls.svelte +1 -106
  2. package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte +0 -36
  3. package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte +0 -2
  4. package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte +0 -22
  5. package/dist/apps/antenna-pattern/components/chart-engines/PolarAreaChart.svelte +0 -2
  6. package/dist/apps/antenna-pattern/components/chart-engines/PolarBarChart.svelte +0 -2
  7. package/dist/apps/antenna-pattern/components/chart-engines/PolarLineChart.svelte +0 -2
  8. package/dist/apps/site-check/data-loader.js +0 -8
  9. package/dist/core/Charts/GlobalControls.svelte +0 -4
  10. package/dist/core/Desktop/Grid/ResizeHandle.svelte +0 -7
  11. package/dist/core/Desktop/Grid/resizeStore.js +0 -1
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.js +2 -0
  14. package/dist/map-v2/demo/DemoMap.svelte +0 -2
  15. package/dist/map-v2/demo/demo-cells.js +0 -1
  16. package/dist/map-v2/features/cells/layers/CellsLayer.svelte +7 -26
  17. package/dist/map-v2/features/cells/utils/cellTree.js +0 -29
  18. package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte +3 -27
  19. package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +8 -25
  20. package/dist/map-v2/features/repeaters/utils/repeaterTree.js +0 -6
  21. package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +0 -8
  22. package/dist/map-v2/features/sites/utils/siteTreeUtils.js +0 -6
  23. package/dist/map-v3/core/components/Map.svelte +89 -0
  24. package/dist/map-v3/core/components/Map.svelte.d.ts +13 -0
  25. package/dist/map-v3/core/controls/FeatureSettingsControl.svelte +103 -0
  26. package/dist/map-v3/core/controls/FeatureSettingsControl.svelte.d.ts +15 -0
  27. package/dist/map-v3/core/controls/MapStyleControl.svelte +271 -0
  28. package/dist/map-v3/core/controls/MapStyleControl.svelte.d.ts +28 -0
  29. package/dist/map-v3/core/index.d.ts +6 -0
  30. package/dist/map-v3/core/index.js +5 -0
  31. package/dist/map-v3/core/stores/map.store.svelte.d.ts +8 -0
  32. package/dist/map-v3/core/stores/map.store.svelte.js +29 -0
  33. package/dist/map-v3/core/stores/viewport.store.svelte.d.ts +38 -0
  34. package/dist/map-v3/core/stores/viewport.store.svelte.js +107 -0
  35. package/dist/map-v3/demo/DemoMap.svelte +104 -0
  36. package/dist/map-v3/demo/DemoMap.svelte.d.ts +6 -0
  37. package/dist/map-v3/demo/demo-cells.d.ts +13 -0
  38. package/dist/map-v3/demo/demo-cells.js +130 -0
  39. package/dist/map-v3/demo/demo-data.d.ts +8 -0
  40. package/dist/map-v3/demo/demo-data.js +104 -0
  41. package/dist/map-v3/demo/demo-repeaters.d.ts +13 -0
  42. package/dist/map-v3/demo/demo-repeaters.js +73 -0
  43. package/dist/map-v3/features/cells/components/CellFilterControl.svelte +208 -0
  44. package/dist/map-v3/features/cells/components/CellFilterControl.svelte.d.ts +12 -0
  45. package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte +229 -0
  46. package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte.d.ts +7 -0
  47. package/dist/map-v3/features/cells/constants.d.ts +18 -0
  48. package/dist/map-v3/features/cells/constants.js +37 -0
  49. package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte +230 -0
  50. package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte.d.ts +11 -0
  51. package/dist/map-v3/features/cells/layers/CellsLayer.svelte +194 -0
  52. package/dist/map-v3/features/cells/layers/CellsLayer.svelte.d.ts +11 -0
  53. package/dist/map-v3/features/cells/layers/index.d.ts +2 -0
  54. package/dist/map-v3/features/cells/layers/index.js +2 -0
  55. package/dist/map-v3/features/cells/logic/geometry.d.ts +12 -0
  56. package/dist/map-v3/features/cells/logic/geometry.js +35 -0
  57. package/dist/map-v3/features/cells/logic/grouping.d.ts +18 -0
  58. package/dist/map-v3/features/cells/logic/grouping.js +30 -0
  59. package/dist/map-v3/features/cells/logic/tree-adapter.d.ts +11 -0
  60. package/dist/map-v3/features/cells/logic/tree-adapter.js +53 -0
  61. package/dist/map-v3/features/cells/stores/cell.data.svelte.d.ts +9 -0
  62. package/dist/map-v3/features/cells/stores/cell.data.svelte.js +16 -0
  63. package/dist/map-v3/features/cells/stores/cell.display.svelte.d.ts +25 -0
  64. package/dist/map-v3/features/cells/stores/cell.display.svelte.js +67 -0
  65. package/dist/map-v3/features/cells/stores/cell.registry.svelte.d.ts +23 -0
  66. package/dist/map-v3/features/cells/stores/cell.registry.svelte.js +68 -0
  67. package/dist/map-v3/features/cells/types.d.ts +62 -0
  68. package/dist/map-v3/features/cells/types.js +6 -0
  69. package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +148 -0
  70. package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte.d.ts +12 -0
  71. package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte +209 -0
  72. package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte.d.ts +7 -0
  73. package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte +177 -0
  74. package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte.d.ts +11 -0
  75. package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte +163 -0
  76. package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte.d.ts +11 -0
  77. package/dist/map-v3/features/repeaters/logic/geometry.d.ts +3 -0
  78. package/dist/map-v3/features/repeaters/logic/geometry.js +23 -0
  79. package/dist/map-v3/features/repeaters/logic/grouping.d.ts +8 -0
  80. package/dist/map-v3/features/repeaters/logic/grouping.js +20 -0
  81. package/dist/map-v3/features/repeaters/logic/tree-adapter.d.ts +8 -0
  82. package/dist/map-v3/features/repeaters/logic/tree-adapter.js +43 -0
  83. package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.d.ts +8 -0
  84. package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.js +13 -0
  85. package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.d.ts +21 -0
  86. package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.js +64 -0
  87. package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.d.ts +23 -0
  88. package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.js +68 -0
  89. package/dist/map-v3/features/repeaters/types.d.ts +18 -0
  90. package/dist/map-v3/features/repeaters/types.js +1 -0
  91. package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +119 -0
  92. package/dist/map-v3/features/sites/components/SiteFilterControl.svelte.d.ts +12 -0
  93. package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte +241 -0
  94. package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte.d.ts +7 -0
  95. package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte +152 -0
  96. package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte.d.ts +11 -0
  97. package/dist/map-v3/features/sites/layers/SitesLayer.svelte +132 -0
  98. package/dist/map-v3/features/sites/layers/SitesLayer.svelte.d.ts +11 -0
  99. package/dist/map-v3/features/sites/logic/tree-adapter.d.ts +9 -0
  100. package/dist/map-v3/features/sites/logic/tree-adapter.js +75 -0
  101. package/dist/map-v3/features/sites/stores/site.data.svelte.d.ts +8 -0
  102. package/dist/map-v3/features/sites/stores/site.data.svelte.js +40 -0
  103. package/dist/map-v3/features/sites/stores/site.display.svelte.d.ts +20 -0
  104. package/dist/map-v3/features/sites/stores/site.display.svelte.js +63 -0
  105. package/dist/map-v3/features/sites/stores/site.registry.svelte.d.ts +13 -0
  106. package/dist/map-v3/features/sites/stores/site.registry.svelte.js +83 -0
  107. package/dist/map-v3/features/sites/types.d.ts +12 -0
  108. package/dist/map-v3/features/sites/types.js +1 -0
  109. package/dist/map-v3/index.d.ts +30 -0
  110. package/dist/map-v3/index.js +36 -0
  111. package/dist/map-v3/shared/controls/MapControl.svelte +242 -0
  112. package/dist/map-v3/shared/controls/MapControl.svelte.d.ts +27 -0
  113. package/dist/map-v3/shared/index.d.ts +1 -0
  114. package/dist/map-v3/shared/index.js +1 -0
  115. package/package.json +1 -1
@@ -0,0 +1,194 @@
1
+ <script lang="ts">
2
+ import { getContext, onMount, onDestroy } from 'svelte';
3
+ import type { MapStore } from '../../../core/stores/map.store.svelte';
4
+ import type { CellDataStore } from '../stores/cell.data.svelte';
5
+ import type { CellRegistry } from '../stores/cell.registry.svelte';
6
+ import type { CellDisplayStore } from '../stores/cell.display.svelte';
7
+ import { groupCells, getColorForGroup } from '../logic/grouping';
8
+ import { generateCellArc, calculateRadiusInMeters } from '../logic/geometry';
9
+ import { Z_INDEX_BY_BAND } from '../constants';
10
+ import type { TechnologyBandKey } from '../types';
11
+ import type mapboxgl from 'mapbox-gl';
12
+
13
+ interface Props {
14
+ dataStore: CellDataStore;
15
+ registry: CellRegistry;
16
+ displayStore: CellDisplayStore;
17
+ }
18
+
19
+ let { dataStore, registry, displayStore }: Props = $props();
20
+
21
+ const mapStore = getContext<MapStore>('MAP_CONTEXT');
22
+ let sourceId = 'cells-source';
23
+ let layerId = 'cells-layer';
24
+ let lineLayerId = 'cells-line-layer';
25
+
26
+ // Debounce timer
27
+ let updateTimeout: any;
28
+
29
+ // Update paint properties when display settings change
30
+ $effect(() => {
31
+ const map = mapStore.map;
32
+ if (!map) return;
33
+
34
+ if (map.getLayer(layerId)) {
35
+ map.setPaintProperty(layerId, 'fill-opacity', displayStore.fillOpacity);
36
+ }
37
+ if (map.getLayer(lineLayerId)) {
38
+ map.setPaintProperty(lineLayerId, 'line-width', displayStore.lineWidth);
39
+ }
40
+ });
41
+
42
+ $effect(() => {
43
+ const map = mapStore.map;
44
+ if (!map) return;
45
+
46
+ const addLayers = () => {
47
+ if (!map.getSource(sourceId)) {
48
+ map.addSource(sourceId, {
49
+ type: 'geojson',
50
+ data: { type: 'FeatureCollection', features: [] }
51
+ });
52
+ }
53
+
54
+ if (!map.getLayer(layerId)) {
55
+ // Fill Layer
56
+ map.addLayer({
57
+ id: layerId,
58
+ type: 'fill',
59
+ source: sourceId,
60
+ paint: {
61
+ 'fill-color': ['get', 'color'],
62
+ 'fill-opacity': displayStore.fillOpacity
63
+ },
64
+ layout: {
65
+ 'fill-sort-key': ['get', 'zIndex']
66
+ }
67
+ });
68
+ }
69
+
70
+ if (!map.getLayer(lineLayerId)) {
71
+ // Line Layer (Border)
72
+ map.addLayer({
73
+ id: lineLayerId,
74
+ type: 'line',
75
+ source: sourceId,
76
+ paint: {
77
+ 'line-color': '#000',
78
+ 'line-width': displayStore.lineWidth,
79
+ 'line-opacity': 0.3
80
+ }
81
+ });
82
+ }
83
+
84
+ // Force update to populate data
85
+ updateLayer();
86
+ };
87
+
88
+
89
+ // Initial setup
90
+ addLayers();
91
+
92
+ // Events for updating
93
+ map.on('style.load', addLayers);
94
+ map.on('moveend', updateLayer);
95
+ map.on('zoomend', updateLayer);
96
+
97
+ // Cleanup
98
+ return () => {
99
+ map.off('style.load', addLayers);
100
+ map.off('moveend', updateLayer);
101
+ map.off('zoomend', updateLayer);
102
+
103
+ if (map.getLayer(lineLayerId)) map.removeLayer(lineLayerId);
104
+ if (map.getLayer(layerId)) map.removeLayer(layerId);
105
+ if (map.getSource(sourceId)) map.removeSource(sourceId);
106
+ };
107
+ });
108
+
109
+ // React to data changes or display setting changes
110
+ $effect(() => {
111
+ // Explicitly read dependencies to ensure reactivity
112
+ // Note: We must read them before the short-circuit check or passing to function
113
+ const _cells = dataStore.filteredCells;
114
+ const _pixelSize = displayStore.targetPixelSize;
115
+ const _registryVersion = registry.version;
116
+ const _l1 = displayStore.level1;
117
+ const _l2 = displayStore.level2;
118
+
119
+ updateLayer();
120
+ });
121
+
122
+ function updateLayer() {
123
+ const map = mapStore.map;
124
+ if (!map) return;
125
+
126
+ clearTimeout(updateTimeout);
127
+ updateTimeout = setTimeout(() => {
128
+ renderCells(map);
129
+ }, 100); // Debounce 100ms
130
+ }
131
+
132
+ function renderCells(map: mapboxgl.Map) {
133
+ const bounds = map.getBounds();
134
+ if (!bounds) return;
135
+
136
+ const zoom = map.getZoom();
137
+ const centerLat = map.getCenter().lat;
138
+
139
+ console.log(`[CellsLayer] Rendering.. Zoom: ${zoom.toFixed(2)}, Cells: ${dataStore.filteredCells.length}`);
140
+
141
+ // 1. Calculate Radius for this zoom
142
+ const radiusMeters = calculateRadiusInMeters(centerLat, zoom, displayStore.targetPixelSize);
143
+ console.log(`[CellsLayer] Radius: ${radiusMeters.toFixed(2)}m for target ${displayStore.targetPixelSize}px`);
144
+
145
+ // 2. Group cells (Level 1=Tech, Level 2=Band for now hardcoded)
146
+ // In real app, this comes from a store
147
+ const groups = groupCells(dataStore.filteredCells, displayStore.level1, displayStore.level2);
148
+ console.log(`[CellsLayer] Groups: ${groups.size}`);
149
+
150
+ const features: GeoJSON.Feature[] = [];
151
+ let groupIndex = 0;
152
+
153
+ // 3. Iterate groups and generate features
154
+ for (const [groupId, cells] of groups) {
155
+ // Get style from registry
156
+ const defaultColor = getColorForGroup(groupIndex++);
157
+ const style = registry.getStyle(groupId, defaultColor);
158
+
159
+ if (!style.visible) continue;
160
+
161
+ for (const cell of cells) {
162
+ // 4. BBox Filter (Simple point check)
163
+ if (bounds.contains([cell.longitude, cell.latitude])) {
164
+ // 5. Z-Index Lookup
165
+ const zIndexKey = `${cell.tech}_${cell.frq}` as TechnologyBandKey; // Approx key
166
+ const zIndex = Z_INDEX_BY_BAND[zIndexKey] || 10;
167
+
168
+ // 6. Calculate Scaled Radius based on Z-Index
169
+ // Higher Z-index (Top layer) = Smaller radius
170
+ // Lower Z-index (Bottom layer) = Larger radius
171
+ // This ensures stacked cells are visible
172
+ const MAX_Z = 15;
173
+ const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08; // 8% size diff per layer
174
+ const effectiveRadius = radiusMeters * scaleFactor;
175
+
176
+ // 7. Generate Arc
177
+ const feature = generateCellArc(cell, effectiveRadius, zIndex, style.color);
178
+ features.push(feature);
179
+ }
180
+ }
181
+ }
182
+
183
+ console.log(`[CellsLayer] Generated ${features.length} features in view`);
184
+
185
+ // 8. Update Source
186
+ const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource;
187
+ if (source) {
188
+ source.setData({
189
+ type: 'FeatureCollection',
190
+ features: features as any
191
+ });
192
+ }
193
+ }
194
+ </script>
@@ -0,0 +1,11 @@
1
+ import type { CellDataStore } from '../stores/cell.data.svelte';
2
+ import type { CellRegistry } from '../stores/cell.registry.svelte';
3
+ import type { CellDisplayStore } from '../stores/cell.display.svelte';
4
+ interface Props {
5
+ dataStore: CellDataStore;
6
+ registry: CellRegistry;
7
+ displayStore: CellDisplayStore;
8
+ }
9
+ declare const CellsLayer: import("svelte").Component<Props, {}, "">;
10
+ type CellsLayer = ReturnType<typeof CellsLayer>;
11
+ export default CellsLayer;
@@ -0,0 +1,2 @@
1
+ export { default as CellsLayer } from './CellsLayer.svelte';
2
+ export { default as CellLabelsLayer } from './CellLabelsLayer.svelte';
@@ -0,0 +1,2 @@
1
+ export { default as CellsLayer } from './CellsLayer.svelte';
2
+ export { default as CellLabelsLayer } from './CellLabelsLayer.svelte';
@@ -0,0 +1,12 @@
1
+ import type { Cell } from '../types';
2
+ /**
3
+ * Calculates the radius in meters required to achieve a target pixel size
4
+ * at a specific latitude and zoom level.
5
+ *
6
+ * Formula: meters_per_pixel = 156543.03392 * cos(lat * PI / 180) / 2^zoom
7
+ */
8
+ export declare function calculateRadiusInMeters(latitude: number, zoom: number, targetPixelSize: number): number;
9
+ /**
10
+ * Generates a sector arc GeoJSON feature for a cell
11
+ */
12
+ export declare function generateCellArc(cell: Cell, radiusMeters: number, zIndex: number, color: string): GeoJSON.Feature<GeoJSON.Polygon>;
@@ -0,0 +1,35 @@
1
+ import * as turf from '@turf/turf';
2
+ /**
3
+ * Calculates the radius in meters required to achieve a target pixel size
4
+ * at a specific latitude and zoom level.
5
+ *
6
+ * Formula: meters_per_pixel = 156543.03392 * cos(lat * PI / 180) / 2^zoom
7
+ */
8
+ export function calculateRadiusInMeters(latitude, zoom, targetPixelSize) {
9
+ const metersPerPixel = (156543.03392 * Math.cos((latitude * Math.PI) / 180)) / Math.pow(2, zoom);
10
+ return targetPixelSize * metersPerPixel;
11
+ }
12
+ /**
13
+ * Generates a sector arc GeoJSON feature for a cell
14
+ */
15
+ export function generateCellArc(cell, radiusMeters, zIndex, color) {
16
+ const center = [cell.longitude, cell.latitude];
17
+ const bearing1 = cell.azimuth - (cell.beamwidth / 2);
18
+ const bearing2 = cell.azimuth + (cell.beamwidth / 2);
19
+ // Use Turf to generate the sector
20
+ // Note: turf.sector takes (center, radius, bearing1, bearing2)
21
+ // Radius must be in kilometers for default turf units, or specify units
22
+ const sector = turf.sector(center, radiusMeters / 1000, bearing1, bearing2, {
23
+ steps: 10 // Low steps for performance, increase if jagged
24
+ });
25
+ // Attach properties for styling and interaction
26
+ sector.properties = {
27
+ id: cell.id,
28
+ cellName: cell.cellName,
29
+ tech: cell.tech,
30
+ fband: cell.fband,
31
+ zIndex: zIndex,
32
+ color: color
33
+ };
34
+ return sector;
35
+ }
@@ -0,0 +1,18 @@
1
+ import type { Cell, CellGroupingField } from '../types';
2
+ export interface TreeData {
3
+ nodes: Map<string, any>;
4
+ rootPaths: string[];
5
+ }
6
+ /**
7
+ * Generates a stable ID for a leaf node based on grouping values
8
+ */
9
+ export declare function generateLeafId(level1: string, level2: string): string;
10
+ /**
11
+ * Gets a color from the palette based on index or hash
12
+ */
13
+ export declare function getColorForGroup(index: number): string;
14
+ /**
15
+ * Groups cells into a tree structure
16
+ * This is a simplified version for the initial implementation
17
+ */
18
+ export declare function groupCells(cells: Cell[], level1Field: CellGroupingField, level2Field: CellGroupingField): Map<string, Cell[]>;
@@ -0,0 +1,30 @@
1
+ import { DEFAULT_PALETTE } from '../constants';
2
+ /**
3
+ * Generates a stable ID for a leaf node based on grouping values
4
+ */
5
+ export function generateLeafId(level1, level2) {
6
+ return `${level1}__${level2}`;
7
+ }
8
+ /**
9
+ * Gets a color from the palette based on index or hash
10
+ */
11
+ export function getColorForGroup(index) {
12
+ return DEFAULT_PALETTE[index % DEFAULT_PALETTE.length];
13
+ }
14
+ /**
15
+ * Groups cells into a tree structure
16
+ * This is a simplified version for the initial implementation
17
+ */
18
+ export function groupCells(cells, level1Field, level2Field) {
19
+ const groups = new Map();
20
+ for (const cell of cells) {
21
+ const l1 = cell[level1Field] || 'Unknown';
22
+ const l2 = cell[level2Field] || 'Unknown';
23
+ const key = generateLeafId(String(l1), String(l2));
24
+ if (!groups.has(key)) {
25
+ groups.set(key, []);
26
+ }
27
+ groups.get(key)?.push(cell);
28
+ }
29
+ return groups;
30
+ }
@@ -0,0 +1,11 @@
1
+ import type { TreeNode } from '../../../../core/TreeView/tree.model';
2
+ import type { Cell, CellGroupingField } from '../types';
3
+ import type { CellRegistry } from '../stores/cell.registry.svelte';
4
+ /**
5
+ * Converts a list of cells into a Tree structure for the TreeView component
6
+ */
7
+ export declare function buildCellTree(cells: Cell[], registry: CellRegistry, level1?: CellGroupingField, level2?: CellGroupingField): TreeNode<{
8
+ color: string;
9
+ count: number;
10
+ groupId: string;
11
+ }>[];
@@ -0,0 +1,53 @@
1
+ import { groupCells, generateLeafId, getColorForGroup } from './grouping';
2
+ /**
3
+ * Converts a list of cells into a Tree structure for the TreeView component
4
+ */
5
+ export function buildCellTree(cells, registry, level1 = 'tech', level2 = 'fband') {
6
+ // 1. Group by Level 1 -> Level 2
7
+ const groups = groupCells(cells, level1, level2);
8
+ // 2. Build Tree Nodes
9
+ const level1Nodes = new Map();
10
+ let groupIndex = 0;
11
+ for (const [groupId, groupCells] of groups) {
12
+ if (groupCells.length === 0)
13
+ continue;
14
+ const sample = groupCells[0];
15
+ const l1Value = String(sample[level1] || 'Unknown');
16
+ const l2Value = String(sample[level2] || 'Unknown');
17
+ // Ensure Level 1 Node (Root)
18
+ if (!level1Nodes.has(l1Value)) {
19
+ level1Nodes.set(l1Value, {
20
+ id: l1Value,
21
+ label: l1Value,
22
+ children: [],
23
+ defaultExpanded: true,
24
+ });
25
+ }
26
+ // Get style from registry (or default)
27
+ const defaultColor = getColorForGroup(groupIndex++);
28
+ const style = registry.getStyle(groupId, defaultColor);
29
+ // Create Level 2 Node (Leaf)
30
+ const leafNode = {
31
+ id: groupId,
32
+ label: `${l2Value} (${groupCells.length})`,
33
+ metadata: {
34
+ color: style.color,
35
+ count: groupCells.length,
36
+ groupId: groupId
37
+ },
38
+ defaultChecked: style.visible,
39
+ };
40
+ level1Nodes.get(l1Value)?.children?.push(leafNode);
41
+ }
42
+ // 3. Sort Level 1 Nodes
43
+ const sortedNodes = Array.from(level1Nodes.values()).sort((a, b) => a.label.localeCompare(b.label));
44
+ // 4. Wrap in Root Node
45
+ const rootNode = {
46
+ id: 'root-cells',
47
+ label: `Cells (${cells.length})`,
48
+ children: sortedNodes,
49
+ defaultExpanded: true,
50
+ defaultChecked: true,
51
+ };
52
+ return [rootNode];
53
+ }
@@ -0,0 +1,9 @@
1
+ import type { Cell } from '../types';
2
+ export declare class CellDataStore {
3
+ rawCells: Cell[];
4
+ filterOnAir: boolean;
5
+ constructor();
6
+ setCells(cells: Cell[]): void;
7
+ get filteredCells(): Cell[];
8
+ }
9
+ export declare function createCellDataStore(): CellDataStore;
@@ -0,0 +1,16 @@
1
+ export class CellDataStore {
2
+ rawCells = $state([]);
3
+ filterOnAir = $state(false);
4
+ constructor() { }
5
+ setCells(cells) {
6
+ this.rawCells = cells;
7
+ }
8
+ get filteredCells() {
9
+ if (!this.filterOnAir)
10
+ return this.rawCells;
11
+ return this.rawCells.filter(c => c.status === 'On_Air');
12
+ }
13
+ }
14
+ export function createCellDataStore() {
15
+ return new CellDataStore();
16
+ }
@@ -0,0 +1,25 @@
1
+ import type { CellGroupingField } from '../types';
2
+ export declare class CellDisplayStore {
3
+ key: string;
4
+ targetPixelSize: number;
5
+ fillOpacity: number;
6
+ lineWidth: number;
7
+ showLabels: boolean;
8
+ level1: CellGroupingField;
9
+ level2: CellGroupingField;
10
+ labelPixelDistance: number;
11
+ labelFontSize: number;
12
+ labelAzimuthTolerance: number;
13
+ labelColor: string;
14
+ labelHaloColor: string;
15
+ labelHaloWidth: number;
16
+ labels2G: {
17
+ primary: string;
18
+ secondary: string;
19
+ };
20
+ labels4G: {
21
+ primary: string;
22
+ secondary: string;
23
+ };
24
+ constructor();
25
+ }
@@ -0,0 +1,67 @@
1
+ import { browser } from '$app/environment';
2
+ export class CellDisplayStore {
3
+ key = 'map-v3-cell-display';
4
+ // State
5
+ targetPixelSize = $state(50);
6
+ fillOpacity = $state(0.6);
7
+ lineWidth = $state(1);
8
+ showLabels = $state(false);
9
+ // Grouping
10
+ level1 = $state('tech');
11
+ level2 = $state('fband');
12
+ // Label Settings
13
+ labelPixelDistance = $state(60);
14
+ labelFontSize = $state(12);
15
+ labelAzimuthTolerance = $state(10);
16
+ labelColor = $state('#333333');
17
+ labelHaloColor = $state('#ffffff');
18
+ labelHaloWidth = $state(1);
19
+ // Tech-specific label config
20
+ labels2G = $state({ primary: 'cellID', secondary: 'none' });
21
+ labels4G = $state({ primary: 'cellID', secondary: 'dlEarfn' });
22
+ constructor() {
23
+ if (browser) {
24
+ const saved = localStorage.getItem(this.key);
25
+ if (saved) {
26
+ try {
27
+ const parsed = JSON.parse(saved);
28
+ this.targetPixelSize = parsed.targetPixelSize ?? 50;
29
+ this.fillOpacity = parsed.fillOpacity ?? 0.6;
30
+ this.lineWidth = parsed.lineWidth ?? 1;
31
+ this.showLabels = parsed.showLabels ?? false;
32
+ this.level1 = parsed.level1 ?? 'tech';
33
+ this.level2 = parsed.level2 ?? 'fband';
34
+ this.labelPixelDistance = parsed.labelPixelDistance ?? 60;
35
+ this.labelFontSize = parsed.labelFontSize ?? 12;
36
+ this.labelAzimuthTolerance = parsed.labelAzimuthTolerance ?? 10;
37
+ this.labelColor = parsed.labelColor ?? '#333333';
38
+ this.labelHaloColor = parsed.labelHaloColor ?? '#ffffff';
39
+ this.labelHaloWidth = parsed.labelHaloWidth ?? 1;
40
+ this.labels2G = parsed.labels2G ?? { primary: 'cellID', secondary: 'none' };
41
+ this.labels4G = parsed.labels4G ?? { primary: 'cellID', secondary: 'dlEarfn' };
42
+ }
43
+ catch (e) {
44
+ console.error('Failed to load cell display settings', e);
45
+ }
46
+ }
47
+ $effect(() => {
48
+ localStorage.setItem(this.key, JSON.stringify({
49
+ targetPixelSize: this.targetPixelSize,
50
+ fillOpacity: this.fillOpacity,
51
+ lineWidth: this.lineWidth,
52
+ showLabels: this.showLabels,
53
+ level1: this.level1,
54
+ level2: this.level2,
55
+ labelPixelDistance: this.labelPixelDistance,
56
+ labelFontSize: this.labelFontSize,
57
+ labelAzimuthTolerance: this.labelAzimuthTolerance,
58
+ labelColor: this.labelColor,
59
+ labelHaloColor: this.labelHaloColor,
60
+ labelHaloWidth: this.labelHaloWidth,
61
+ labels2G: this.labels2G,
62
+ labels4G: this.labels4G
63
+ }));
64
+ });
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Persistent store for cell styling (colors, visibility)
3
+ * Key: Stable Group ID (e.g. "4G__LTE700")
4
+ * Value: { color: string, visible: boolean }
5
+ */
6
+ export declare class CellRegistry {
7
+ state: Record<string, {
8
+ color: string;
9
+ visible: boolean;
10
+ }>;
11
+ version: number;
12
+ namespace: string;
13
+ constructor(namespace?: string);
14
+ load(): void;
15
+ save(): void;
16
+ getStyle(id: string, defaultColor: string): {
17
+ color: string;
18
+ visible: boolean;
19
+ };
20
+ toggleVisibility(id: string): void;
21
+ setColor(id: string, color: string): void;
22
+ }
23
+ export declare function createCellRegistry(namespace: string): CellRegistry;
@@ -0,0 +1,68 @@
1
+ import { writable } from 'svelte/store';
2
+ /**
3
+ * Persistent store for cell styling (colors, visibility)
4
+ * Key: Stable Group ID (e.g. "4G__LTE700")
5
+ * Value: { color: string, visible: boolean }
6
+ */
7
+ export class CellRegistry {
8
+ state = $state({});
9
+ version = $state(0); // Signal for reactivity
10
+ namespace;
11
+ constructor(namespace = 'default') {
12
+ this.namespace = namespace;
13
+ this.load();
14
+ $effect(() => {
15
+ this.save();
16
+ });
17
+ }
18
+ load() {
19
+ if (typeof window === 'undefined')
20
+ return;
21
+ try {
22
+ const stored = localStorage.getItem(`${this.namespace}:cell-registry`);
23
+ if (stored) {
24
+ this.state = JSON.parse(stored);
25
+ this.version++;
26
+ }
27
+ }
28
+ catch (e) {
29
+ console.warn('Failed to load cell registry', e);
30
+ }
31
+ }
32
+ save() {
33
+ if (typeof window === 'undefined')
34
+ return;
35
+ try {
36
+ localStorage.setItem(`${this.namespace}:cell-registry`, JSON.stringify(this.state));
37
+ }
38
+ catch (e) {
39
+ console.warn('Failed to save cell registry', e);
40
+ }
41
+ }
42
+ getStyle(id, defaultColor) {
43
+ if (!this.state[id]) {
44
+ // Initialize if missing
45
+ this.state[id] = { color: defaultColor, visible: true };
46
+ // No version bump here to avoid loops during render
47
+ }
48
+ return this.state[id];
49
+ }
50
+ toggleVisibility(id) {
51
+ if (this.state[id]) {
52
+ this.state[id].visible = !this.state[id].visible;
53
+ this.version++;
54
+ }
55
+ else {
56
+ console.warn(`[CellRegistry] Tried to toggle missing ID: ${id}`);
57
+ }
58
+ }
59
+ setColor(id, color) {
60
+ if (this.state[id]) {
61
+ this.state[id].color = color;
62
+ this.version++;
63
+ }
64
+ }
65
+ }
66
+ export function createCellRegistry(namespace) {
67
+ return new CellRegistry(namespace);
68
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Cell Feature - Type Definitions
3
+ *
4
+ * Core interfaces and types for cellular network visualization
5
+ */
6
+ /**
7
+ * Cell data model - represents a radio cell/sector
8
+ */
9
+ export interface Cell {
10
+ id: string;
11
+ txId: string;
12
+ cellID: string;
13
+ cellID2G: string;
14
+ cellName: string;
15
+ siteId: string;
16
+ tech: string;
17
+ fband: string;
18
+ frq: string;
19
+ type: string;
20
+ status: string;
21
+ onAirDate: string;
22
+ bcch: number;
23
+ ctrlid: string;
24
+ dlEarfn: number;
25
+ antenna: string;
26
+ azimuth: number;
27
+ height: number;
28
+ electricalTilt: string;
29
+ beamwidth: number;
30
+ latitude: number;
31
+ longitude: number;
32
+ dx: number;
33
+ dy: number;
34
+ siteLatitude: number;
35
+ siteLongitude: number;
36
+ comment: string;
37
+ planner: string;
38
+ atollETP: number;
39
+ atollPW: number;
40
+ atollRS: number;
41
+ atollBW: number;
42
+ cellId3: string;
43
+ nwtP1: number;
44
+ nwtP2: number;
45
+ pci1: number;
46
+ nwtRS: number;
47
+ nwtBW: number;
48
+ other?: Record<string, any>;
49
+ customSubgroup: string;
50
+ }
51
+ /**
52
+ * Supported cell sector status values
53
+ */
54
+ export type CellStatus = 'On_Air' | 'On_Air_UNDER_CONSTRUCTION' | 'On_Air_Locked' | 'RF_Plan_Ready' | 'Re-Planned_RF_Plan_Ready' | 'Tavlati_RF_Plan_Ready';
55
+ /**
56
+ * Technology-Band combination key
57
+ */
58
+ export type TechnologyBandKey = string;
59
+ /**
60
+ * Grouping fields for Tree View
61
+ */
62
+ export type CellGroupingField = 'tech' | 'fband' | 'frq' | 'status' | 'siteId' | 'none';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Cell Feature - Type Definitions
3
+ *
4
+ * Core interfaces and types for cellular network visualization
5
+ */
6
+ export {};