@smartnet360/svelte-components 0.0.87 → 0.0.89
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/map-v3/core/components/MapStoreBridge.svelte +25 -0
- package/dist/map-v3/core/components/MapStoreBridge.svelte.d.ts +8 -0
- package/dist/map-v3/core/index.d.ts +1 -0
- package/dist/map-v3/core/index.js +1 -0
- package/dist/map-v3/demo/DemoMap.svelte +10 -0
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte +24 -6
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte.d.ts +3 -0
- package/dist/map-v3/features/cells/constants/statusStyles.d.ts +14 -0
- package/dist/map-v3/features/cells/constants/statusStyles.js +49 -0
- package/dist/map-v3/features/cells/constants.d.ts +1 -0
- package/dist/map-v3/features/cells/constants.js +54 -13
- package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte +7 -2
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte +28 -12
- package/dist/map-v3/features/cells/logic/geometry.d.ts +1 -1
- package/dist/map-v3/features/cells/logic/geometry.js +26 -5
- package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +14 -0
- package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte +57 -26
- package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte +309 -0
- package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte.d.ts +20 -0
- package/dist/map-v3/features/selection/index.d.ts +9 -0
- package/dist/map-v3/features/selection/index.js +10 -0
- package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte +209 -0
- package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte.d.ts +13 -0
- package/dist/map-v3/features/selection/stores/selection.store.svelte.d.ts +84 -0
- package/dist/map-v3/features/selection/stores/selection.store.svelte.js +174 -0
- package/dist/map-v3/features/selection/types.d.ts +25 -0
- package/dist/map-v3/features/selection/types.js +4 -0
- package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +14 -0
- package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte +57 -26
- package/dist/map-v3/index.d.ts +1 -0
- package/dist/map-v3/index.js +2 -0
- package/dist/map-v3/shared/controls/MapControl.svelte +37 -10
- package/dist/map-v3/shared/controls/MapControl.svelte.d.ts +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getContext } from 'svelte';
|
|
3
|
+
import type { MapStore } from '../stores/map.store.svelte';
|
|
4
|
+
import type { Map as MapboxMap } from 'mapbox-gl';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
onMapChange?: (map: MapboxMap | null) => void;
|
|
8
|
+
children?: import('svelte').Snippet;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { onMapChange, children }: Props = $props();
|
|
12
|
+
|
|
13
|
+
const mapStore = getContext<MapStore>('MAP_CONTEXT');
|
|
14
|
+
let ready = $state(false);
|
|
15
|
+
|
|
16
|
+
$effect(() => {
|
|
17
|
+
const map = mapStore.map;
|
|
18
|
+
onMapChange?.(map);
|
|
19
|
+
ready = map !== null;
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
{#if ready && children}
|
|
24
|
+
{@render children()}
|
|
25
|
+
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Map as MapboxMap } from 'mapbox-gl';
|
|
2
|
+
interface Props {
|
|
3
|
+
onMapChange?: (map: MapboxMap | null) => void;
|
|
4
|
+
children?: import('svelte').Snippet;
|
|
5
|
+
}
|
|
6
|
+
declare const MapStoreBridge: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type MapStoreBridge = ReturnType<typeof MapStoreBridge>;
|
|
8
|
+
export default MapStoreBridge;
|
|
@@ -2,5 +2,6 @@ export { MapStore, createMapStore } from './stores/map.store.svelte';
|
|
|
2
2
|
export type { ViewportStore, ViewportState } from './stores/viewport.store.svelte';
|
|
3
3
|
export { createViewportStore } from './stores/viewport.store.svelte';
|
|
4
4
|
export { default as Map } from './components/Map.svelte';
|
|
5
|
+
export { default as MapStoreBridge } from './components/MapStoreBridge.svelte';
|
|
5
6
|
export { default as MapStyleControl } from './controls/MapStyleControl.svelte';
|
|
6
7
|
export { default as FeatureSettingsControl } from './controls/FeatureSettingsControl.svelte';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { MapStore, createMapStore } from './stores/map.store.svelte';
|
|
2
2
|
export { createViewportStore } from './stores/viewport.store.svelte';
|
|
3
3
|
export { default as Map } from './components/Map.svelte';
|
|
4
|
+
export { default as MapStoreBridge } from './components/MapStoreBridge.svelte';
|
|
4
5
|
export { default as MapStyleControl } from './controls/MapStyleControl.svelte';
|
|
5
6
|
export { default as FeatureSettingsControl } from './controls/FeatureSettingsControl.svelte';
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import SiteLabelsLayer from '../features/sites/layers/SiteLabelsLayer.svelte';
|
|
20
20
|
import SiteFilterControl from '../features/sites/components/SiteFilterControl.svelte';
|
|
21
21
|
import { createSiteRegistry } from '../features/sites/stores/site.registry.svelte';
|
|
22
|
+
import FeatureSelectionControl from '../features/selection/components/FeatureSelectionControl.svelte';
|
|
22
23
|
import { demoCells } from './demo-cells';
|
|
23
24
|
import { demoRepeaters } from './demo-repeaters';
|
|
24
25
|
|
|
@@ -93,6 +94,15 @@
|
|
|
93
94
|
<CellLabelsLayer dataStore={cellData} registry={cellRegistry} displayStore={cellDisplay} />
|
|
94
95
|
<RepeatersLayer dataStore={repeaterData} registry={repeaterRegistry} displayStore={repeaterDisplay} />
|
|
95
96
|
<RepeaterLabelsLayer dataStore={repeaterData} registry={repeaterRegistry} displayStore={repeaterDisplay} />
|
|
97
|
+
|
|
98
|
+
<FeatureSelectionControl
|
|
99
|
+
position="bottom-left"
|
|
100
|
+
cellDataStore={cellData}
|
|
101
|
+
cellDisplayStore={cellDisplay}
|
|
102
|
+
title="Feature Selection"
|
|
103
|
+
actionButtonLabel="Export Selected"
|
|
104
|
+
onAction={(ids) => console.log('Selected features:', ids)}
|
|
105
|
+
/>
|
|
96
106
|
</Map>
|
|
97
107
|
</div>
|
|
98
108
|
|
|
@@ -14,17 +14,25 @@
|
|
|
14
14
|
registry: CellRegistry;
|
|
15
15
|
displayStore: CellDisplayStore;
|
|
16
16
|
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
17
|
+
level1Options?: CellGroupingField[];
|
|
18
|
+
level2Options?: CellGroupingField[];
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
let {
|
|
21
|
+
let {
|
|
22
|
+
dataStore,
|
|
23
|
+
registry,
|
|
24
|
+
displayStore,
|
|
25
|
+
position = 'top-left',
|
|
26
|
+
level1Options = ['tech', 'status', 'siteId'],
|
|
27
|
+
level2Options = ['fband', 'frq', 'status', 'none']
|
|
28
|
+
}: Props = $props();
|
|
20
29
|
|
|
21
30
|
// Local State for Grouping
|
|
22
|
-
// let level1 = $state<CellGroupingField>('tech');
|
|
23
|
-
// let level2 = $state<CellGroupingField>('fband');
|
|
24
31
|
let includePlanned = $state(false);
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
function toggleLabels() {
|
|
34
|
+
displayStore.showLabels = !displayStore.showLabels;
|
|
35
|
+
}
|
|
28
36
|
|
|
29
37
|
// Sync includePlanned with dataStore
|
|
30
38
|
$effect(() => {
|
|
@@ -106,7 +114,17 @@
|
|
|
106
114
|
}
|
|
107
115
|
</script>
|
|
108
116
|
|
|
109
|
-
<MapControl {position} title="
|
|
117
|
+
<MapControl {position} title="Cells" icon="grid" controlWidth="320px">
|
|
118
|
+
{#snippet actions()}
|
|
119
|
+
<button
|
|
120
|
+
class="btn btn-sm btn-outline-secondary border-0 p-1 px-2"
|
|
121
|
+
title={displayStore.showLabels ? 'Hide Labels' : 'Show Labels'}
|
|
122
|
+
aria-label={displayStore.showLabels ? 'Hide Labels' : 'Show Labels'}
|
|
123
|
+
onclick={toggleLabels}
|
|
124
|
+
>
|
|
125
|
+
<i class="bi bi-tag{displayStore.showLabels ? '-fill' : ''}"></i>
|
|
126
|
+
</button>
|
|
127
|
+
{/snippet}
|
|
110
128
|
<div class="cell-filter-control">
|
|
111
129
|
<!-- Status Filter Checkbox -->
|
|
112
130
|
<div class="mb-3 px-1">
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { CellDataStore } from '../stores/cell.data.svelte';
|
|
2
2
|
import type { CellRegistry } from '../stores/cell.registry.svelte';
|
|
3
3
|
import type { CellDisplayStore } from '../stores/cell.display.svelte';
|
|
4
|
+
import type { CellGroupingField } from '../types';
|
|
4
5
|
interface Props {
|
|
5
6
|
dataStore: CellDataStore;
|
|
6
7
|
registry: CellRegistry;
|
|
7
8
|
displayStore: CellDisplayStore;
|
|
8
9
|
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
10
|
+
level1Options?: CellGroupingField[];
|
|
11
|
+
level2Options?: CellGroupingField[];
|
|
9
12
|
}
|
|
10
13
|
declare const CellFilterControl: import("svelte").Component<Props, {}, "">;
|
|
11
14
|
type CellFilterControl = ReturnType<typeof CellFilterControl>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Status Styles
|
|
3
|
+
*
|
|
4
|
+
* Visual styling for cell sector borders based on status
|
|
5
|
+
*/
|
|
6
|
+
import type { CellStatus } from '../types';
|
|
7
|
+
export interface CellStatusStyle {
|
|
8
|
+
lineType: 'solid' | 'dashed' | 'dotted';
|
|
9
|
+
lineWidth: number;
|
|
10
|
+
lineColor: string;
|
|
11
|
+
opacity: number;
|
|
12
|
+
dashArray: number[];
|
|
13
|
+
}
|
|
14
|
+
export declare const DEFAULT_STATUS_STYLES: Record<CellStatus, CellStatusStyle>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Status Styles
|
|
3
|
+
*
|
|
4
|
+
* Visual styling for cell sector borders based on status
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_STATUS_STYLES = {
|
|
7
|
+
On_Air: {
|
|
8
|
+
lineType: 'solid',
|
|
9
|
+
lineWidth: 2,
|
|
10
|
+
lineColor: '#000000',
|
|
11
|
+
opacity: 1.0,
|
|
12
|
+
dashArray: []
|
|
13
|
+
},
|
|
14
|
+
On_Air_UNDER_CONSTRUCTION: {
|
|
15
|
+
lineType: 'solid',
|
|
16
|
+
lineWidth: 2,
|
|
17
|
+
lineColor: '#FFA500',
|
|
18
|
+
opacity: 0.8,
|
|
19
|
+
dashArray: []
|
|
20
|
+
},
|
|
21
|
+
On_Air_Locked: {
|
|
22
|
+
lineType: 'solid',
|
|
23
|
+
lineWidth: 2,
|
|
24
|
+
lineColor: '#FF0000',
|
|
25
|
+
opacity: 0.9,
|
|
26
|
+
dashArray: []
|
|
27
|
+
},
|
|
28
|
+
RF_Plan_Ready: {
|
|
29
|
+
lineType: 'dashed',
|
|
30
|
+
lineWidth: 2,
|
|
31
|
+
lineColor: '#666666',
|
|
32
|
+
opacity: 0.6,
|
|
33
|
+
dashArray: [5, 5]
|
|
34
|
+
},
|
|
35
|
+
'Re-Planned_RF_Plan_Ready': {
|
|
36
|
+
lineType: 'dashed',
|
|
37
|
+
lineWidth: 2,
|
|
38
|
+
lineColor: '#888888',
|
|
39
|
+
opacity: 0.5,
|
|
40
|
+
dashArray: [5, 5]
|
|
41
|
+
},
|
|
42
|
+
Tavlati_RF_Plan_Ready: {
|
|
43
|
+
lineType: 'dashed',
|
|
44
|
+
lineWidth: 2,
|
|
45
|
+
lineColor: '#999999',
|
|
46
|
+
opacity: 0.4,
|
|
47
|
+
dashArray: [5, 5]
|
|
48
|
+
}
|
|
49
|
+
};
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { TechnologyBandKey } from './types';
|
|
8
8
|
export declare const Z_INDEX_BY_BAND: Record<TechnologyBandKey, number>;
|
|
9
|
+
export declare const BEAMWIDTH_BOOST_BY_BAND: Record<TechnologyBandKey, number>;
|
|
9
10
|
/**
|
|
10
11
|
* Base Z-Index for Mapbox layer ordering
|
|
11
12
|
* Cells should render below sites but above base map features
|
|
@@ -5,20 +5,61 @@
|
|
|
5
5
|
* Higher frequency bands typically rendered on top
|
|
6
6
|
*/
|
|
7
7
|
export const Z_INDEX_BY_BAND = {
|
|
8
|
-
//
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
//
|
|
8
|
+
// '2G_900': 1,
|
|
9
|
+
// '2G_1800': 3,
|
|
10
|
+
// '4G_700': 7,
|
|
11
|
+
// '4G_800': 9,
|
|
12
|
+
// '4G_900': 11,
|
|
13
|
+
// '4G_1800': 13,
|
|
14
|
+
// '4G_2100': 15,
|
|
15
|
+
// '4G_2600': 19,
|
|
16
|
+
// '5G_700': 23,
|
|
17
|
+
// '5G_2100': 25,
|
|
18
|
+
// '5G_3500': 27,
|
|
19
|
+
// //low
|
|
20
|
+
// '4G_700': 0,
|
|
21
|
+
// '5G_700': 2,
|
|
22
|
+
// '4G_800': 4,
|
|
23
|
+
// '2G_900': 6,
|
|
24
|
+
// '4G_900': 8,
|
|
25
|
+
// //mid
|
|
26
|
+
// '2G_1800': 14,
|
|
27
|
+
// '4G_1800': 16,
|
|
28
|
+
// '4G_2100': 18,
|
|
29
|
+
// '5G_2100': 20,
|
|
30
|
+
// '4G_2600': 22,
|
|
31
|
+
// //high
|
|
32
|
+
// '5G_3500': 28,
|
|
33
|
+
//low
|
|
34
|
+
'4G_700': 1,
|
|
35
|
+
'5G_700': 0.99,
|
|
36
|
+
'4G_800': 5,
|
|
37
|
+
'2G_900': 7,
|
|
38
|
+
'4G_900': 9,
|
|
39
|
+
//mid
|
|
40
|
+
'4G_1800': 15,
|
|
41
|
+
'2G_1800': 14.99,
|
|
42
|
+
'4G_2100': 19,
|
|
43
|
+
'5G_2100': 18.99,
|
|
44
|
+
'4G_2600': 23,
|
|
45
|
+
//high
|
|
46
|
+
'5G_3500': 25,
|
|
47
|
+
};
|
|
48
|
+
export const BEAMWIDTH_BOOST_BY_BAND = {
|
|
49
|
+
// Low band
|
|
12
50
|
'4G_700': 0,
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
51
|
+
'5G_700': 15,
|
|
52
|
+
'4G_800': 0,
|
|
53
|
+
'2G_900': 0,
|
|
54
|
+
'4G_900': 0,
|
|
55
|
+
// Mid band
|
|
56
|
+
'2G_1800': 15,
|
|
57
|
+
'4G_1800': 0,
|
|
58
|
+
'4G_2100': 0,
|
|
59
|
+
'5G_2100': 15,
|
|
60
|
+
'4G_2600': 0,
|
|
61
|
+
// High band
|
|
62
|
+
'5G_3500': 0
|
|
22
63
|
};
|
|
23
64
|
/**
|
|
24
65
|
* Base Z-Index for Mapbox layer ordering
|
|
@@ -214,13 +214,18 @@
|
|
|
214
214
|
}
|
|
215
215
|
};
|
|
216
216
|
|
|
217
|
+
const handleStyleLoad = () => {
|
|
218
|
+
addLayers();
|
|
219
|
+
updateLayer();
|
|
220
|
+
};
|
|
221
|
+
|
|
217
222
|
addLayers();
|
|
218
|
-
map.on('style.load',
|
|
223
|
+
map.on('style.load', handleStyleLoad);
|
|
219
224
|
map.on('moveend', updateLayer);
|
|
220
225
|
map.on('zoomend', updateLayer);
|
|
221
226
|
|
|
222
227
|
return () => {
|
|
223
|
-
map.off('style.load',
|
|
228
|
+
map.off('style.load', handleStyleLoad);
|
|
224
229
|
map.off('moveend', updateLayer);
|
|
225
230
|
map.off('zoomend', updateLayer);
|
|
226
231
|
if (map.getLayer(layerId)) map.removeLayer(layerId);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import type { CellDisplayStore } from '../stores/cell.display.svelte';
|
|
7
7
|
import { groupCells, getColorForGroup } from '../logic/grouping';
|
|
8
8
|
import { generateCellArc, calculateRadiusInMeters } from '../logic/geometry';
|
|
9
|
-
import { Z_INDEX_BY_BAND } from '../constants';
|
|
9
|
+
import { Z_INDEX_BY_BAND, BEAMWIDTH_BOOST_BY_BAND } from '../constants';
|
|
10
10
|
import type { TechnologyBandKey } from '../types';
|
|
11
11
|
import type mapboxgl from 'mapbox-gl';
|
|
12
12
|
|
|
@@ -34,9 +34,8 @@
|
|
|
34
34
|
if (map.getLayer(layerId)) {
|
|
35
35
|
map.setPaintProperty(layerId, 'fill-opacity', displayStore.fillOpacity);
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
37
|
+
// Note: Line styling is now data-driven from status properties
|
|
38
|
+
// but we can still apply a global multiplier via lineWidth
|
|
40
39
|
});
|
|
41
40
|
|
|
42
41
|
$effect(() => {
|
|
@@ -68,15 +67,28 @@
|
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
if (!map.getLayer(lineLayerId)) {
|
|
71
|
-
// Line Layer (Border)
|
|
70
|
+
// Line Layer (Border) - Status-based styling
|
|
72
71
|
map.addLayer({
|
|
73
72
|
id: lineLayerId,
|
|
74
73
|
type: 'line',
|
|
75
74
|
source: sourceId,
|
|
76
75
|
paint: {
|
|
77
|
-
'line-color': '
|
|
78
|
-
'line-width':
|
|
79
|
-
|
|
76
|
+
'line-color': ['get', 'lineColor'],
|
|
77
|
+
'line-width': [
|
|
78
|
+
'*',
|
|
79
|
+
['get', 'lineWidth'],
|
|
80
|
+
displayStore.lineWidth
|
|
81
|
+
],
|
|
82
|
+
'line-opacity': ['get', 'lineOpacity'],
|
|
83
|
+
'line-dasharray': [
|
|
84
|
+
'case',
|
|
85
|
+
['>', ['length', ['get', 'dashArray']], 0],
|
|
86
|
+
['get', 'dashArray'],
|
|
87
|
+
['literal', []]
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
layout: {
|
|
91
|
+
'line-sort-key': ['get', 'zIndex']
|
|
80
92
|
}
|
|
81
93
|
});
|
|
82
94
|
}
|
|
@@ -162,19 +174,23 @@
|
|
|
162
174
|
// 4. BBox Filter (Simple point check)
|
|
163
175
|
if (bounds.contains([cell.longitude, cell.latitude])) {
|
|
164
176
|
// 5. Z-Index Lookup
|
|
165
|
-
const zIndexKey = `${cell.tech}_${cell.frq}` as TechnologyBandKey;
|
|
177
|
+
const zIndexKey = `${cell.tech}_${cell.frq}` as TechnologyBandKey;
|
|
166
178
|
const zIndex = Z_INDEX_BY_BAND[zIndexKey] || 10;
|
|
167
179
|
|
|
168
180
|
// 6. Calculate Scaled Radius based on Z-Index
|
|
169
181
|
// Higher Z-index (Top layer) = Smaller radius
|
|
170
182
|
// Lower Z-index (Bottom layer) = Larger radius
|
|
171
183
|
// This ensures stacked cells are visible
|
|
172
|
-
const MAX_Z =
|
|
184
|
+
const MAX_Z = 30;
|
|
173
185
|
const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08; // 8% size diff per layer
|
|
174
186
|
const effectiveRadius = radiusMeters * scaleFactor;
|
|
175
187
|
|
|
176
|
-
// 7.
|
|
177
|
-
const
|
|
188
|
+
// 7. Apply beamwidth boost from constants
|
|
189
|
+
const beamwidthBoost = BEAMWIDTH_BOOST_BY_BAND[zIndexKey] || 0;
|
|
190
|
+
const adjustedBeamwidth = cell.beamwidth + beamwidthBoost;
|
|
191
|
+
|
|
192
|
+
// 8. Generate Arc
|
|
193
|
+
const feature = generateCellArc(cell, effectiveRadius, zIndex, style.color, adjustedBeamwidth);
|
|
178
194
|
features.push(feature);
|
|
179
195
|
}
|
|
180
196
|
}
|
|
@@ -9,4 +9,4 @@ export declare function calculateRadiusInMeters(latitude: number, zoom: number,
|
|
|
9
9
|
/**
|
|
10
10
|
* Generates a sector arc GeoJSON feature for a cell
|
|
11
11
|
*/
|
|
12
|
-
export declare function generateCellArc(cell: Cell, radiusMeters: number, zIndex: number, color: string): GeoJSON.Feature<GeoJSON.Polygon>;
|
|
12
|
+
export declare function generateCellArc(cell: Cell, radiusMeters: number, zIndex: number, color: string, beamwidthOverride?: number): GeoJSON.Feature<GeoJSON.Polygon>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as turf from '@turf/turf';
|
|
2
|
+
import { DEFAULT_STATUS_STYLES } from '../constants/statusStyles';
|
|
2
3
|
/**
|
|
3
4
|
* Calculates the radius in meters required to achieve a target pixel size
|
|
4
5
|
* at a specific latitude and zoom level.
|
|
@@ -12,24 +13,44 @@ export function calculateRadiusInMeters(latitude, zoom, targetPixelSize) {
|
|
|
12
13
|
/**
|
|
13
14
|
* Generates a sector arc GeoJSON feature for a cell
|
|
14
15
|
*/
|
|
15
|
-
export function generateCellArc(cell, radiusMeters, zIndex, color) {
|
|
16
|
+
export function generateCellArc(cell, radiusMeters, zIndex, color, beamwidthOverride) {
|
|
16
17
|
const center = [cell.longitude, cell.latitude];
|
|
17
|
-
const
|
|
18
|
-
const
|
|
18
|
+
const beamwidth = beamwidthOverride ?? cell.beamwidth;
|
|
19
|
+
const bearing1 = cell.azimuth - (beamwidth / 2);
|
|
20
|
+
const bearing2 = cell.azimuth + (beamwidth / 2);
|
|
19
21
|
// Use Turf to generate the sector
|
|
20
22
|
// Note: turf.sector takes (center, radius, bearing1, bearing2)
|
|
21
23
|
// Radius must be in kilometers for default turf units, or specify units
|
|
22
24
|
const sector = turf.sector(center, radiusMeters / 1000, bearing1, bearing2, {
|
|
23
25
|
steps: 10 // Low steps for performance, increase if jagged
|
|
24
26
|
});
|
|
27
|
+
// Get status style
|
|
28
|
+
const statusStyle = DEFAULT_STATUS_STYLES[cell.status] || DEFAULT_STATUS_STYLES['On_Air'];
|
|
25
29
|
// Attach properties for styling and interaction
|
|
26
30
|
sector.properties = {
|
|
27
|
-
id: cell.
|
|
31
|
+
id: cell.txId,
|
|
32
|
+
txId: cell.txId,
|
|
33
|
+
cellId: cell.txId,
|
|
34
|
+
cellID: cell.cellID,
|
|
28
35
|
cellName: cell.cellName,
|
|
36
|
+
siteId: cell.siteId,
|
|
37
|
+
latitude: cell.latitude,
|
|
38
|
+
longitude: cell.longitude,
|
|
39
|
+
// Cell attributes
|
|
29
40
|
tech: cell.tech,
|
|
41
|
+
band: cell.frq,
|
|
42
|
+
status: cell.status,
|
|
43
|
+
type: cell.type,
|
|
44
|
+
azimuth: cell.azimuth,
|
|
45
|
+
beamwidth: cell.beamwidth,
|
|
30
46
|
fband: cell.fband,
|
|
31
47
|
zIndex: zIndex,
|
|
32
|
-
color: color
|
|
48
|
+
color: color,
|
|
49
|
+
// Status-based line styling
|
|
50
|
+
lineColor: statusStyle.lineColor,
|
|
51
|
+
lineWidth: statusStyle.lineWidth,
|
|
52
|
+
lineOpacity: statusStyle.opacity,
|
|
53
|
+
dashArray: statusStyle.dashArray
|
|
33
54
|
};
|
|
34
55
|
return sector;
|
|
35
56
|
}
|
|
@@ -74,9 +74,23 @@
|
|
|
74
74
|
};
|
|
75
75
|
return labels[field] || field;
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
function toggleLabels() {
|
|
79
|
+
displayStore.showLabels = !displayStore.showLabels;
|
|
80
|
+
}
|
|
77
81
|
</script>
|
|
78
82
|
|
|
79
83
|
<MapControl {position} title="Repeaters" icon="router" controlWidth="320px">
|
|
84
|
+
{#snippet actions()}
|
|
85
|
+
<button
|
|
86
|
+
class="btn btn-sm btn-outline-secondary border-0 p-1 px-2"
|
|
87
|
+
title={displayStore.showLabels ? 'Hide Labels' : 'Show Labels'}
|
|
88
|
+
aria-label={displayStore.showLabels ? 'Hide Labels' : 'Show Labels'}
|
|
89
|
+
onclick={toggleLabels}
|
|
90
|
+
>
|
|
91
|
+
<i class="bi bi-tag{displayStore.showLabels ? '-fill' : ''}"></i>
|
|
92
|
+
</button>
|
|
93
|
+
{/snippet}
|
|
80
94
|
<div class="card shadow-sm border-0">
|
|
81
95
|
<!-- <div class="card-header bg-white py-2 d-flex justify-content-between align-items-center">
|
|
82
96
|
<h6 class="mb-0 fw-bold text-primary">
|
|
@@ -143,35 +143,66 @@
|
|
|
143
143
|
features
|
|
144
144
|
};
|
|
145
145
|
|
|
146
|
+
// 4. Update Source
|
|
146
147
|
const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource;
|
|
147
148
|
if (source) {
|
|
148
149
|
source.setData(geojson);
|
|
149
|
-
} else {
|
|
150
|
-
map.addSource(sourceId, {
|
|
151
|
-
type: 'geojson',
|
|
152
|
-
data: geojson
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
map.addLayer({
|
|
156
|
-
id: layerId,
|
|
157
|
-
type: 'symbol',
|
|
158
|
-
source: sourceId,
|
|
159
|
-
layout: {
|
|
160
|
-
'text-field': ['get', 'label'],
|
|
161
|
-
'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
|
|
162
|
-
'text-size': displayStore.labelFontSize,
|
|
163
|
-
'text-offset': ['get', 'offset'],
|
|
164
|
-
'text-anchor': 'center',
|
|
165
|
-
'text-justify': 'auto',
|
|
166
|
-
'text-allow-overlap': false,
|
|
167
|
-
'text-ignore-placement': false
|
|
168
|
-
},
|
|
169
|
-
paint: {
|
|
170
|
-
'text-color': displayStore.labelColor,
|
|
171
|
-
'text-halo-color': displayStore.labelHaloColor,
|
|
172
|
-
'text-halo-width': displayStore.labelHaloWidth
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
150
|
}
|
|
176
151
|
}
|
|
152
|
+
|
|
153
|
+
// Initial Setup
|
|
154
|
+
$effect(() => {
|
|
155
|
+
const map = mapStore.map;
|
|
156
|
+
if (!map) return;
|
|
157
|
+
|
|
158
|
+
const addLayers = () => {
|
|
159
|
+
if (!map.getSource(sourceId)) {
|
|
160
|
+
map.addSource(sourceId, {
|
|
161
|
+
type: 'geojson',
|
|
162
|
+
data: { type: 'FeatureCollection', features: [] }
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!map.getLayer(layerId)) {
|
|
167
|
+
map.addLayer({
|
|
168
|
+
id: layerId,
|
|
169
|
+
type: 'symbol',
|
|
170
|
+
source: sourceId,
|
|
171
|
+
layout: {
|
|
172
|
+
'text-field': ['get', 'label'],
|
|
173
|
+
'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
|
|
174
|
+
'text-size': displayStore.labelFontSize,
|
|
175
|
+
'text-offset': ['get', 'offset'],
|
|
176
|
+
'text-anchor': 'center',
|
|
177
|
+
'text-justify': 'auto',
|
|
178
|
+
'text-allow-overlap': false,
|
|
179
|
+
'text-ignore-placement': false
|
|
180
|
+
},
|
|
181
|
+
paint: {
|
|
182
|
+
'text-color': displayStore.labelColor,
|
|
183
|
+
'text-halo-color': displayStore.labelHaloColor,
|
|
184
|
+
'text-halo-width': displayStore.labelHaloWidth
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const handleStyleLoad = () => {
|
|
191
|
+
addLayers();
|
|
192
|
+
updateLayer();
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
addLayers();
|
|
196
|
+
map.on('style.load', handleStyleLoad);
|
|
197
|
+
map.on('moveend', updateLayer);
|
|
198
|
+
map.on('zoomend', updateLayer);
|
|
199
|
+
|
|
200
|
+
return () => {
|
|
201
|
+
map.off('style.load', handleStyleLoad);
|
|
202
|
+
map.off('moveend', updateLayer);
|
|
203
|
+
map.off('zoomend', updateLayer);
|
|
204
|
+
if (map.getLayer(layerId)) map.removeLayer(layerId);
|
|
205
|
+
if (map.getSource(sourceId)) map.removeSource(sourceId);
|
|
206
|
+
};
|
|
207
|
+
});
|
|
177
208
|
</script>
|