@smartnet360/svelte-components 0.0.100 → 0.0.102
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/site-check/SiteCheck.svelte +54 -272
- package/dist/apps/site-check/SiteCheckControls.svelte +294 -0
- package/dist/apps/site-check/SiteCheckControls.svelte.d.ts +30 -0
- package/dist/map-v2/demo/DemoMap.svelte +39 -7
- package/dist/map-v2/features/cells/utils/cellGeoJSON.js +1 -0
- package/dist/map-v2/shared/controls/FeatureSelectionControl.svelte +20 -25
- package/dist/map-v2/shared/controls/FeatureSelectionControl.svelte.d.ts +2 -4
- package/dist/map-v3/demo/DemoMap.svelte +31 -5
- package/dist/map-v3/demo/demo-cells.js +51 -22
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte +29 -9
- package/dist/map-v3/features/cells/logic/geometry.js +3 -0
- package/dist/map-v3/features/cells/stores/cell.data.svelte.d.ts +27 -0
- package/dist/map-v3/features/cells/stores/cell.data.svelte.js +65 -0
- package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte +82 -65
- package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte.d.ts +5 -9
- package/dist/map-v3/features/selection/index.d.ts +1 -2
- package/dist/map-v3/features/selection/index.js +0 -1
- package/dist/map-v3/features/selection/stores/selection.store.svelte.d.ts +44 -15
- package/dist/map-v3/features/selection/stores/selection.store.svelte.js +163 -40
- package/dist/map-v3/features/selection/types.d.ts +4 -2
- package/dist/shared/ResizableSplitPanel.svelte +175 -0
- package/dist/shared/ResizableSplitPanel.svelte.d.ts +17 -0
- package/package.json +1 -1
- package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte +0 -209
- package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte.d.ts +0 -13
|
@@ -87,9 +87,19 @@
|
|
|
87
87
|
alert(`Selected ${siteIds.length} sites:\n${siteIds.join(', ')}`);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// Handler for
|
|
91
|
-
function
|
|
92
|
-
alert(`
|
|
90
|
+
// Handler for cluster processing
|
|
91
|
+
function handleProcessCluster(featureIds: string[]) {
|
|
92
|
+
alert(`Processing cluster with ${featureIds.length} features:\n${featureIds.join(', ')}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Handler for data export
|
|
96
|
+
function handleExportData(featureIds: string[]) {
|
|
97
|
+
alert(`Exporting ${featureIds.length} features:\n${featureIds.join(', ')}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handler for feature analysis
|
|
101
|
+
function handleAnalyzeFeatures(featureIds: string[]) {
|
|
102
|
+
alert(`Analyzing ${featureIds.length} features:\n${featureIds.join(', ')}`);
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
// Cell filter grouping configuration
|
|
@@ -231,12 +241,34 @@
|
|
|
231
241
|
<FeatureSelectionControl
|
|
232
242
|
position="bottom-left"
|
|
233
243
|
title="Cluster Tool"
|
|
234
|
-
icon="
|
|
244
|
+
icon="bi bi-graph-up"
|
|
235
245
|
iconOnlyWhenCollapsed={useIconHeaders}
|
|
236
|
-
onAction={handleProcessFeatures}
|
|
237
|
-
actionButtonLabel="Process Cluster"
|
|
238
246
|
featureIcon="pin-map-fill"
|
|
239
|
-
|
|
247
|
+
>
|
|
248
|
+
{#snippet children(selectedIds)}
|
|
249
|
+
<button
|
|
250
|
+
class="btn btn-primary"
|
|
251
|
+
disabled={selectedIds.length === 0}
|
|
252
|
+
onclick={() => handleProcessCluster(selectedIds)}
|
|
253
|
+
>
|
|
254
|
+
<i class="bi bi-lightning-charge-fill"></i> Process Cluster
|
|
255
|
+
</button>
|
|
256
|
+
<button
|
|
257
|
+
class="btn btn-success"
|
|
258
|
+
disabled={selectedIds.length === 0}
|
|
259
|
+
onclick={() => handleExportData(selectedIds)}
|
|
260
|
+
>
|
|
261
|
+
<i class="bi bi-download"></i> Export Data
|
|
262
|
+
</button>
|
|
263
|
+
<button
|
|
264
|
+
class="btn btn-info"
|
|
265
|
+
disabled={selectedIds.length === 0}
|
|
266
|
+
onclick={() => handleAnalyzeFeatures(selectedIds)}
|
|
267
|
+
>
|
|
268
|
+
<i class="bi bi-graph-up"></i> Analyze
|
|
269
|
+
</button>
|
|
270
|
+
{/snippet}
|
|
271
|
+
</FeatureSelectionControl>
|
|
240
272
|
|
|
241
273
|
<!-- Unified feature settings control - Sites, Cells, and Repeaters -->
|
|
242
274
|
<FeatureSettingsControl
|
|
@@ -42,6 +42,7 @@ export function cellsToGeoJSON(cells, currentZoom, baseRadius = 500, groupColorM
|
|
|
42
42
|
// Build feature with styling properties
|
|
43
43
|
return {
|
|
44
44
|
type: 'Feature',
|
|
45
|
+
id: Number(cell.cellName), // Numeric ID for feature-state (cellName is numeric string)
|
|
45
46
|
geometry: arc.geometry,
|
|
46
47
|
properties: {
|
|
47
48
|
// Cell identification
|
|
@@ -28,16 +28,14 @@
|
|
|
28
28
|
icon?: string;
|
|
29
29
|
/** Show icon when collapsed (default: true) */
|
|
30
30
|
iconOnlyWhenCollapsed?: boolean;
|
|
31
|
-
/** Callback when action button clicked */
|
|
32
|
-
onAction?: (featureIds: string[]) => void;
|
|
33
|
-
/** Action button label */
|
|
34
|
-
actionButtonLabel?: string;
|
|
35
31
|
/** Feature icon (default: geo-alt-fill) */
|
|
36
32
|
featureIcon?: string;
|
|
37
33
|
/** Available property names to use as ID (default: ['id', 'siteId', 'cellName']) */
|
|
38
34
|
idPropertyOptions?: string[];
|
|
39
35
|
/** Default property to use as ID (default: 'id') */
|
|
40
36
|
defaultIdProperty?: string;
|
|
37
|
+
/** Slot for custom action buttons that receive selected feature IDs */
|
|
38
|
+
children?: import('svelte').Snippet<[string[]]>;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
let {
|
|
@@ -45,11 +43,10 @@
|
|
|
45
43
|
title = 'Cluster Tool',
|
|
46
44
|
icon = 'speedometer2',
|
|
47
45
|
iconOnlyWhenCollapsed = true,
|
|
48
|
-
onAction,
|
|
49
|
-
actionButtonLabel = 'Process Cluster',
|
|
50
46
|
featureIcon = 'geo-alt-fill',
|
|
51
47
|
idPropertyOptions = ['none','siteId','sectorId', 'cellName','id'],
|
|
52
|
-
defaultIdProperty = 'none'
|
|
48
|
+
defaultIdProperty = 'none',
|
|
49
|
+
children
|
|
53
50
|
}: Props = $props();
|
|
54
51
|
|
|
55
52
|
// Get map from context
|
|
@@ -228,13 +225,6 @@
|
|
|
228
225
|
console.error('Failed to copy:', err);
|
|
229
226
|
}
|
|
230
227
|
}
|
|
231
|
-
|
|
232
|
-
function handleAction() {
|
|
233
|
-
if (onAction && hasSelection) {
|
|
234
|
-
const ids = store.getSelectedIds();
|
|
235
|
-
onAction(ids);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
228
|
</script>
|
|
239
229
|
|
|
240
230
|
<MapControl {position} {title} {icon} {iconOnlyWhenCollapsed} collapsible={true} onCollapseToggle={handleCollapseToggle}>
|
|
@@ -316,17 +306,10 @@
|
|
|
316
306
|
</div>
|
|
317
307
|
{/if}
|
|
318
308
|
|
|
319
|
-
<!-- Action
|
|
320
|
-
{#if
|
|
321
|
-
<div class="mt-3">
|
|
322
|
-
|
|
323
|
-
type="button"
|
|
324
|
-
class="btn btn-primary w-100"
|
|
325
|
-
disabled={!hasSelection}
|
|
326
|
-
onclick={handleAction}
|
|
327
|
-
>
|
|
328
|
-
<i class="bi bi-lightning-charge-fill"></i> {actionButtonLabel}
|
|
329
|
-
</button>
|
|
309
|
+
<!-- Custom Action Buttons Slot -->
|
|
310
|
+
{#if children}
|
|
311
|
+
<div class="mt-3 action-slot">
|
|
312
|
+
{@render children(store.getSelectedIds())}
|
|
330
313
|
</div>
|
|
331
314
|
{/if}
|
|
332
315
|
</div>
|
|
@@ -435,6 +418,18 @@
|
|
|
435
418
|
background-color: #ffcccc;
|
|
436
419
|
}
|
|
437
420
|
|
|
421
|
+
/* Action slot styles */
|
|
422
|
+
.action-slot {
|
|
423
|
+
display: flex;
|
|
424
|
+
flex-direction: column;
|
|
425
|
+
gap: 0.5rem;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* Ensure action slot buttons maintain Bootstrap styling */
|
|
429
|
+
.action-slot :global(.btn) {
|
|
430
|
+
width: 100%;
|
|
431
|
+
}
|
|
432
|
+
|
|
438
433
|
/* Ensure primary action button keeps Bootstrap styling inside Mapbox control */
|
|
439
434
|
.feature-selection-control .btn-primary {
|
|
440
435
|
background-color: var(--bs-btn-bg, var(--bs-primary));
|
|
@@ -7,16 +7,14 @@ interface Props {
|
|
|
7
7
|
icon?: string;
|
|
8
8
|
/** Show icon when collapsed (default: true) */
|
|
9
9
|
iconOnlyWhenCollapsed?: boolean;
|
|
10
|
-
/** Callback when action button clicked */
|
|
11
|
-
onAction?: (featureIds: string[]) => void;
|
|
12
|
-
/** Action button label */
|
|
13
|
-
actionButtonLabel?: string;
|
|
14
10
|
/** Feature icon (default: geo-alt-fill) */
|
|
15
11
|
featureIcon?: string;
|
|
16
12
|
/** Available property names to use as ID (default: ['id', 'siteId', 'cellName']) */
|
|
17
13
|
idPropertyOptions?: string[];
|
|
18
14
|
/** Default property to use as ID (default: 'id') */
|
|
19
15
|
defaultIdProperty?: string;
|
|
16
|
+
/** Slot for custom action buttons that receive selected feature IDs */
|
|
17
|
+
children?: import('svelte').Snippet<[string[]]>;
|
|
20
18
|
}
|
|
21
19
|
declare const FeatureSelectionControl: import("svelte").Component<Props, {}, "">;
|
|
22
20
|
type FeatureSelectionControl = ReturnType<typeof FeatureSelectionControl>;
|
|
@@ -102,11 +102,37 @@
|
|
|
102
102
|
<FeatureSelectionControl
|
|
103
103
|
position="bottom-left"
|
|
104
104
|
cellDataStore={cellData}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
title="Cell Selection"
|
|
106
|
+
featureIcon="📡"
|
|
107
|
+
defaultSelectionMode="site"
|
|
108
|
+
>
|
|
109
|
+
{#snippet children(selectedIds)}
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
class="btn btn-primary w-100 mb-2"
|
|
113
|
+
disabled={selectedIds.length === 0}
|
|
114
|
+
onclick={() => console.log('Process Selected:', selectedIds)}
|
|
115
|
+
>
|
|
116
|
+
<i class="bi bi-gear-fill"></i> Process ({selectedIds.length})
|
|
117
|
+
</button>
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
class="btn btn-outline-primary w-100 mb-2"
|
|
121
|
+
disabled={selectedIds.length === 0}
|
|
122
|
+
onclick={() => console.log('Export Selected:', selectedIds)}
|
|
123
|
+
>
|
|
124
|
+
<i class="bi bi-download"></i> Export Data
|
|
125
|
+
</button>
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
class="btn btn-outline-secondary w-100"
|
|
129
|
+
disabled={selectedIds.length === 0}
|
|
130
|
+
onclick={() => console.log('Analyze Selected:', selectedIds)}
|
|
131
|
+
>
|
|
132
|
+
<i class="bi bi-graph-up"></i> Analyze
|
|
133
|
+
</button>
|
|
134
|
+
{/snippet}
|
|
135
|
+
</FeatureSelectionControl>
|
|
110
136
|
</Map>
|
|
111
137
|
</div>
|
|
112
138
|
|
|
@@ -87,8 +87,13 @@ const TECH_BANDS = [
|
|
|
87
87
|
{ tech: '5G', band: '2100', fband: '5G-2100' },
|
|
88
88
|
{ tech: '5G', band: '3500', fband: '5G-3500' }
|
|
89
89
|
];
|
|
90
|
-
// Three sector azimuths
|
|
91
|
-
|
|
90
|
+
// Three sector azimuths with sector numbers
|
|
91
|
+
// Sector 1 = 0°, Sector 2 = 120°, Sector 3 = 240°
|
|
92
|
+
const SECTORS = [
|
|
93
|
+
{ azimuth: 0, sectorNum: 1 },
|
|
94
|
+
{ azimuth: 120, sectorNum: 2 },
|
|
95
|
+
{ azimuth: 240, sectorNum: 3 }
|
|
96
|
+
];
|
|
92
97
|
// Status rotation for variety
|
|
93
98
|
const STATUSES = [
|
|
94
99
|
'On_Air',
|
|
@@ -104,6 +109,18 @@ const STATUSES = [
|
|
|
104
109
|
'On_Air',
|
|
105
110
|
'On_Air'
|
|
106
111
|
];
|
|
112
|
+
/**
|
|
113
|
+
* Generate 7-digit cellName from site and sector and band index
|
|
114
|
+
* Format: SSSS S BB
|
|
115
|
+
* SSSS = 4-digit site ID
|
|
116
|
+
* S = 1-digit sector number (1, 2, or 3)
|
|
117
|
+
* BB = 2-digit band/cell index (41-52 for 11 tech-bands)
|
|
118
|
+
*/
|
|
119
|
+
function generateCellName(siteNum, sectorNum, bandIndex) {
|
|
120
|
+
const siteId = String(siteNum + 1000).padStart(4, '0'); // Start sites from 1000
|
|
121
|
+
const cellSuffix = String(41 + bandIndex).padStart(2, '0'); // Bands: 41-52
|
|
122
|
+
return `${siteId}${sectorNum}${cellSuffix}`;
|
|
123
|
+
}
|
|
107
124
|
/**
|
|
108
125
|
* Generate demo cells with varied density patterns in circular distribution
|
|
109
126
|
* Creates density zones radiating from center with random placement
|
|
@@ -144,23 +161,26 @@ for (let attempt = 0; attempt < NUM_SITES * 3 && actualSiteIndex < NUM_SITES; at
|
|
|
144
161
|
const siteLng = addJitter(lng, jitterAmount);
|
|
145
162
|
// Record position
|
|
146
163
|
usedPositions.push({ lat: siteLat, lng: siteLng, minSpacing });
|
|
147
|
-
|
|
164
|
+
// 4-digit site ID (starting from 1000)
|
|
165
|
+
const siteNum = actualSiteIndex;
|
|
166
|
+
const siteId = String(siteNum + 1000).padStart(4, '0');
|
|
148
167
|
actualSiteIndex++;
|
|
149
168
|
// Generate 3 sectors per site (with some random 1 or 2 sector sites)
|
|
150
169
|
const numSectors = Math.random() < 0.1 ? (Math.random() < 0.5 ? 1 : 2) : 3; // 10% chance of 1-2 sectors
|
|
151
|
-
const sectorsToGenerate =
|
|
152
|
-
sectorsToGenerate.forEach((
|
|
153
|
-
// Generate
|
|
154
|
-
TECH_BANDS.forEach((techBand,
|
|
155
|
-
|
|
156
|
-
const
|
|
170
|
+
const sectorsToGenerate = SECTORS.slice(0, numSectors);
|
|
171
|
+
sectorsToGenerate.forEach((sector) => {
|
|
172
|
+
// Generate 11 tech-bands per sector (indexes 0-10)
|
|
173
|
+
TECH_BANDS.forEach((techBand, bandIndex) => {
|
|
174
|
+
// Generate 7-digit cellName: SSSS + S + BB (e.g., "1000141")
|
|
175
|
+
const cellName = generateCellName(siteNum, sector.sectorNum, bandIndex);
|
|
176
|
+
const status = STATUSES[bandIndex];
|
|
157
177
|
demoCells.push({
|
|
158
178
|
// Core properties
|
|
159
|
-
id:
|
|
160
|
-
txId:
|
|
161
|
-
cellID:
|
|
162
|
-
cellID2G: techBand.tech === '2G' ?
|
|
163
|
-
cellName:
|
|
179
|
+
id: cellName,
|
|
180
|
+
txId: cellName,
|
|
181
|
+
cellID: cellName,
|
|
182
|
+
cellID2G: techBand.tech === '2G' ? cellName : '',
|
|
183
|
+
cellName: cellName,
|
|
164
184
|
siteId: siteId,
|
|
165
185
|
tech: techBand.tech,
|
|
166
186
|
fband: techBand.fband,
|
|
@@ -169,13 +189,13 @@ for (let attempt = 0; attempt < NUM_SITES * 3 && actualSiteIndex < NUM_SITES; at
|
|
|
169
189
|
status: status,
|
|
170
190
|
onAirDate: '2024-01-15',
|
|
171
191
|
// 2G specific
|
|
172
|
-
bcch: techBand.tech === '2G' ? 100 +
|
|
173
|
-
ctrlid: techBand.tech === '2G' ? `CTRL-${
|
|
192
|
+
bcch: techBand.tech === '2G' ? 100 + bandIndex : 0,
|
|
193
|
+
ctrlid: techBand.tech === '2G' ? `CTRL-${cellName}` : '',
|
|
174
194
|
// 4G specific
|
|
175
|
-
dlEarfn: techBand.tech === '4G' ? 6200 +
|
|
195
|
+
dlEarfn: techBand.tech === '4G' ? 6200 + bandIndex * 100 : 0,
|
|
176
196
|
// Physical properties
|
|
177
197
|
antenna: 'DEMO-ANTENNA-MODEL',
|
|
178
|
-
azimuth: azimuth,
|
|
198
|
+
azimuth: sector.azimuth,
|
|
179
199
|
height: 30, // 30 meters antenna height
|
|
180
200
|
electricalTilt: '3',
|
|
181
201
|
beamwidth: BEAMWIDTH,
|
|
@@ -186,7 +206,7 @@ for (let attempt = 0; attempt < NUM_SITES * 3 && actualSiteIndex < NUM_SITES; at
|
|
|
186
206
|
siteLatitude: siteLat,
|
|
187
207
|
siteLongitude: siteLng,
|
|
188
208
|
// Planning
|
|
189
|
-
comment: `Demo ${techBand.tech} ${techBand.band} cell at azimuth ${azimuth}°`,
|
|
209
|
+
comment: `Demo ${techBand.tech} ${techBand.band} cell at azimuth ${sector.azimuth}°`,
|
|
190
210
|
planner: 'Demo User',
|
|
191
211
|
// Atoll properties
|
|
192
212
|
atollETP: 43.0,
|
|
@@ -194,7 +214,7 @@ for (let attempt = 0; attempt < NUM_SITES * 3 && actualSiteIndex < NUM_SITES; at
|
|
|
194
214
|
atollRS: 500.0 + (techBand.band === '700' ? 200 : 0), // Lower freq = longer range
|
|
195
215
|
atollBW: parseFloat(techBand.band) / 100, // Simplified bandwidth
|
|
196
216
|
// Network properties
|
|
197
|
-
cellId3: `${
|
|
217
|
+
cellId3: `${cellName}-3G`,
|
|
198
218
|
nwtP1: 20,
|
|
199
219
|
nwtP2: 40,
|
|
200
220
|
pci1: (cellCounter % 504), // Physical Cell ID for LTE
|
|
@@ -204,14 +224,23 @@ for (let attempt = 0; attempt < NUM_SITES * 3 && actualSiteIndex < NUM_SITES; at
|
|
|
204
224
|
other: {
|
|
205
225
|
demoCell: true,
|
|
206
226
|
siteNumber: actualSiteIndex,
|
|
207
|
-
sector:
|
|
227
|
+
sector: sector.sectorNum,
|
|
208
228
|
techBandKey: `${techBand.tech}_${techBand.band}`,
|
|
209
229
|
radius: normalizedRadius,
|
|
210
230
|
densityZone: zone.name
|
|
211
231
|
},
|
|
212
|
-
customSubgroup: `Sector-${
|
|
232
|
+
customSubgroup: `Sector-${sector.sectorNum}`
|
|
213
233
|
});
|
|
214
234
|
cellCounter++;
|
|
215
235
|
});
|
|
216
236
|
});
|
|
217
237
|
}
|
|
238
|
+
// Summary of generated data structure
|
|
239
|
+
console.log(`[Demo Data] Generated ${demoCells.length} cells across ${actualSiteIndex} sites`);
|
|
240
|
+
console.log(`[Demo Data] CellName format: 7-digit numeric (e.g., "1000141")`);
|
|
241
|
+
console.log(`[Demo Data] Structure: SSSS (site) + S (sector 1-3) + BB (band 41-51)`);
|
|
242
|
+
console.log(`[Demo Data] Site ID range: 1000-${1000 + actualSiteIndex - 1}`);
|
|
243
|
+
console.log(`[Demo Data] Example Site 1000:`);
|
|
244
|
+
console.log(` - Sector 1 (0°): 1000141-1000151 (11 bands)`);
|
|
245
|
+
console.log(` - Sector 2 (120°): 1000241-1000251 (11 bands)`);
|
|
246
|
+
console.log(` - Sector 3 (240°): 1000341-1000351 (11 bands)`);
|
|
@@ -66,24 +66,44 @@
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
if (!map.getLayer(lineLayerId)) {
|
|
69
|
-
// Line Layer (Border) - Status-based styling
|
|
69
|
+
// Line Layer (Border) - Status-based styling with feature-state support
|
|
70
70
|
map.addLayer({
|
|
71
71
|
id: lineLayerId,
|
|
72
72
|
type: 'line',
|
|
73
73
|
source: sourceId,
|
|
74
74
|
paint: {
|
|
75
|
-
'line-color': [
|
|
75
|
+
'line-color': [
|
|
76
|
+
'case',
|
|
77
|
+
['boolean', ['feature-state', 'selected'], false],
|
|
78
|
+
'#FF6B00', // Selected: orange
|
|
79
|
+
['get', 'lineColor'] // Default: status color
|
|
80
|
+
],
|
|
76
81
|
'line-width': [
|
|
77
|
-
'
|
|
78
|
-
['
|
|
79
|
-
|
|
82
|
+
'case',
|
|
83
|
+
['boolean', ['feature-state', 'selected'], false],
|
|
84
|
+
4, // Selected: thick
|
|
85
|
+
[
|
|
86
|
+
'*',
|
|
87
|
+
['get', 'lineWidth'],
|
|
88
|
+
displayStore.lineWidth
|
|
89
|
+
] // Default: scaled by display setting
|
|
90
|
+
],
|
|
91
|
+
'line-opacity': [
|
|
92
|
+
'case',
|
|
93
|
+
['boolean', ['feature-state', 'selected'], false],
|
|
94
|
+
1, // Selected: full opacity
|
|
95
|
+
['get', 'lineOpacity'] // Default: status opacity
|
|
80
96
|
],
|
|
81
|
-
'line-opacity': ['get', 'lineOpacity'],
|
|
82
97
|
'line-dasharray': [
|
|
83
98
|
'case',
|
|
84
|
-
['
|
|
85
|
-
['
|
|
86
|
-
[
|
|
99
|
+
['boolean', ['feature-state', 'selected'], false],
|
|
100
|
+
['literal', []], // Selected: solid line
|
|
101
|
+
[
|
|
102
|
+
'case',
|
|
103
|
+
['>', ['length', ['get', 'dashArray']], 0],
|
|
104
|
+
['get', 'dashArray'],
|
|
105
|
+
['literal', []]
|
|
106
|
+
] // Default: status dash
|
|
87
107
|
]
|
|
88
108
|
},
|
|
89
109
|
layout: {
|
|
@@ -79,6 +79,9 @@ export function generateCellArc(cell, radiusMeters, zIndex, color, beamwidthOver
|
|
|
79
79
|
const sector = turf.sector(center, radiusMeters / 1000, bearing1, bearing2, {
|
|
80
80
|
steps: 10 // Low steps for performance, increase if jagged
|
|
81
81
|
});
|
|
82
|
+
// Set numeric ID at feature level for Mapbox feature-state support
|
|
83
|
+
// cellName is a numeric string (e.g., "4080141"), convert to number
|
|
84
|
+
sector.id = Number(cell.cellName);
|
|
82
85
|
// Get status style
|
|
83
86
|
const statusStyle = DEFAULT_STATUS_STYLES[cell.status] || DEFAULT_STATUS_STYLES['On_Air'];
|
|
84
87
|
// Attach properties for styling and interaction
|
|
@@ -4,8 +4,35 @@ export declare class CellDataStore {
|
|
|
4
4
|
rawCells: Cell[];
|
|
5
5
|
filterOnAir: boolean;
|
|
6
6
|
siteDistanceStore: SiteDistanceStore;
|
|
7
|
+
private _siteToCellsMap;
|
|
8
|
+
private _sectorToCellsMap;
|
|
9
|
+
private _cellNameToIdMap;
|
|
7
10
|
constructor();
|
|
8
11
|
setCells(cells: Cell[]): void;
|
|
12
|
+
/**
|
|
13
|
+
* Rebuild lookup maps when cell data changes
|
|
14
|
+
*/
|
|
15
|
+
private rebuildLookupMaps;
|
|
9
16
|
get filteredCells(): Cell[];
|
|
17
|
+
/**
|
|
18
|
+
* Get all cell names belonging to a site (by site ID)
|
|
19
|
+
*/
|
|
20
|
+
getCellsBySiteId(siteId: string): string[];
|
|
21
|
+
/**
|
|
22
|
+
* Get all cell names belonging to a sector (by sector ID)
|
|
23
|
+
*/
|
|
24
|
+
getCellsBySectorId(sectorId: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Get numeric ID for a cell name
|
|
27
|
+
*/
|
|
28
|
+
getNumericId(cellName: string): number | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Extract site ID from cell name (first 4 digits)
|
|
31
|
+
*/
|
|
32
|
+
getSiteIdFromCellName(cellName: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Extract sector ID from cell name (first 5 digits)
|
|
35
|
+
*/
|
|
36
|
+
getSectorIdFromCellName(cellName: string): string;
|
|
10
37
|
}
|
|
11
38
|
export declare function createCellDataStore(): CellDataStore;
|
|
@@ -4,6 +4,10 @@ export class CellDataStore {
|
|
|
4
4
|
filterOnAir = $state(false);
|
|
5
5
|
// Internal site distance store for auto-sizing
|
|
6
6
|
siteDistanceStore;
|
|
7
|
+
// Cached lookup maps (rebuilt when cells change)
|
|
8
|
+
_siteToCellsMap = $state(new Map());
|
|
9
|
+
_sectorToCellsMap = $state(new Map());
|
|
10
|
+
_cellNameToIdMap = $state(new Map());
|
|
7
11
|
constructor() {
|
|
8
12
|
this.siteDistanceStore = new SiteDistanceStore();
|
|
9
13
|
}
|
|
@@ -11,12 +15,73 @@ export class CellDataStore {
|
|
|
11
15
|
this.rawCells = cells;
|
|
12
16
|
// Automatically update site distances when cells are loaded
|
|
13
17
|
this.siteDistanceStore.updateDistances(cells);
|
|
18
|
+
// Rebuild lookup maps
|
|
19
|
+
this.rebuildLookupMaps();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Rebuild lookup maps when cell data changes
|
|
23
|
+
*/
|
|
24
|
+
rebuildLookupMaps() {
|
|
25
|
+
console.log('[CellDataStore] Rebuilding lookup maps for', this.rawCells.length, 'cells');
|
|
26
|
+
// Clear existing maps
|
|
27
|
+
this._siteToCellsMap.clear();
|
|
28
|
+
this._sectorToCellsMap.clear();
|
|
29
|
+
this._cellNameToIdMap.clear();
|
|
30
|
+
// Build all maps in one pass
|
|
31
|
+
for (const cell of this.rawCells) {
|
|
32
|
+
const siteId = cell.cellName.substring(0, 4);
|
|
33
|
+
const sectorId = cell.cellName.substring(0, 5);
|
|
34
|
+
const numericId = Number(cell.cellName);
|
|
35
|
+
// Site map
|
|
36
|
+
if (!this._siteToCellsMap.has(siteId)) {
|
|
37
|
+
this._siteToCellsMap.set(siteId, []);
|
|
38
|
+
}
|
|
39
|
+
this._siteToCellsMap.get(siteId).push(cell.cellName);
|
|
40
|
+
// Sector map
|
|
41
|
+
if (!this._sectorToCellsMap.has(sectorId)) {
|
|
42
|
+
this._sectorToCellsMap.set(sectorId, []);
|
|
43
|
+
}
|
|
44
|
+
this._sectorToCellsMap.get(sectorId).push(cell.cellName);
|
|
45
|
+
// Cell name to ID map
|
|
46
|
+
this._cellNameToIdMap.set(cell.cellName, numericId);
|
|
47
|
+
}
|
|
48
|
+
console.log('[CellDataStore] Lookup maps built:', this._siteToCellsMap.size, 'sites,', this._sectorToCellsMap.size, 'sectors,', this._cellNameToIdMap.size, 'cells');
|
|
14
49
|
}
|
|
15
50
|
get filteredCells() {
|
|
16
51
|
if (!this.filterOnAir)
|
|
17
52
|
return this.rawCells;
|
|
18
53
|
return this.rawCells.filter(c => c.status === 'On_Air');
|
|
19
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Get all cell names belonging to a site (by site ID)
|
|
57
|
+
*/
|
|
58
|
+
getCellsBySiteId(siteId) {
|
|
59
|
+
return this._siteToCellsMap.get(siteId) || [];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get all cell names belonging to a sector (by sector ID)
|
|
63
|
+
*/
|
|
64
|
+
getCellsBySectorId(sectorId) {
|
|
65
|
+
return this._sectorToCellsMap.get(sectorId) || [];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get numeric ID for a cell name
|
|
69
|
+
*/
|
|
70
|
+
getNumericId(cellName) {
|
|
71
|
+
return this._cellNameToIdMap.get(cellName);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Extract site ID from cell name (first 4 digits)
|
|
75
|
+
*/
|
|
76
|
+
getSiteIdFromCellName(cellName) {
|
|
77
|
+
return cellName.substring(0, 4);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Extract sector ID from cell name (first 5 digits)
|
|
81
|
+
*/
|
|
82
|
+
getSectorIdFromCellName(cellName) {
|
|
83
|
+
return cellName.substring(0, 5);
|
|
84
|
+
}
|
|
20
85
|
}
|
|
21
86
|
export function createCellDataStore() {
|
|
22
87
|
return new CellDataStore();
|