@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.
- package/dist/apps/antenna-pattern/components/AntennaControls.svelte +1 -106
- package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte +0 -36
- package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte +0 -2
- package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte +0 -22
- package/dist/apps/antenna-pattern/components/chart-engines/PolarAreaChart.svelte +0 -2
- package/dist/apps/antenna-pattern/components/chart-engines/PolarBarChart.svelte +0 -2
- package/dist/apps/antenna-pattern/components/chart-engines/PolarLineChart.svelte +0 -2
- package/dist/apps/site-check/data-loader.js +0 -8
- package/dist/core/Charts/GlobalControls.svelte +0 -4
- package/dist/core/Desktop/Grid/ResizeHandle.svelte +0 -7
- package/dist/core/Desktop/Grid/resizeStore.js +0 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/map-v2/demo/DemoMap.svelte +0 -2
- package/dist/map-v2/demo/demo-cells.js +0 -1
- package/dist/map-v2/features/cells/layers/CellsLayer.svelte +7 -26
- package/dist/map-v2/features/cells/utils/cellTree.js +0 -29
- package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte +3 -27
- package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +8 -25
- package/dist/map-v2/features/repeaters/utils/repeaterTree.js +0 -6
- package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +0 -8
- package/dist/map-v2/features/sites/utils/siteTreeUtils.js +0 -6
- package/dist/map-v3/core/components/Map.svelte +89 -0
- package/dist/map-v3/core/components/Map.svelte.d.ts +13 -0
- package/dist/map-v3/core/controls/FeatureSettingsControl.svelte +103 -0
- package/dist/map-v3/core/controls/FeatureSettingsControl.svelte.d.ts +15 -0
- package/dist/map-v3/core/controls/MapStyleControl.svelte +271 -0
- package/dist/map-v3/core/controls/MapStyleControl.svelte.d.ts +28 -0
- package/dist/map-v3/core/index.d.ts +6 -0
- package/dist/map-v3/core/index.js +5 -0
- package/dist/map-v3/core/stores/map.store.svelte.d.ts +8 -0
- package/dist/map-v3/core/stores/map.store.svelte.js +29 -0
- package/dist/map-v3/core/stores/viewport.store.svelte.d.ts +38 -0
- package/dist/map-v3/core/stores/viewport.store.svelte.js +107 -0
- package/dist/map-v3/demo/DemoMap.svelte +104 -0
- package/dist/map-v3/demo/DemoMap.svelte.d.ts +6 -0
- package/dist/map-v3/demo/demo-cells.d.ts +13 -0
- package/dist/map-v3/demo/demo-cells.js +130 -0
- package/dist/map-v3/demo/demo-data.d.ts +8 -0
- package/dist/map-v3/demo/demo-data.js +104 -0
- package/dist/map-v3/demo/demo-repeaters.d.ts +13 -0
- package/dist/map-v3/demo/demo-repeaters.js +73 -0
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte +208 -0
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte.d.ts +12 -0
- package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte +229 -0
- package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte.d.ts +7 -0
- package/dist/map-v3/features/cells/constants.d.ts +18 -0
- package/dist/map-v3/features/cells/constants.js +37 -0
- package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte +230 -0
- package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte +194 -0
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/cells/layers/index.d.ts +2 -0
- package/dist/map-v3/features/cells/layers/index.js +2 -0
- package/dist/map-v3/features/cells/logic/geometry.d.ts +12 -0
- package/dist/map-v3/features/cells/logic/geometry.js +35 -0
- package/dist/map-v3/features/cells/logic/grouping.d.ts +18 -0
- package/dist/map-v3/features/cells/logic/grouping.js +30 -0
- package/dist/map-v3/features/cells/logic/tree-adapter.d.ts +11 -0
- package/dist/map-v3/features/cells/logic/tree-adapter.js +53 -0
- package/dist/map-v3/features/cells/stores/cell.data.svelte.d.ts +9 -0
- package/dist/map-v3/features/cells/stores/cell.data.svelte.js +16 -0
- package/dist/map-v3/features/cells/stores/cell.display.svelte.d.ts +25 -0
- package/dist/map-v3/features/cells/stores/cell.display.svelte.js +67 -0
- package/dist/map-v3/features/cells/stores/cell.registry.svelte.d.ts +23 -0
- package/dist/map-v3/features/cells/stores/cell.registry.svelte.js +68 -0
- package/dist/map-v3/features/cells/types.d.ts +62 -0
- package/dist/map-v3/features/cells/types.js +6 -0
- package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +148 -0
- package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte.d.ts +12 -0
- package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte +209 -0
- package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte.d.ts +7 -0
- package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte +177 -0
- package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte +163 -0
- package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/repeaters/logic/geometry.d.ts +3 -0
- package/dist/map-v3/features/repeaters/logic/geometry.js +23 -0
- package/dist/map-v3/features/repeaters/logic/grouping.d.ts +8 -0
- package/dist/map-v3/features/repeaters/logic/grouping.js +20 -0
- package/dist/map-v3/features/repeaters/logic/tree-adapter.d.ts +8 -0
- package/dist/map-v3/features/repeaters/logic/tree-adapter.js +43 -0
- package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.d.ts +8 -0
- package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.js +13 -0
- package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.d.ts +21 -0
- package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.js +64 -0
- package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.d.ts +23 -0
- package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.js +68 -0
- package/dist/map-v3/features/repeaters/types.d.ts +18 -0
- package/dist/map-v3/features/repeaters/types.js +1 -0
- package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +119 -0
- package/dist/map-v3/features/sites/components/SiteFilterControl.svelte.d.ts +12 -0
- package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte +241 -0
- package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte.d.ts +7 -0
- package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte +152 -0
- package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/sites/layers/SitesLayer.svelte +132 -0
- package/dist/map-v3/features/sites/layers/SitesLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/sites/logic/tree-adapter.d.ts +9 -0
- package/dist/map-v3/features/sites/logic/tree-adapter.js +75 -0
- package/dist/map-v3/features/sites/stores/site.data.svelte.d.ts +8 -0
- package/dist/map-v3/features/sites/stores/site.data.svelte.js +40 -0
- package/dist/map-v3/features/sites/stores/site.display.svelte.d.ts +20 -0
- package/dist/map-v3/features/sites/stores/site.display.svelte.js +63 -0
- package/dist/map-v3/features/sites/stores/site.registry.svelte.d.ts +13 -0
- package/dist/map-v3/features/sites/stores/site.registry.svelte.js +83 -0
- package/dist/map-v3/features/sites/types.d.ts +12 -0
- package/dist/map-v3/features/sites/types.js +1 -0
- package/dist/map-v3/index.d.ts +30 -0
- package/dist/map-v3/index.js +36 -0
- package/dist/map-v3/shared/controls/MapControl.svelte +242 -0
- package/dist/map-v3/shared/controls/MapControl.svelte.d.ts +27 -0
- package/dist/map-v3/shared/index.d.ts +1 -0
- package/dist/map-v3/shared/index.js +1 -0
- 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,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';
|