@smartnet360/svelte-components 0.0.102 → 0.0.104
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/index.d.ts +1 -0
- package/dist/apps/antenna-pattern/index.js +1 -0
- package/dist/apps/antenna-pattern/utils/load-static-antennas.d.ts +17 -0
- package/dist/apps/antenna-pattern/utils/load-static-antennas.js +83 -0
- package/dist/apps/site-check/SiteCheck.svelte +13 -81
- package/dist/apps/site-check/SiteCheckControls.svelte +0 -7
- package/dist/apps/site-check/helper.js +0 -33
- package/dist/apps/site-check/transforms.js +15 -65
- package/dist/core/CellTable/CellTable.svelte +456 -0
- package/dist/core/CellTable/CellTable.svelte.d.ts +27 -0
- package/dist/core/CellTable/CellTablePanel.svelte +211 -0
- package/dist/core/CellTable/CellTablePanel.svelte.d.ts +49 -0
- package/dist/core/CellTable/CellTableToolbar.svelte +218 -0
- package/dist/core/CellTable/CellTableToolbar.svelte.d.ts +32 -0
- package/dist/core/CellTable/column-config.d.ts +63 -0
- package/dist/core/CellTable/column-config.js +465 -0
- package/dist/core/CellTable/index.d.ts +10 -0
- package/dist/core/CellTable/index.js +11 -0
- package/dist/core/CellTable/types.d.ts +166 -0
- package/dist/core/CellTable/types.js +6 -0
- package/dist/core/Charts/ChartCard.svelte +118 -31
- package/dist/core/Charts/ChartCard.svelte.d.ts +2 -0
- package/dist/core/Charts/ChartComponent.svelte +8 -31
- package/dist/core/Charts/data-processor.js +1 -19
- package/dist/core/CoverageMap/ai/AITools.d.ts +117 -0
- package/dist/core/CoverageMap/ai/AITools.js +380 -0
- package/dist/core/CoverageMap/core/CoverageCalculator.d.ts +138 -0
- package/dist/core/CoverageMap/core/CoverageCalculator.js +375 -0
- package/dist/core/CoverageMap/core/GridCalculator.d.ts +115 -0
- package/dist/core/CoverageMap/core/GridCalculator.js +484 -0
- package/dist/core/CoverageMap/core/PathLossModels.d.ts +253 -0
- package/dist/core/CoverageMap/core/PathLossModels.js +380 -0
- package/dist/core/CoverageMap/core/SignalProcessor.d.ts +288 -0
- package/dist/core/CoverageMap/core/SignalProcessor.js +424 -0
- package/dist/core/CoverageMap/data/AntennaStore.d.ts +165 -0
- package/dist/core/CoverageMap/data/AntennaStore.js +327 -0
- package/dist/core/CoverageMap/data/SiteStore.d.ts +155 -0
- package/dist/core/CoverageMap/data/SiteStore.js +355 -0
- package/dist/core/CoverageMap/index.d.ts +74 -0
- package/dist/core/CoverageMap/index.js +103 -0
- package/dist/core/CoverageMap/types.d.ts +252 -0
- package/dist/core/CoverageMap/types.js +7 -0
- package/dist/core/CoverageMap/utils/geoUtils.d.ts +223 -0
- package/dist/core/CoverageMap/utils/geoUtils.js +374 -0
- package/dist/core/CoverageMap/utils/rfUtils.d.ts +329 -0
- package/dist/core/CoverageMap/utils/rfUtils.js +434 -0
- package/dist/core/CoverageMap/visualization/ColorSchemes.d.ts +149 -0
- package/dist/core/CoverageMap/visualization/ColorSchemes.js +377 -0
- package/dist/core/TreeView/index.d.ts +4 -4
- package/dist/core/TreeView/index.js +5 -5
- package/dist/core/TreeView/tree-utils.d.ts +12 -0
- package/dist/core/TreeView/tree-utils.js +115 -6
- package/dist/core/TreeView/tree.store.svelte.d.ts +94 -0
- package/dist/core/TreeView/tree.store.svelte.js +274 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +2 -0
- package/dist/map-v2/features/cells/controls/CellFilterControl.svelte +16 -27
- package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte +33 -42
- package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +12 -19
- package/dist/map-v3/core/components/Map.svelte +4 -0
- package/dist/map-v3/core/stores/map.store.svelte.js +2 -0
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte +24 -30
- package/dist/map-v3/features/coverage/index.d.ts +12 -0
- package/dist/map-v3/features/coverage/index.js +16 -0
- package/dist/map-v3/features/coverage/layers/CoverageLayer.svelte +198 -0
- package/dist/map-v3/features/coverage/layers/CoverageLayer.svelte.d.ts +10 -0
- package/dist/map-v3/features/coverage/logic/coloring.d.ts +28 -0
- package/dist/map-v3/features/coverage/logic/coloring.js +77 -0
- package/dist/map-v3/features/coverage/logic/geometry.d.ts +33 -0
- package/dist/map-v3/features/coverage/logic/geometry.js +112 -0
- package/dist/map-v3/features/coverage/stores/coverage.data.svelte.d.ts +46 -0
- package/dist/map-v3/features/coverage/stores/coverage.data.svelte.js +95 -0
- package/dist/map-v3/features/coverage/stores/coverage.display.svelte.d.ts +33 -0
- package/dist/map-v3/features/coverage/stores/coverage.display.svelte.js +90 -0
- package/dist/map-v3/features/coverage/types.d.ts +52 -0
- package/dist/map-v3/features/coverage/types.js +7 -0
- package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +14 -20
- package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +23 -33
- package/dist/map-v3/index.d.ts +4 -0
- package/dist/map-v3/index.js +5 -0
- package/package.json +4 -3
- package/dist/apps/site-check/transforms-old.d.ts +0 -56
- package/dist/apps/site-check/transforms-old.js +0 -273
- package/dist/core/TreeView/tree.store.d.ts +0 -10
- package/dist/core/TreeView/tree.store.js +0 -320
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onDestroy, untrack } from 'svelte';
|
|
3
3
|
import { MapControl } from '../../../shared';
|
|
4
|
-
import { createTreeStore } from '../../../../core/TreeView
|
|
5
|
-
import TreeView from '../../../../core/TreeView/TreeView.svelte';
|
|
4
|
+
import { createTreeStore, TreeView } from '../../../../core/TreeView';
|
|
6
5
|
import type { CellDataStore } from '../stores/cell.data.svelte';
|
|
7
6
|
import type { CellRegistry } from '../stores/cell.registry.svelte';
|
|
8
7
|
import type { CellDisplayStore } from '../stores/cell.display.svelte';
|
|
@@ -64,36 +63,31 @@
|
|
|
64
63
|
|
|
65
64
|
// Sync Tree Selection -> Cell Registry Visibility
|
|
66
65
|
$effect(() => {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
const val = treeStore;
|
|
67
|
+
// Iterate all leaf nodes to sync visibility
|
|
68
|
+
// This is a bit heavy but ensures consistency.
|
|
69
|
+
// Optimization: Only update changed nodes if we had a diff.
|
|
70
|
+
|
|
71
|
+
// We only care about leaf nodes (bands)
|
|
72
|
+
let changes = 0;
|
|
73
|
+
val.state.nodes.forEach((nodeState) => {
|
|
74
|
+
if (nodeState.node.children && nodeState.node.children.length > 0) return; // Skip folders
|
|
75
|
+
|
|
76
|
+
const groupId = nodeState.node.id;
|
|
77
|
+
// IMPORTANT: Read from checkedPaths set, NOT nodeState.checked (which is static/stale)
|
|
78
|
+
const isVisible = val.state.checkedPaths.has(nodeState.path);
|
|
71
79
|
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// IMPORTANT: Read from checkedPaths set, NOT nodeState.checked (which is static/stale)
|
|
79
|
-
const isVisible = val.state.checkedPaths.has(nodeState.path);
|
|
80
|
-
|
|
81
|
-
// Update registry if different
|
|
82
|
-
const currentStyle = registry.getStyle(groupId, '#000'); // Color doesn't matter here
|
|
83
|
-
if (currentStyle.visible !== isVisible) {
|
|
84
|
-
// console.log(`[CellFilterControl] Syncing ${groupId}: ${currentStyle.visible} -> ${isVisible}`);
|
|
85
|
-
registry.toggleVisibility(groupId);
|
|
86
|
-
changes++;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
if (changes > 0) {
|
|
90
|
-
console.log(`[CellFilterControl] Synced ${changes} visibility changes to registry`);
|
|
80
|
+
// Update registry if different
|
|
81
|
+
const currentStyle = registry.getStyle(groupId, '#000'); // Color doesn't matter here
|
|
82
|
+
if (currentStyle.visible !== isVisible) {
|
|
83
|
+
// console.log(`[CellFilterControl] Syncing ${groupId}: ${currentStyle.visible} -> ${isVisible}`);
|
|
84
|
+
registry.toggleVisibility(groupId);
|
|
85
|
+
changes++;
|
|
91
86
|
}
|
|
92
87
|
});
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
};
|
|
88
|
+
if (changes > 0) {
|
|
89
|
+
console.log(`[CellFilterControl] Synced ${changes} visibility changes to registry`);
|
|
90
|
+
}
|
|
97
91
|
});
|
|
98
92
|
|
|
99
93
|
function handleColorChange(groupId: string, event: Event) {
|
|
@@ -184,7 +178,7 @@
|
|
|
184
178
|
No cells loaded.
|
|
185
179
|
</div>
|
|
186
180
|
{:else}
|
|
187
|
-
<TreeView showControls={false} store={
|
|
181
|
+
<TreeView showControls={false} store={treeStore} height="300px">
|
|
188
182
|
{#snippet children({ node, state })}
|
|
189
183
|
<!-- Color Picker (Only for leaves) -->
|
|
190
184
|
{#if !node.children || node.children.length === 0}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage Feature - Public Exports
|
|
3
|
+
*
|
|
4
|
+
* Self-contained RF coverage prediction and visualization.
|
|
5
|
+
* Zero dependencies on cells/sites features.
|
|
6
|
+
*/
|
|
7
|
+
export * from './types';
|
|
8
|
+
export { CoverageDataStore, createCoverageDataStore } from './stores/coverage.data.svelte';
|
|
9
|
+
export { CoverageDisplayStore, createCoverageDisplayStore } from './stores/coverage.display.svelte';
|
|
10
|
+
export { default as CoverageLayer } from './layers/CoverageLayer.svelte';
|
|
11
|
+
export { gridToGeoJSON, getGridBounds, getSignalRange } from './logic/geometry';
|
|
12
|
+
export { getColorForSignal, getLegendItems } from './logic/coloring';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage Feature - Public Exports
|
|
3
|
+
*
|
|
4
|
+
* Self-contained RF coverage prediction and visualization.
|
|
5
|
+
* Zero dependencies on cells/sites features.
|
|
6
|
+
*/
|
|
7
|
+
// Types
|
|
8
|
+
export * from './types';
|
|
9
|
+
// Stores
|
|
10
|
+
export { CoverageDataStore, createCoverageDataStore } from './stores/coverage.data.svelte';
|
|
11
|
+
export { CoverageDisplayStore, createCoverageDisplayStore } from './stores/coverage.display.svelte';
|
|
12
|
+
// Layer
|
|
13
|
+
export { default as CoverageLayer } from './layers/CoverageLayer.svelte';
|
|
14
|
+
// Utilities (for advanced usage)
|
|
15
|
+
export { gridToGeoJSON, getGridBounds, getSignalRange } from './logic/geometry';
|
|
16
|
+
export { getColorForSignal, getLegendItems } from './logic/coloring';
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Coverage Layer
|
|
4
|
+
*
|
|
5
|
+
* Renders coverage grid as Mapbox fill layer.
|
|
6
|
+
* Self-contained - only needs map instance from context.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getContext, onMount } from 'svelte';
|
|
10
|
+
import type { MapStore } from '../../../core/stores/map.store.svelte';
|
|
11
|
+
import type { CoverageDataStore } from '../stores/coverage.data.svelte';
|
|
12
|
+
import type { CoverageDisplayStore } from '../stores/coverage.display.svelte';
|
|
13
|
+
import { gridToGeoJSON, getGridBounds } from '../logic/geometry';
|
|
14
|
+
import type mapboxgl from 'mapbox-gl';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
dataStore: CoverageDataStore;
|
|
18
|
+
displayStore: CoverageDisplayStore;
|
|
19
|
+
autoFitBounds?: boolean; // Automatically fit map to coverage bounds
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let { dataStore, displayStore, autoFitBounds = true }: Props = $props();
|
|
23
|
+
|
|
24
|
+
const mapStore = getContext<MapStore>('MAP_CONTEXT');
|
|
25
|
+
const sourceId = 'coverage-source';
|
|
26
|
+
const layerId = 'coverage-layer';
|
|
27
|
+
const componentId = Math.random().toString(36).substring(7);
|
|
28
|
+
|
|
29
|
+
let layerInitialized = $state(false);
|
|
30
|
+
|
|
31
|
+
console.log(`[CoverageLayer-${componentId}] Component created, mapStore:`, mapStore);
|
|
32
|
+
|
|
33
|
+
// Main effect: manage layer lifecycle - runs once (avoid reactive dependencies!)
|
|
34
|
+
$effect(() => {
|
|
35
|
+
const map = mapStore.map;
|
|
36
|
+
if (!map) return;
|
|
37
|
+
|
|
38
|
+
// Initialize layers when style is loaded
|
|
39
|
+
const initializeLayers = () => {
|
|
40
|
+
// Always check if layer already exists (style might have reloaded)
|
|
41
|
+
if (map.getLayer(layerId)) {
|
|
42
|
+
console.log(`[CoverageLayer-${componentId}] Layer already exists, skipping initialization`);
|
|
43
|
+
layerInitialized = true;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`[CoverageLayer-${componentId}] Initializing layer...`);
|
|
48
|
+
|
|
49
|
+
// Add source if it doesn't exist
|
|
50
|
+
if (!map.getSource(sourceId)) {
|
|
51
|
+
map.addSource(sourceId, {
|
|
52
|
+
type: 'geojson',
|
|
53
|
+
data: { type: 'FeatureCollection', features: [] }
|
|
54
|
+
});
|
|
55
|
+
console.log(`[CoverageLayer-${componentId}] Source added:`, sourceId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Add layer - use static defaults
|
|
59
|
+
map.addLayer({
|
|
60
|
+
id: layerId,
|
|
61
|
+
type: 'fill',
|
|
62
|
+
source: sourceId,
|
|
63
|
+
paint: {
|
|
64
|
+
'fill-color': ['get', 'color'],
|
|
65
|
+
'fill-opacity': 0.7 // Will be updated by separate effect
|
|
66
|
+
},
|
|
67
|
+
layout: {
|
|
68
|
+
'visibility': 'visible' // Will be updated by separate effect
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
console.log(`[CoverageLayer-${componentId}] Layer added:`, layerId);
|
|
72
|
+
|
|
73
|
+
layerInitialized = true;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Listen to style.load event - fires when style finishes loading (and on style changes)
|
|
77
|
+
const handleStyleLoad = () => {
|
|
78
|
+
console.log(`[CoverageLayer-${componentId}] Style loaded, reinitializing layers...`);
|
|
79
|
+
layerInitialized = false; // Reset flag so layers are recreated
|
|
80
|
+
initializeLayers();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Initialize immediately if style is already loaded
|
|
84
|
+
if (map.isStyleLoaded()) {
|
|
85
|
+
initializeLayers();
|
|
86
|
+
} else {
|
|
87
|
+
map.once('style.load', initializeLayers);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Re-initialize layers when style changes/reloads
|
|
91
|
+
map.on('style.load', handleStyleLoad);
|
|
92
|
+
|
|
93
|
+
// Cleanup on unmount only
|
|
94
|
+
return () => {
|
|
95
|
+
console.log(`[CoverageLayer-${componentId}] Component unmounting, cleaning up...`);
|
|
96
|
+
map.off('style.load', handleStyleLoad);
|
|
97
|
+
if (map.getLayer(layerId)) map.removeLayer(layerId);
|
|
98
|
+
if (map.getSource(sourceId)) map.removeSource(sourceId);
|
|
99
|
+
layerInitialized = false;
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Update layer visibility and opacity dynamically
|
|
104
|
+
$effect(() => {
|
|
105
|
+
const map = mapStore.map;
|
|
106
|
+
if (!map || !layerInitialized) return;
|
|
107
|
+
|
|
108
|
+
const opacity = displayStore.opacity;
|
|
109
|
+
const visible = displayStore.visible;
|
|
110
|
+
|
|
111
|
+
if (map.getLayer(layerId)) {
|
|
112
|
+
map.setPaintProperty(layerId, 'fill-opacity', opacity);
|
|
113
|
+
map.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none');
|
|
114
|
+
console.log(`[CoverageLayer-${componentId}] Updated opacity:`, opacity, 'visible:', visible);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// React to data changes - separate effect
|
|
119
|
+
$effect(() => {
|
|
120
|
+
const map = mapStore.map;
|
|
121
|
+
if (!map || !layerInitialized) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Explicitly read dependencies for reactivity
|
|
126
|
+
const hasResults = dataStore.hasResults;
|
|
127
|
+
const colorScheme = displayStore.colorScheme;
|
|
128
|
+
const minSignal = displayStore.minSignalThreshold;
|
|
129
|
+
const maxSignal = displayStore.maxSignalThreshold;
|
|
130
|
+
|
|
131
|
+
console.log(`[CoverageLayer-${componentId}] Data/settings changed, hasResults:`, hasResults);
|
|
132
|
+
|
|
133
|
+
if (hasResults) {
|
|
134
|
+
updateLayer(map);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
function updateLayer(map: mapboxgl.Map) {
|
|
139
|
+
if (!dataStore.result) {
|
|
140
|
+
console.log(`[CoverageLayer-${componentId}] No coverage data to render`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const grid = dataStore.result.grid;
|
|
145
|
+
const thresholds = dataStore.result.config.signalThresholds;
|
|
146
|
+
|
|
147
|
+
console.log(`[CoverageLayer-${componentId}] Grid structure:`, {
|
|
148
|
+
rows: grid.cells.length,
|
|
149
|
+
cols: grid.cells[0]?.length,
|
|
150
|
+
cellSizeMeters: grid.cellSizeMeters,
|
|
151
|
+
bounds: grid.bounds,
|
|
152
|
+
firstCell: grid.cells[0]?.[0]
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Convert grid to GeoJSON
|
|
156
|
+
const geojson = gridToGeoJSON(
|
|
157
|
+
grid,
|
|
158
|
+
displayStore.colorScheme,
|
|
159
|
+
displayStore.minSignalThreshold,
|
|
160
|
+
displayStore.maxSignalThreshold,
|
|
161
|
+
thresholds
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
console.log(`[CoverageLayer-${componentId}] GeoJSON created:`, {
|
|
165
|
+
features: geojson.features.length,
|
|
166
|
+
firstFeature: geojson.features[0],
|
|
167
|
+
firstFeatureCoords: geojson.features[0]?.geometry.coordinates,
|
|
168
|
+
firstFeatureColor: geojson.features[0]?.properties?.color
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Check if layer exists
|
|
172
|
+
console.log(`[CoverageLayer-${componentId}] Layer exists?`, map.getLayer(layerId) !== undefined);
|
|
173
|
+
console.log(`[CoverageLayer-${componentId}] Source exists?`, map.getSource(sourceId) !== undefined);
|
|
174
|
+
|
|
175
|
+
// Update source
|
|
176
|
+
const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource;
|
|
177
|
+
if (source) {
|
|
178
|
+
source.setData(geojson);
|
|
179
|
+
console.log(`[CoverageLayer-${componentId}] Source data updated successfully`);
|
|
180
|
+
|
|
181
|
+
// Force a repaint
|
|
182
|
+
map.triggerRepaint();
|
|
183
|
+
} else {
|
|
184
|
+
console.error('[CoverageLayer] Source not found:', sourceId);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Auto-fit bounds on first render
|
|
188
|
+
if (autoFitBounds && geojson.features.length > 0) {
|
|
189
|
+
const bounds = getGridBounds(grid);
|
|
190
|
+
console.log(`[CoverageLayer-${componentId}] Fitting bounds:`, bounds);
|
|
191
|
+
map.fitBounds(bounds, {
|
|
192
|
+
padding: 50,
|
|
193
|
+
maxZoom: 14,
|
|
194
|
+
duration: 1000
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
</script>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CoverageDataStore } from '../stores/coverage.data.svelte';
|
|
2
|
+
import type { CoverageDisplayStore } from '../stores/coverage.display.svelte';
|
|
3
|
+
interface Props {
|
|
4
|
+
dataStore: CoverageDataStore;
|
|
5
|
+
displayStore: CoverageDisplayStore;
|
|
6
|
+
autoFitBounds?: boolean;
|
|
7
|
+
}
|
|
8
|
+
declare const CoverageLayer: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type CoverageLayer = ReturnType<typeof CoverageLayer>;
|
|
10
|
+
export default CoverageLayer;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color Scheme Utilities
|
|
3
|
+
*
|
|
4
|
+
* Wraps CoverageMap color scheme functions for coverage layer use.
|
|
5
|
+
*/
|
|
6
|
+
import type { SignalThresholds } from '../../../../core/CoverageMap';
|
|
7
|
+
import type { CoverageColorScheme } from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* Get color for signal strength using specified color scheme
|
|
10
|
+
*
|
|
11
|
+
* @param signalDbm - Signal strength in dBm
|
|
12
|
+
* @param colorScheme - Color scheme name
|
|
13
|
+
* @param thresholds - Optional signal thresholds
|
|
14
|
+
* @returns Hex color string
|
|
15
|
+
*/
|
|
16
|
+
export declare function getColorForSignal(signalDbm: number, colorScheme: CoverageColorScheme, thresholds?: SignalThresholds): string;
|
|
17
|
+
/**
|
|
18
|
+
* Get legend items for a color scheme
|
|
19
|
+
*
|
|
20
|
+
* @param colorScheme - Color scheme name
|
|
21
|
+
* @param thresholds - Signal thresholds
|
|
22
|
+
* @returns Array of legend items with color and label
|
|
23
|
+
*/
|
|
24
|
+
export declare function getLegendItems(colorScheme: CoverageColorScheme, thresholds?: SignalThresholds): Array<{
|
|
25
|
+
color: string;
|
|
26
|
+
label: string;
|
|
27
|
+
range: string;
|
|
28
|
+
}>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color Scheme Utilities
|
|
3
|
+
*
|
|
4
|
+
* Wraps CoverageMap color scheme functions for coverage layer use.
|
|
5
|
+
*/
|
|
6
|
+
import { getColorForSignal as getCoverageColor, HEATMAP_RED_BLUE, VIRIDIS, PLASMA, CATEGORICAL } from '../../../../core/CoverageMap';
|
|
7
|
+
/**
|
|
8
|
+
* Default signal thresholds for coloring
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_THRESHOLDS = {
|
|
11
|
+
excellent: -70,
|
|
12
|
+
good: -85,
|
|
13
|
+
fair: -95,
|
|
14
|
+
edge: -105
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Map coverage color scheme names to CoverageMap ColorScheme objects
|
|
18
|
+
*/
|
|
19
|
+
const SCHEME_MAP = {
|
|
20
|
+
'heatmap': HEATMAP_RED_BLUE,
|
|
21
|
+
'quality': CATEGORICAL,
|
|
22
|
+
'viridis': VIRIDIS,
|
|
23
|
+
'plasma': PLASMA,
|
|
24
|
+
'categorical': CATEGORICAL
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Get color for signal strength using specified color scheme
|
|
28
|
+
*
|
|
29
|
+
* @param signalDbm - Signal strength in dBm
|
|
30
|
+
* @param colorScheme - Color scheme name
|
|
31
|
+
* @param thresholds - Optional signal thresholds
|
|
32
|
+
* @returns Hex color string
|
|
33
|
+
*/
|
|
34
|
+
export function getColorForSignal(signalDbm, colorScheme, thresholds = DEFAULT_THRESHOLDS) {
|
|
35
|
+
const scheme = SCHEME_MAP[colorScheme];
|
|
36
|
+
if (!scheme) {
|
|
37
|
+
console.warn(`[ColorSchemes] Unknown scheme: ${colorScheme}, using heatmap`);
|
|
38
|
+
return getCoverageColor(signalDbm, thresholds, HEATMAP_RED_BLUE);
|
|
39
|
+
}
|
|
40
|
+
return getCoverageColor(signalDbm, thresholds, scheme);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get legend items for a color scheme
|
|
44
|
+
*
|
|
45
|
+
* @param colorScheme - Color scheme name
|
|
46
|
+
* @param thresholds - Signal thresholds
|
|
47
|
+
* @returns Array of legend items with color and label
|
|
48
|
+
*/
|
|
49
|
+
export function getLegendItems(colorScheme, thresholds = DEFAULT_THRESHOLDS) {
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
color: getColorForSignal(thresholds.excellent, colorScheme, thresholds),
|
|
53
|
+
label: 'Excellent',
|
|
54
|
+
range: `> ${thresholds.excellent} dBm`
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
color: getColorForSignal((thresholds.excellent + thresholds.good) / 2, colorScheme, thresholds),
|
|
58
|
+
label: 'Good',
|
|
59
|
+
range: `${thresholds.good} to ${thresholds.excellent} dBm`
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
color: getColorForSignal((thresholds.good + thresholds.fair) / 2, colorScheme, thresholds),
|
|
63
|
+
label: 'Fair',
|
|
64
|
+
range: `${thresholds.fair} to ${thresholds.good} dBm`
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
color: getColorForSignal((thresholds.fair + thresholds.edge) / 2, colorScheme, thresholds),
|
|
68
|
+
label: 'Poor',
|
|
69
|
+
range: `${thresholds.edge} to ${thresholds.fair} dBm`
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
color: getColorForSignal(thresholds.edge - 5, colorScheme, thresholds),
|
|
73
|
+
label: 'Edge',
|
|
74
|
+
range: `< ${thresholds.edge} dBm`
|
|
75
|
+
}
|
|
76
|
+
];
|
|
77
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid Geometry Utilities
|
|
3
|
+
*
|
|
4
|
+
* Convert coverage grid cells to GeoJSON for Mapbox rendering.
|
|
5
|
+
* Applies color schemes and filtering.
|
|
6
|
+
*/
|
|
7
|
+
import type { CoverageGrid, SignalThresholds } from '../types';
|
|
8
|
+
import type { CoverageColorScheme } from '../types';
|
|
9
|
+
import type { FeatureCollection, Polygon } from 'geojson';
|
|
10
|
+
/**
|
|
11
|
+
* Convert coverage grid to GeoJSON FeatureCollection
|
|
12
|
+
*
|
|
13
|
+
* @param grid - Coverage grid from calculation
|
|
14
|
+
* @param colorScheme - Color scheme to apply
|
|
15
|
+
* @param minSignal - Minimum signal threshold (dBm)
|
|
16
|
+
* @param maxSignal - Maximum signal threshold (dBm)
|
|
17
|
+
* @returns GeoJSON FeatureCollection ready for Mapbox
|
|
18
|
+
*/
|
|
19
|
+
export declare function gridToGeoJSON(grid: CoverageGrid, colorScheme: CoverageColorScheme, minSignal?: number, maxSignal?: number, thresholds?: SignalThresholds): FeatureCollection<Polygon>;
|
|
20
|
+
/**
|
|
21
|
+
* Calculate grid bounds for initial map view
|
|
22
|
+
*
|
|
23
|
+
* @param grid - Coverage grid
|
|
24
|
+
* @returns Mapbox-compatible bounds [[west, south], [east, north]]
|
|
25
|
+
*/
|
|
26
|
+
export declare function getGridBounds(grid: CoverageGrid): [[number, number], [number, number]];
|
|
27
|
+
/**
|
|
28
|
+
* Get signal strength range from grid
|
|
29
|
+
*
|
|
30
|
+
* @param grid - Coverage grid
|
|
31
|
+
* @returns [min, max] signal strength in dBm
|
|
32
|
+
*/
|
|
33
|
+
export declare function getSignalRange(grid: CoverageGrid): [number, number];
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid Geometry Utilities
|
|
3
|
+
*
|
|
4
|
+
* Convert coverage grid cells to GeoJSON for Mapbox rendering.
|
|
5
|
+
* Applies color schemes and filtering.
|
|
6
|
+
*/
|
|
7
|
+
import { getColorForSignal } from './coloring';
|
|
8
|
+
/**
|
|
9
|
+
* Convert coverage grid to GeoJSON FeatureCollection
|
|
10
|
+
*
|
|
11
|
+
* @param grid - Coverage grid from calculation
|
|
12
|
+
* @param colorScheme - Color scheme to apply
|
|
13
|
+
* @param minSignal - Minimum signal threshold (dBm)
|
|
14
|
+
* @param maxSignal - Maximum signal threshold (dBm)
|
|
15
|
+
* @returns GeoJSON FeatureCollection ready for Mapbox
|
|
16
|
+
*/
|
|
17
|
+
export function gridToGeoJSON(grid, colorScheme, minSignal = -115, maxSignal = -40, thresholds) {
|
|
18
|
+
const features = [];
|
|
19
|
+
const cellSizeMeters = grid.cellSizeMeters;
|
|
20
|
+
// Flatten 2D grid to 1D for processing
|
|
21
|
+
for (const row of grid.cells) {
|
|
22
|
+
for (const cell of row) {
|
|
23
|
+
// Filter by signal strength
|
|
24
|
+
if (cell.signalStrength < minSignal || cell.signalStrength > maxSignal) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
// Skip no-signal cells
|
|
28
|
+
if (cell.quality === 'no-signal') {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
// Get color for this cell
|
|
32
|
+
const color = getColorForSignal(cell.signalStrength, colorScheme, thresholds);
|
|
33
|
+
// Create polygon feature
|
|
34
|
+
const feature = createCellPolygon(cell, cellSizeMeters, color);
|
|
35
|
+
features.push(feature);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
type: 'FeatureCollection',
|
|
40
|
+
features
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create a GeoJSON polygon for a single grid cell
|
|
45
|
+
*
|
|
46
|
+
* @param cell - Grid cell data
|
|
47
|
+
* @param cellSizeMeters - Size of cell in meters
|
|
48
|
+
* @param color - Hex color for this cell
|
|
49
|
+
* @returns GeoJSON Feature
|
|
50
|
+
*/
|
|
51
|
+
function createCellPolygon(cell, cellSizeMeters, color) {
|
|
52
|
+
// Calculate cell corners (approximate, good enough for visualization)
|
|
53
|
+
const latOffset = (cellSizeMeters / 111320); // ~111,320 meters per degree lat
|
|
54
|
+
const lngOffset = (cellSizeMeters / (111320 * Math.cos(cell.position.lat * Math.PI / 180)));
|
|
55
|
+
const lat = cell.position.lat;
|
|
56
|
+
const lng = cell.position.lng;
|
|
57
|
+
// Cell corners (clockwise from bottom-left)
|
|
58
|
+
const coordinates = [[
|
|
59
|
+
[lng - lngOffset / 2, lat - latOffset / 2], // SW
|
|
60
|
+
[lng + lngOffset / 2, lat - latOffset / 2], // SE
|
|
61
|
+
[lng + lngOffset / 2, lat + latOffset / 2], // NE
|
|
62
|
+
[lng - lngOffset / 2, lat + latOffset / 2], // NW
|
|
63
|
+
[lng - lngOffset / 2, lat - latOffset / 2] // Close polygon
|
|
64
|
+
]];
|
|
65
|
+
return {
|
|
66
|
+
type: 'Feature',
|
|
67
|
+
geometry: {
|
|
68
|
+
type: 'Polygon',
|
|
69
|
+
coordinates
|
|
70
|
+
},
|
|
71
|
+
properties: {
|
|
72
|
+
signal: cell.signalStrength,
|
|
73
|
+
quality: cell.quality,
|
|
74
|
+
color: color,
|
|
75
|
+
lat: cell.position.lat,
|
|
76
|
+
lon: cell.position.lng,
|
|
77
|
+
dominantSector: cell.dominantSector
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Calculate grid bounds for initial map view
|
|
83
|
+
*
|
|
84
|
+
* @param grid - Coverage grid
|
|
85
|
+
* @returns Mapbox-compatible bounds [[west, south], [east, north]]
|
|
86
|
+
*/
|
|
87
|
+
export function getGridBounds(grid) {
|
|
88
|
+
const bounds = grid.bounds;
|
|
89
|
+
return [
|
|
90
|
+
[bounds.west, bounds.south],
|
|
91
|
+
[bounds.east, bounds.north]
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get signal strength range from grid
|
|
96
|
+
*
|
|
97
|
+
* @param grid - Coverage grid
|
|
98
|
+
* @returns [min, max] signal strength in dBm
|
|
99
|
+
*/
|
|
100
|
+
export function getSignalRange(grid) {
|
|
101
|
+
let min = 0;
|
|
102
|
+
let max = -120;
|
|
103
|
+
for (const row of grid.cells) {
|
|
104
|
+
for (const cell of row) {
|
|
105
|
+
if (cell.quality !== 'no-signal') {
|
|
106
|
+
min = Math.min(min, cell.signalStrength);
|
|
107
|
+
max = Math.max(max, cell.signalStrength);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [min, max];
|
|
112
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage Data Store
|
|
3
|
+
*
|
|
4
|
+
* Self-contained store for coverage calculation data and state.
|
|
5
|
+
* Zero dependencies on cells/sites features - fully encapsulated.
|
|
6
|
+
*/
|
|
7
|
+
import type { CoverageResult, CoverageConfig } from '../types';
|
|
8
|
+
export declare class CoverageDataStore {
|
|
9
|
+
result: CoverageResult | null;
|
|
10
|
+
calculating: boolean;
|
|
11
|
+
progress: number;
|
|
12
|
+
progressStage: string;
|
|
13
|
+
error: string | null;
|
|
14
|
+
private calculator;
|
|
15
|
+
constructor();
|
|
16
|
+
/**
|
|
17
|
+
* Calculate coverage prediction
|
|
18
|
+
* @param config - Complete RF configuration
|
|
19
|
+
*/
|
|
20
|
+
calculateCoverage(config: CoverageConfig): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Clear current coverage data
|
|
23
|
+
*/
|
|
24
|
+
clearCoverage(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Get grid cells for rendering
|
|
27
|
+
* Returns null if no result available
|
|
28
|
+
*/
|
|
29
|
+
get gridCells(): import("../../../../core/CoverageMap").GridCell[][] | null;
|
|
30
|
+
/**
|
|
31
|
+
* Get coverage statistics
|
|
32
|
+
*/
|
|
33
|
+
get stats(): import("../../../../core/CoverageMap").CoverageStats | null;
|
|
34
|
+
/**
|
|
35
|
+
* Get coverage configuration used
|
|
36
|
+
*/
|
|
37
|
+
get config(): CoverageConfig | null;
|
|
38
|
+
/**
|
|
39
|
+
* Check if results are available
|
|
40
|
+
*/
|
|
41
|
+
get hasResults(): boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Factory function to create coverage data store
|
|
45
|
+
*/
|
|
46
|
+
export declare function createCoverageDataStore(): CoverageDataStore;
|