@smartnet360/svelte-components 0.0.75 → 0.0.77
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-v2/demo/demo-cells.js +1 -1
- package/dist/map-v2/features/cells/controls/CellFilterControl.svelte +27 -12
- package/dist/map-v2/features/cells/layers/CellsLayer.svelte +127 -106
- package/dist/map-v2/features/cells/utils/cellGeoJSON.js +2 -0
- package/dist/map-v2/features/repeaters/constants/techBandZOrder.d.ts +4 -17
- package/dist/map-v2/features/repeaters/constants/techBandZOrder.js +17 -34
- package/dist/map-v2/features/repeaters/index.d.ts +1 -1
- package/dist/map-v2/features/repeaters/index.js +1 -1
- package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +114 -98
- package/dist/map-v2/features/repeaters/utils/repeaterGeoJSON.js +24 -10
- package/dist/map-v2/index.d.ts +1 -1
- package/dist/map-v2/index.js +1 -1
- package/package.json +7 -7
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
const BASE_LAT = 37.7749;
|
|
11
11
|
const BASE_LNG = -122.4194;
|
|
12
12
|
// Grid parameters for distributing sites
|
|
13
|
-
const NUM_SITES =
|
|
13
|
+
const NUM_SITES = 1700;
|
|
14
14
|
const GRID_SIZE = 10; // 10×10 grid
|
|
15
15
|
const LAT_SPACING = 0.01; // ~1.1 km spacing
|
|
16
16
|
const LNG_SPACING = 0.015; // ~1.1 km spacing (adjusted for longitude)
|
|
@@ -56,7 +56,16 @@
|
|
|
56
56
|
let treeStore = $state<Writable<TreeStoreValue> | null>(null);
|
|
57
57
|
let level1 = $state<Exclude<CellGroupingField, 'none'>>(store.groupingConfig.level1);
|
|
58
58
|
let level2 = $state<CellGroupingField>(store.groupingConfig.level2);
|
|
59
|
-
|
|
59
|
+
|
|
60
|
+
// Track checked paths separately to avoid re-filtering on expand/collapse
|
|
61
|
+
let checkedPaths = $state<Set<string>>(new Set());
|
|
62
|
+
|
|
63
|
+
// When checkedPaths change, re-run the filter
|
|
64
|
+
$effect(() => {
|
|
65
|
+
const newFilteredCells = getFilteredCells(checkedPaths, store.cellsFilteredByStatus);
|
|
66
|
+
store.setFilteredCells(newFilteredCells);
|
|
67
|
+
});
|
|
68
|
+
|
|
60
69
|
// Track config for localStorage invalidation
|
|
61
70
|
const STORAGE_CONFIG_KEY = 'cellular-cell-filter:config';
|
|
62
71
|
|
|
@@ -100,24 +109,30 @@
|
|
|
100
109
|
persistState: true,
|
|
101
110
|
defaultExpandAll: false
|
|
102
111
|
});
|
|
103
|
-
|
|
112
|
+
|
|
104
113
|
// Subscribe to tree changes and update filtered cells
|
|
105
114
|
if (treeStore) {
|
|
115
|
+
// This subscription now only syncs the checkedPaths state
|
|
116
|
+
// The $effect above will handle the actual filtering
|
|
106
117
|
const unsub = treeStore.subscribe((treeValue: TreeStoreValue) => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
// getCheckedPaths() is expensive, so check if tree is initializing
|
|
119
|
+
if (treeValue.isInitializing) return;
|
|
120
|
+
|
|
121
|
+
const newCheckedPaths = new Set(treeValue.getCheckedPaths());
|
|
122
|
+
|
|
123
|
+
// Avoid unnecessary updates if the set hasn't changed
|
|
124
|
+
if (
|
|
125
|
+
newCheckedPaths.size !== checkedPaths.size ||
|
|
126
|
+
![...newCheckedPaths].every((path) => checkedPaths.has(path))
|
|
127
|
+
) {
|
|
128
|
+
checkedPaths = newCheckedPaths;
|
|
129
|
+
}
|
|
115
130
|
});
|
|
116
|
-
|
|
131
|
+
|
|
117
132
|
return () => unsub();
|
|
118
133
|
}
|
|
119
134
|
}
|
|
120
|
-
|
|
135
|
+
|
|
121
136
|
onMount(() => {
|
|
122
137
|
rebuildTree();
|
|
123
138
|
});
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import type { CellStoreContext } from '../stores/cellStoreContext.svelte';
|
|
15
15
|
import { useMapbox } from '../../../core/hooks/useMapbox';
|
|
16
16
|
import { cellsToGeoJSON } from '../utils/cellGeoJSON';
|
|
17
|
-
import { CELL_FILL_Z_INDEX, CELL_LINE_Z_INDEX } from '../constants/zIndex';
|
|
17
|
+
import { CELL_FILL_Z_INDEX, CELL_LINE_Z_INDEX, Z_INDEX_BY_BAND } from '../constants/zIndex';
|
|
18
18
|
|
|
19
19
|
interface Props {
|
|
20
20
|
/** Cell store context */
|
|
@@ -24,28 +24,34 @@
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
let { store, namespace }: Props = $props();
|
|
27
|
-
|
|
28
|
-
const FILL_LAYER_ID = `${namespace}-cells-fill`;
|
|
29
|
-
const LINE_LAYER_ID = `${namespace}-cells-line`;
|
|
27
|
+
|
|
30
28
|
const SOURCE_ID = `${namespace}-cells`;
|
|
31
|
-
|
|
29
|
+
|
|
32
30
|
// Get map from mapbox hook
|
|
33
31
|
const mapStore = useMapbox();
|
|
34
|
-
|
|
32
|
+
|
|
35
33
|
let map = $state<MapboxMap | null>(null);
|
|
36
34
|
let mounted = $state(false);
|
|
37
35
|
let viewportUpdateTimer: ReturnType<typeof setTimeout> | null = null;
|
|
38
36
|
let viewportVersion = $state(0); // Increment this to force $effect re-run on viewport changes
|
|
39
|
-
|
|
37
|
+
|
|
38
|
+
// Get all unique z-index values and sort them
|
|
39
|
+
const sortedZIndexes = [...new Set(Object.values(Z_INDEX_BY_BAND))].sort((a, b) => a - b);
|
|
40
|
+
|
|
41
|
+
// Create layer IDs for each z-index
|
|
42
|
+
const fillLayerIds = sortedZIndexes.map((zIndex) => `${namespace}-cells-fill-${zIndex}`);
|
|
43
|
+
const lineLayerIds = sortedZIndexes.map((zIndex) => `${namespace}-cells-line-${zIndex}`);
|
|
44
|
+
const allLayerIds = [...fillLayerIds, ...lineLayerIds];
|
|
45
|
+
|
|
40
46
|
// Viewport change handler (pan/zoom/move) with debouncing
|
|
41
47
|
function handleMoveEnd() {
|
|
42
48
|
if (!map) return;
|
|
43
|
-
|
|
49
|
+
|
|
44
50
|
// Clear any existing timer
|
|
45
51
|
if (viewportUpdateTimer) {
|
|
46
52
|
clearTimeout(viewportUpdateTimer);
|
|
47
53
|
}
|
|
48
|
-
|
|
54
|
+
|
|
49
55
|
// Debounce: wait 200ms after last move event before re-rendering
|
|
50
56
|
viewportUpdateTimer = setTimeout(() => {
|
|
51
57
|
if (!map) return;
|
|
@@ -57,86 +63,85 @@
|
|
|
57
63
|
viewportVersion++;
|
|
58
64
|
}, 200);
|
|
59
65
|
}
|
|
60
|
-
|
|
66
|
+
|
|
61
67
|
/**
|
|
62
68
|
* Initialize or reinitialize the cell layers
|
|
63
69
|
* Called on mount and when map style changes
|
|
64
70
|
*/
|
|
65
71
|
function initializeLayer() {
|
|
66
72
|
if (!map) return;
|
|
67
|
-
|
|
73
|
+
|
|
68
74
|
console.log('CellsLayer: initializeLayer called');
|
|
69
|
-
|
|
75
|
+
|
|
70
76
|
// Set initial zoom
|
|
71
77
|
store.setCurrentZoom(map.getZoom());
|
|
72
|
-
|
|
78
|
+
|
|
73
79
|
// Add moveend listener if not already added
|
|
74
80
|
// Note: We need to be careful not to duplicate listeners
|
|
75
81
|
map.off('moveend', handleMoveEnd); // Remove if exists
|
|
76
|
-
map.on('moveend', handleMoveEnd);
|
|
77
|
-
|
|
82
|
+
map.on('moveend', handleMoveEnd); // Add it back
|
|
83
|
+
|
|
78
84
|
// Mark as mounted to trigger $effect
|
|
79
85
|
mounted = true;
|
|
80
|
-
|
|
86
|
+
|
|
81
87
|
// Force $effect to re-run by incrementing viewportVersion
|
|
82
88
|
// This is critical after style.load when layers need to be recreated
|
|
83
89
|
// but mounted/map haven't changed (they're already true/set)
|
|
84
90
|
viewportVersion++;
|
|
85
91
|
}
|
|
86
|
-
|
|
92
|
+
|
|
87
93
|
onMount(() => {
|
|
88
94
|
console.log('CellsLayer: onMount, waiting for map...');
|
|
89
|
-
|
|
95
|
+
|
|
90
96
|
// Subscribe to map store
|
|
91
97
|
const unsubscribe = mapStore.subscribe((mapInstance) => {
|
|
92
98
|
if (mapInstance && !map) {
|
|
93
99
|
console.log('CellsLayer: Map available, initializing...');
|
|
94
100
|
map = mapInstance;
|
|
95
|
-
|
|
101
|
+
|
|
96
102
|
// Initial layer setup
|
|
97
103
|
initializeLayer();
|
|
98
|
-
|
|
104
|
+
|
|
99
105
|
// Re-initialize layer when map style changes
|
|
100
106
|
// Mapbox removes all custom layers/sources when setStyle() is called
|
|
101
107
|
map.on('style.load', initializeLayer);
|
|
102
108
|
}
|
|
103
109
|
});
|
|
104
|
-
|
|
110
|
+
|
|
105
111
|
return () => {
|
|
106
112
|
unsubscribe();
|
|
107
113
|
};
|
|
108
114
|
});
|
|
109
|
-
|
|
115
|
+
|
|
110
116
|
onDestroy(() => {
|
|
111
117
|
if (!map) return;
|
|
112
|
-
|
|
118
|
+
|
|
113
119
|
// Clean up timer
|
|
114
120
|
if (viewportUpdateTimer) {
|
|
115
121
|
clearTimeout(viewportUpdateTimer);
|
|
116
122
|
}
|
|
117
|
-
|
|
123
|
+
|
|
118
124
|
// Remove style.load listener
|
|
119
125
|
map.off('style.load', initializeLayer);
|
|
120
|
-
|
|
126
|
+
|
|
121
127
|
map.off('moveend', handleMoveEnd);
|
|
122
|
-
|
|
128
|
+
|
|
123
129
|
// Clean up layers and source
|
|
124
|
-
|
|
125
|
-
map.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
+
allLayerIds.forEach((id) => {
|
|
131
|
+
if (map.getLayer(id)) {
|
|
132
|
+
map.removeLayer(id);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
130
135
|
if (map.getSource(SOURCE_ID)) {
|
|
131
136
|
map.removeSource(SOURCE_ID);
|
|
132
137
|
}
|
|
133
138
|
});
|
|
134
|
-
|
|
139
|
+
|
|
135
140
|
// Reactive: Update GeoJSON when cells/zoom/filters/settings change
|
|
136
141
|
$effect(() => {
|
|
137
142
|
// Track viewportVersion to force re-run on pan (even without zoom change)
|
|
138
143
|
viewportVersion;
|
|
139
|
-
|
|
144
|
+
|
|
140
145
|
console.log('CellsLayer $effect triggered:', {
|
|
141
146
|
mounted,
|
|
142
147
|
hasMap: !!map,
|
|
@@ -146,35 +151,35 @@
|
|
|
146
151
|
baseRadius: store.baseRadius,
|
|
147
152
|
viewportVersion
|
|
148
153
|
});
|
|
149
|
-
|
|
150
|
-
if (!mounted || !map
|
|
154
|
+
|
|
155
|
+
if (!mounted || !map) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!store.showCells) {
|
|
151
160
|
// Remove layers if showCells is false
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
map.removeLayer(LINE_LAYER_ID);
|
|
156
|
-
}
|
|
157
|
-
if (map.getLayer(FILL_LAYER_ID)) {
|
|
158
|
-
map.removeLayer(FILL_LAYER_ID);
|
|
159
|
-
}
|
|
160
|
-
if (map.getSource(SOURCE_ID)) {
|
|
161
|
-
map.removeSource(SOURCE_ID);
|
|
161
|
+
allLayerIds.forEach((id) => {
|
|
162
|
+
if (map.getLayer(id)) {
|
|
163
|
+
map.removeLayer(id);
|
|
162
164
|
}
|
|
165
|
+
});
|
|
166
|
+
if (map.getSource(SOURCE_ID)) {
|
|
167
|
+
map.removeSource(SOURCE_ID);
|
|
163
168
|
}
|
|
164
169
|
return;
|
|
165
170
|
}
|
|
166
|
-
|
|
171
|
+
|
|
167
172
|
// Filter cells by viewport bounds (only render visible cells)
|
|
168
173
|
const bounds = map.getBounds();
|
|
169
174
|
if (!bounds) {
|
|
170
175
|
console.warn('CellsLayer: Cannot get map bounds, skipping viewport filter');
|
|
171
176
|
return;
|
|
172
177
|
}
|
|
173
|
-
|
|
174
|
-
const visibleCells = store.filteredCells.filter(cell =>
|
|
178
|
+
|
|
179
|
+
const visibleCells = store.filteredCells.filter((cell) =>
|
|
175
180
|
bounds.contains([cell.longitude, cell.latitude])
|
|
176
181
|
);
|
|
177
|
-
|
|
182
|
+
|
|
178
183
|
console.log('CellsLayer: Viewport filtering:', {
|
|
179
184
|
totalFiltered: store.filteredCells.length,
|
|
180
185
|
visibleInViewport: visibleCells.length,
|
|
@@ -185,7 +190,7 @@
|
|
|
185
190
|
west: bounds.getWest()
|
|
186
191
|
}
|
|
187
192
|
});
|
|
188
|
-
|
|
193
|
+
|
|
189
194
|
// Generate GeoJSON from visible cells only
|
|
190
195
|
const geoJSON = cellsToGeoJSON(
|
|
191
196
|
visibleCells,
|
|
@@ -194,12 +199,12 @@
|
|
|
194
199
|
store.groupColorMap,
|
|
195
200
|
store.cellGroupMap // Pass lookup map for O(1) color lookup
|
|
196
201
|
);
|
|
197
|
-
|
|
202
|
+
|
|
198
203
|
console.log('CellsLayer: Generated GeoJSON:', {
|
|
199
204
|
featureCount: geoJSON.features.length,
|
|
200
205
|
firstFeature: geoJSON.features[0]
|
|
201
206
|
});
|
|
202
|
-
|
|
207
|
+
|
|
203
208
|
// Update or create source
|
|
204
209
|
const source = map.getSource(SOURCE_ID);
|
|
205
210
|
if (source && source.type === 'geojson') {
|
|
@@ -212,60 +217,76 @@
|
|
|
212
217
|
data: geoJSON
|
|
213
218
|
});
|
|
214
219
|
}
|
|
215
|
-
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
paint: {
|
|
224
|
-
'fill-color': [
|
|
225
|
-
'coalesce',
|
|
226
|
-
['get', 'groupColor'],
|
|
227
|
-
['get', 'techBandColor'],
|
|
228
|
-
'#888888' // Fallback
|
|
229
|
-
],
|
|
230
|
-
'fill-opacity': store.fillOpacity
|
|
231
|
-
},
|
|
232
|
-
metadata: {
|
|
233
|
-
zIndex: CELL_FILL_Z_INDEX
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
} else {
|
|
237
|
-
// Update fill opacity
|
|
238
|
-
console.log('CellsLayer: Updating fill opacity:', store.fillOpacity);
|
|
239
|
-
map.setPaintProperty(FILL_LAYER_ID, 'fill-opacity', store.fillOpacity);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Add line layer if not exists
|
|
243
|
-
if (!map.getLayer(LINE_LAYER_ID)) {
|
|
244
|
-
console.log('CellsLayer: Creating line layer');
|
|
245
|
-
map.addLayer({
|
|
246
|
-
id: LINE_LAYER_ID,
|
|
247
|
-
type: 'line',
|
|
248
|
-
source: SOURCE_ID,
|
|
249
|
-
paint: {
|
|
250
|
-
'line-color': ['get', 'lineColor'],
|
|
251
|
-
'line-width': store.lineWidth,
|
|
252
|
-
'line-opacity': ['get', 'lineOpacity'],
|
|
253
|
-
'line-dasharray': [
|
|
254
|
-
'case',
|
|
255
|
-
['has', 'dashArray'],
|
|
256
|
-
['get', 'dashArray'],
|
|
257
|
-
['literal', [1, 0]] // Solid line default
|
|
258
|
-
]
|
|
259
|
-
},
|
|
260
|
-
metadata: {
|
|
261
|
-
zIndex: CELL_LINE_Z_INDEX
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
} else {
|
|
265
|
-
// Update line width
|
|
266
|
-
console.log('CellsLayer: Updating line width:', store.lineWidth);
|
|
267
|
-
map.setPaintProperty(LINE_LAYER_ID, 'line-width', store.lineWidth);
|
|
220
|
+
|
|
221
|
+
// Get all unique bands for the current z-index
|
|
222
|
+
const bandsByZIndex = new Map<number, string[]>();
|
|
223
|
+
for (const [band, zIndex] of Object.entries(Z_INDEX_BY_BAND)) {
|
|
224
|
+
if (!bandsByZIndex.has(zIndex)) {
|
|
225
|
+
bandsByZIndex.set(zIndex, []);
|
|
226
|
+
}
|
|
227
|
+
bandsByZIndex.get(zIndex)?.push(band);
|
|
268
228
|
}
|
|
229
|
+
|
|
230
|
+
// Add/update layers for each z-index level
|
|
231
|
+
sortedZIndexes.forEach((zIndex) => {
|
|
232
|
+
const fillLayerId = `${namespace}-cells-fill-${zIndex}`;
|
|
233
|
+
const lineLayerId = `${namespace}-cells-line-${zIndex}`;
|
|
234
|
+
const bands = bandsByZIndex.get(zIndex) || [];
|
|
235
|
+
|
|
236
|
+
// Add fill layer if not exists
|
|
237
|
+
if (!map.getLayer(fillLayerId)) {
|
|
238
|
+
console.log(`CellsLayer: Creating fill layer for z-index ${zIndex}`);
|
|
239
|
+
map.addLayer({
|
|
240
|
+
id: fillLayerId,
|
|
241
|
+
type: 'fill',
|
|
242
|
+
source: SOURCE_ID,
|
|
243
|
+
filter: ['in', ['get', 'techBandKey'], ['literal', bands]],
|
|
244
|
+
paint: {
|
|
245
|
+
'fill-color': [
|
|
246
|
+
'coalesce',
|
|
247
|
+
['get', 'groupColor'],
|
|
248
|
+
['get', 'techBandColor'],
|
|
249
|
+
'#888888' // Fallback
|
|
250
|
+
],
|
|
251
|
+
'fill-opacity': store.fillOpacity
|
|
252
|
+
},
|
|
253
|
+
metadata: {
|
|
254
|
+
zIndex: CELL_FILL_Z_INDEX + zIndex
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
} else {
|
|
258
|
+
// Update fill opacity
|
|
259
|
+
map.setPaintProperty(fillLayerId, 'fill-opacity', store.fillOpacity);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Add line layer if not exists
|
|
263
|
+
if (!map.getLayer(lineLayerId)) {
|
|
264
|
+
console.log(`CellsLayer: Creating line layer for z-index ${zIndex}`);
|
|
265
|
+
map.addLayer({
|
|
266
|
+
id: lineLayerId,
|
|
267
|
+
type: 'line',
|
|
268
|
+
source: SOURCE_ID,
|
|
269
|
+
filter: ['in', ['get', 'techBandKey'], ['literal', bands]],
|
|
270
|
+
paint: {
|
|
271
|
+
'line-color': ['get', 'lineColor'],
|
|
272
|
+
'line-width': store.lineWidth,
|
|
273
|
+
'line-opacity': ['get', 'lineOpacity'],
|
|
274
|
+
'line-dasharray': [
|
|
275
|
+
'case',
|
|
276
|
+
['has', 'dashArray'],
|
|
277
|
+
['get', 'dashArray'],
|
|
278
|
+
['literal', [1, 0]] // Solid line default
|
|
279
|
+
]
|
|
280
|
+
},
|
|
281
|
+
metadata: {
|
|
282
|
+
zIndex: CELL_LINE_Z_INDEX + zIndex
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
} else {
|
|
286
|
+
// Update line width
|
|
287
|
+
map.setPaintProperty(lineLayerId, 'line-width', store.lineWidth);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
269
290
|
});
|
|
270
291
|
</script>
|
|
271
292
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { TECHNOLOGY_BAND_COLORS } from '../constants/colors';
|
|
7
7
|
import { DEFAULT_STATUS_STYLES } from '../constants/statusStyles';
|
|
8
|
+
import { Z_INDEX_BY_BAND } from '../constants/zIndex';
|
|
8
9
|
import { calculateRadius } from './zoomScaling';
|
|
9
10
|
import { createArcPolygon } from './arcGeometry';
|
|
10
11
|
/**
|
|
@@ -61,6 +62,7 @@ export function cellsToGeoJSON(cells, currentZoom, baseRadius = 500, groupColorM
|
|
|
61
62
|
techBandKey,
|
|
62
63
|
techBandColor: defaultColor, // Default color from constants
|
|
63
64
|
groupColor: groupColor, // Custom color (if set)
|
|
65
|
+
zIndex: Z_INDEX_BY_BAND[techBandKey] || 0, // Z-index for layer ordering
|
|
64
66
|
// Line (border) styling from status
|
|
65
67
|
lineColor: statusStyle.lineColor,
|
|
66
68
|
lineWidth: statusStyle.lineWidth,
|
|
@@ -1,25 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Z-Index ordering for repeater tech-band combinations
|
|
3
3
|
*
|
|
4
|
+
* This uses the same Z_INDEX_BY_BAND constant as cells for consistency.
|
|
4
5
|
* Higher frequency bands are rendered on top when repeaters overlap.
|
|
5
|
-
* This follows the same pattern as cells.
|
|
6
6
|
*/
|
|
7
|
+
import type { TechnologyBandKey } from '../../cells/types';
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
+
* Shared z-index ordering by technology-band key
|
|
9
10
|
* Higher number = rendered on top
|
|
10
|
-
*
|
|
11
|
-
* Organized by frequency (low to high):
|
|
12
|
-
* - Lower frequencies (700-900) at bottom
|
|
13
|
-
* - Mid frequencies (1800-2100) in middle
|
|
14
|
-
* - Higher frequencies (2600-3500) on top
|
|
15
|
-
*/
|
|
16
|
-
export declare const REPEATER_TECH_BAND_Z_ORDER: Record<string, number>;
|
|
17
|
-
/**
|
|
18
|
-
* Get z-order index for a tech:fband combination
|
|
19
|
-
* Used to set the sort key for layering overlapping repeaters
|
|
20
|
-
*
|
|
21
|
-
* @param tech - Technology (2G, 4G, 5G)
|
|
22
|
-
* @param fband - Frequency band (e.g., "LTE1800", "GSM900")
|
|
23
|
-
* @returns Z-order index (defaults to 0 if not found)
|
|
24
11
|
*/
|
|
25
|
-
export declare
|
|
12
|
+
export declare const Z_INDEX_BY_BAND: Record<TechnologyBandKey, number>;
|
|
@@ -1,43 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Z-Index ordering for repeater tech-band combinations
|
|
3
3
|
*
|
|
4
|
+
* This uses the same Z_INDEX_BY_BAND constant as cells for consistency.
|
|
4
5
|
* Higher frequency bands are rendered on top when repeaters overlap.
|
|
5
|
-
* This follows the same pattern as cells.
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Shared z-index ordering by technology-band key
|
|
9
9
|
* Higher number = rendered on top
|
|
10
|
-
*
|
|
11
|
-
* Organized by frequency (low to high):
|
|
12
|
-
* - Lower frequencies (700-900) at bottom
|
|
13
|
-
* - Mid frequencies (1800-2100) in middle
|
|
14
|
-
* - Higher frequencies (2600-3500) on top
|
|
15
10
|
*/
|
|
16
|
-
export const
|
|
17
|
-
// 2G bands
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
// 4G bands
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
// 5G bands
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
11
|
+
export const Z_INDEX_BY_BAND = {
|
|
12
|
+
// 2G bands
|
|
13
|
+
'2G_900': 5,
|
|
14
|
+
'2G_1800': 4,
|
|
15
|
+
// 4G bands
|
|
16
|
+
'4G_700': 2,
|
|
17
|
+
'4G_800': 3,
|
|
18
|
+
'4G_900': 4,
|
|
19
|
+
'4G_1800': 8,
|
|
20
|
+
'4G_2100': 9,
|
|
21
|
+
'4G_2600': 11,
|
|
22
|
+
// 5G bands
|
|
23
|
+
'5G_700': 9,
|
|
24
|
+
'5G_2100': 10,
|
|
25
|
+
'5G_3500': 12
|
|
31
26
|
};
|
|
32
|
-
/**
|
|
33
|
-
* Get z-order index for a tech:fband combination
|
|
34
|
-
* Used to set the sort key for layering overlapping repeaters
|
|
35
|
-
*
|
|
36
|
-
* @param tech - Technology (2G, 4G, 5G)
|
|
37
|
-
* @param fband - Frequency band (e.g., "LTE1800", "GSM900")
|
|
38
|
-
* @returns Z-order index (defaults to 0 if not found)
|
|
39
|
-
*/
|
|
40
|
-
export function getRepeaterZOrder(tech, fband) {
|
|
41
|
-
const key = `${tech}:${fband}`;
|
|
42
|
-
return REPEATER_TECH_BAND_Z_ORDER[key] ?? 0;
|
|
43
|
-
}
|
|
@@ -13,4 +13,4 @@ export { repeatersToGeoJSON } from './utils/repeaterGeoJSON';
|
|
|
13
13
|
export { buildRepeaterTree, getFilteredRepeaters } from './utils/repeaterTree';
|
|
14
14
|
export { REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX } from './constants/zIndex';
|
|
15
15
|
export { REPEATER_RADIUS_MULTIPLIER, getRepeaterRadiusMultiplier } from './constants/radiusMultipliers';
|
|
16
|
-
export {
|
|
16
|
+
export { Z_INDEX_BY_BAND } from './constants/techBandZOrder';
|
|
@@ -16,4 +16,4 @@ export { buildRepeaterTree, getFilteredRepeaters } from './utils/repeaterTree';
|
|
|
16
16
|
// Constants
|
|
17
17
|
export { REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX } from './constants/zIndex';
|
|
18
18
|
export { REPEATER_RADIUS_MULTIPLIER, getRepeaterRadiusMultiplier } from './constants/radiusMultipliers';
|
|
19
|
-
export {
|
|
19
|
+
export { Z_INDEX_BY_BAND } from './constants/techBandZOrder';
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { useMapbox } from '../../../core/hooks/useMapbox';
|
|
18
18
|
import { repeatersToGeoJSON } from '../utils/repeaterGeoJSON';
|
|
19
19
|
import { REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX } from '../constants/zIndex';
|
|
20
|
+
import { Z_INDEX_BY_BAND } from '../constants/techBandZOrder';
|
|
20
21
|
|
|
21
22
|
interface Props {
|
|
22
23
|
/** Repeater store context */
|
|
@@ -26,28 +27,34 @@
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
let { store, namespace }: Props = $props();
|
|
29
|
-
|
|
30
|
-
const FILL_LAYER_ID = `${namespace}-repeaters-fill`;
|
|
31
|
-
const LINE_LAYER_ID = `${namespace}-repeaters-line`;
|
|
30
|
+
|
|
32
31
|
const SOURCE_ID = `${namespace}-repeaters`;
|
|
33
|
-
|
|
32
|
+
|
|
34
33
|
// Get map from mapbox hook
|
|
35
34
|
const mapStore = useMapbox();
|
|
36
|
-
|
|
35
|
+
|
|
37
36
|
let map = $state<MapboxMap | null>(null);
|
|
38
37
|
let mounted = $state(false);
|
|
39
38
|
let viewportUpdateTimer: ReturnType<typeof setTimeout> | null = null;
|
|
40
39
|
let viewportVersion = $state(0); // Increment to force $effect re-run on viewport changes
|
|
41
|
-
|
|
40
|
+
|
|
41
|
+
// Get all unique z-index values and sort them
|
|
42
|
+
const sortedZIndexes = [...new Set(Object.values(Z_INDEX_BY_BAND))].sort((a, b) => a - b);
|
|
43
|
+
|
|
44
|
+
// Create layer IDs for each z-index
|
|
45
|
+
const fillLayerIds = sortedZIndexes.map((zIndex) => `${namespace}-repeaters-fill-${zIndex}`);
|
|
46
|
+
const lineLayerIds = sortedZIndexes.map((zIndex) => `${namespace}-repeaters-line-${zIndex}`);
|
|
47
|
+
const allLayerIds = [...fillLayerIds, ...lineLayerIds];
|
|
48
|
+
|
|
42
49
|
// Viewport change handler with debouncing
|
|
43
50
|
function handleMoveEnd() {
|
|
44
51
|
if (!map) return;
|
|
45
|
-
|
|
52
|
+
|
|
46
53
|
// Clear any existing timer
|
|
47
54
|
if (viewportUpdateTimer) {
|
|
48
55
|
clearTimeout(viewportUpdateTimer);
|
|
49
56
|
}
|
|
50
|
-
|
|
57
|
+
|
|
51
58
|
// Debounce: wait 200ms after last move event before re-rendering
|
|
52
59
|
viewportUpdateTimer = setTimeout(() => {
|
|
53
60
|
if (!map) return;
|
|
@@ -59,73 +66,72 @@
|
|
|
59
66
|
viewportVersion++;
|
|
60
67
|
}, 200);
|
|
61
68
|
}
|
|
62
|
-
|
|
69
|
+
|
|
63
70
|
/**
|
|
64
71
|
* Initialize or reinitialize the repeater layers
|
|
65
72
|
* Called on mount and when map style changes
|
|
66
73
|
*/
|
|
67
74
|
function initializeLayer() {
|
|
68
75
|
if (!map) return;
|
|
69
|
-
|
|
76
|
+
|
|
70
77
|
console.log('RepeatersLayer: initializeLayer called');
|
|
71
|
-
|
|
78
|
+
|
|
72
79
|
// Set initial zoom
|
|
73
80
|
store.setCurrentZoom(map.getZoom());
|
|
74
|
-
|
|
81
|
+
|
|
75
82
|
// Add moveend listener (remove first to avoid duplicates)
|
|
76
83
|
map.off('moveend', handleMoveEnd);
|
|
77
84
|
map.on('moveend', handleMoveEnd);
|
|
78
|
-
|
|
85
|
+
|
|
79
86
|
// Mark as mounted to trigger $effect
|
|
80
87
|
mounted = true;
|
|
81
|
-
|
|
88
|
+
|
|
82
89
|
// Force $effect to re-run
|
|
83
90
|
viewportVersion++;
|
|
84
91
|
}
|
|
85
|
-
|
|
92
|
+
|
|
86
93
|
onMount(() => {
|
|
87
94
|
console.log('RepeatersLayer: onMount, waiting for map...');
|
|
88
|
-
|
|
95
|
+
|
|
89
96
|
// Subscribe to map store
|
|
90
97
|
const unsubscribe = mapStore.subscribe((mapInstance) => {
|
|
91
98
|
if (mapInstance && !map) {
|
|
92
99
|
console.log('RepeatersLayer: Map available, initializing...');
|
|
93
100
|
map = mapInstance;
|
|
94
|
-
|
|
101
|
+
|
|
95
102
|
// Initial layer setup
|
|
96
103
|
initializeLayer();
|
|
97
|
-
|
|
104
|
+
|
|
98
105
|
// Re-initialize layer when map style changes
|
|
99
106
|
// Mapbox removes all custom layers/sources when setStyle() is called
|
|
100
107
|
map.on('style.load', initializeLayer);
|
|
101
108
|
}
|
|
102
109
|
});
|
|
103
|
-
|
|
110
|
+
|
|
104
111
|
return () => {
|
|
105
112
|
unsubscribe();
|
|
106
113
|
};
|
|
107
114
|
});
|
|
108
|
-
|
|
115
|
+
|
|
109
116
|
onDestroy(() => {
|
|
110
117
|
if (!map) return;
|
|
111
|
-
|
|
118
|
+
|
|
112
119
|
// Clean up timer
|
|
113
120
|
if (viewportUpdateTimer) {
|
|
114
121
|
clearTimeout(viewportUpdateTimer);
|
|
115
122
|
}
|
|
116
|
-
|
|
123
|
+
|
|
117
124
|
// Remove style.load listener
|
|
118
125
|
map.off('style.load', initializeLayer);
|
|
119
|
-
|
|
126
|
+
|
|
120
127
|
map.off('moveend', handleMoveEnd);
|
|
121
|
-
|
|
128
|
+
|
|
122
129
|
// Clean up layers and source
|
|
123
|
-
|
|
124
|
-
map.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
130
|
+
allLayerIds.forEach((id) => {
|
|
131
|
+
if (map.getLayer(id)) {
|
|
132
|
+
map.removeLayer(id);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
129
135
|
if (map.getSource(SOURCE_ID)) {
|
|
130
136
|
map.removeSource(SOURCE_ID);
|
|
131
137
|
}
|
|
@@ -135,7 +141,7 @@
|
|
|
135
141
|
$effect(() => {
|
|
136
142
|
// Track viewportVersion to force re-run on pan
|
|
137
143
|
viewportVersion;
|
|
138
|
-
|
|
144
|
+
|
|
139
145
|
console.log('RepeatersLayer $effect triggered:', {
|
|
140
146
|
mounted,
|
|
141
147
|
hasMap: !!map,
|
|
@@ -145,40 +151,40 @@
|
|
|
145
151
|
baseRadius: store.baseRadius,
|
|
146
152
|
viewportVersion
|
|
147
153
|
});
|
|
148
|
-
|
|
149
|
-
if (!mounted || !map
|
|
154
|
+
|
|
155
|
+
if (!mounted || !map) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!store.showRepeaters) {
|
|
150
160
|
// Remove layers if showRepeaters is false
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
map.removeLayer(LINE_LAYER_ID);
|
|
155
|
-
}
|
|
156
|
-
if (map.getLayer(FILL_LAYER_ID)) {
|
|
157
|
-
map.removeLayer(FILL_LAYER_ID);
|
|
158
|
-
}
|
|
159
|
-
if (map.getSource(SOURCE_ID)) {
|
|
160
|
-
map.removeSource(SOURCE_ID);
|
|
161
|
+
allLayerIds.forEach((id) => {
|
|
162
|
+
if (map.getLayer(id)) {
|
|
163
|
+
map.removeLayer(id);
|
|
161
164
|
}
|
|
165
|
+
});
|
|
166
|
+
if (map.getSource(SOURCE_ID)) {
|
|
167
|
+
map.removeSource(SOURCE_ID);
|
|
162
168
|
}
|
|
163
169
|
return;
|
|
164
170
|
}
|
|
165
|
-
|
|
171
|
+
|
|
166
172
|
// Filter repeaters by viewport bounds (only render visible repeaters)
|
|
167
173
|
const bounds = map.getBounds();
|
|
168
174
|
if (!bounds) {
|
|
169
175
|
console.warn('RepeatersLayer: Cannot get map bounds, skipping viewport filter');
|
|
170
176
|
return;
|
|
171
177
|
}
|
|
172
|
-
|
|
173
|
-
const visibleRepeaters = store.filteredRepeaters.filter(repeater =>
|
|
178
|
+
|
|
179
|
+
const visibleRepeaters = store.filteredRepeaters.filter((repeater) =>
|
|
174
180
|
bounds.contains([repeater.longitude, repeater.latitude])
|
|
175
181
|
);
|
|
176
|
-
|
|
182
|
+
|
|
177
183
|
console.log('RepeatersLayer: Viewport filtering:', {
|
|
178
184
|
totalFiltered: store.filteredRepeaters.length,
|
|
179
185
|
visibleInViewport: visibleRepeaters.length
|
|
180
186
|
});
|
|
181
|
-
|
|
187
|
+
|
|
182
188
|
// Generate GeoJSON from visible repeaters only
|
|
183
189
|
const geoJSON = repeatersToGeoJSON(
|
|
184
190
|
visibleRepeaters,
|
|
@@ -186,12 +192,12 @@
|
|
|
186
192
|
store.baseRadius,
|
|
187
193
|
store.techBandColorMap
|
|
188
194
|
);
|
|
189
|
-
|
|
195
|
+
|
|
190
196
|
console.log('RepeatersLayer: Generated GeoJSON:', {
|
|
191
197
|
featureCount: geoJSON.features.length,
|
|
192
198
|
firstFeature: geoJSON.features[0]
|
|
193
199
|
});
|
|
194
|
-
|
|
200
|
+
|
|
195
201
|
// Update or create source
|
|
196
202
|
const source = map.getSource(SOURCE_ID);
|
|
197
203
|
if (source && source.type === 'geojson') {
|
|
@@ -204,55 +210,65 @@
|
|
|
204
210
|
data: geoJSON
|
|
205
211
|
});
|
|
206
212
|
}
|
|
207
|
-
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
layout: {
|
|
216
|
-
'fill-sort-key': ['get', 'sortKey'] // Use sortKey for z-ordering
|
|
217
|
-
},
|
|
218
|
-
paint: {
|
|
219
|
-
'fill-color': ['get', 'techBandColor'],
|
|
220
|
-
'fill-opacity': store.fillOpacity
|
|
221
|
-
},
|
|
222
|
-
metadata: {
|
|
223
|
-
zIndex: REPEATER_FILL_Z_INDEX
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
} else {
|
|
227
|
-
// Update fill opacity
|
|
228
|
-
console.log('RepeatersLayer: Updating fill opacity:', store.fillOpacity);
|
|
229
|
-
map.setPaintProperty(FILL_LAYER_ID, 'fill-opacity', store.fillOpacity);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Add line layer if not exists
|
|
233
|
-
if (!map.getLayer(LINE_LAYER_ID)) {
|
|
234
|
-
console.log('RepeatersLayer: Creating line layer');
|
|
235
|
-
map.addLayer({
|
|
236
|
-
id: LINE_LAYER_ID,
|
|
237
|
-
type: 'line',
|
|
238
|
-
source: SOURCE_ID,
|
|
239
|
-
layout: {
|
|
240
|
-
'line-sort-key': ['get', 'sortKey'] // Use sortKey for z-ordering
|
|
241
|
-
},
|
|
242
|
-
paint: {
|
|
243
|
-
'line-color': ['get', 'lineColor'],
|
|
244
|
-
'line-width': store.lineWidth,
|
|
245
|
-
'line-opacity': ['get', 'lineOpacity']
|
|
246
|
-
},
|
|
247
|
-
metadata: {
|
|
248
|
-
zIndex: REPEATER_LINE_Z_INDEX
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
} else {
|
|
252
|
-
// Update line width
|
|
253
|
-
console.log('RepeatersLayer: Updating line width:', store.lineWidth);
|
|
254
|
-
map.setPaintProperty(LINE_LAYER_ID, 'line-width', store.lineWidth);
|
|
213
|
+
|
|
214
|
+
// Get all unique bands for the current z-index
|
|
215
|
+
const bandsByZIndex = new Map<number, string[]>();
|
|
216
|
+
for (const [band, zIndex] of Object.entries(Z_INDEX_BY_BAND)) {
|
|
217
|
+
if (!bandsByZIndex.has(zIndex)) {
|
|
218
|
+
bandsByZIndex.set(zIndex, []);
|
|
219
|
+
}
|
|
220
|
+
bandsByZIndex.get(zIndex)?.push(band);
|
|
255
221
|
}
|
|
222
|
+
|
|
223
|
+
// Add/update layers for each z-index level
|
|
224
|
+
sortedZIndexes.forEach((zIndex) => {
|
|
225
|
+
const fillLayerId = `${namespace}-repeaters-fill-${zIndex}`;
|
|
226
|
+
const lineLayerId = `${namespace}-repeaters-line-${zIndex}`;
|
|
227
|
+
const bands = bandsByZIndex.get(zIndex) || [];
|
|
228
|
+
|
|
229
|
+
// Add fill layer if not exists
|
|
230
|
+
if (!map.getLayer(fillLayerId)) {
|
|
231
|
+
console.log(`RepeatersLayer: Creating fill layer for z-index ${zIndex}`);
|
|
232
|
+
map.addLayer({
|
|
233
|
+
id: fillLayerId,
|
|
234
|
+
type: 'fill',
|
|
235
|
+
source: SOURCE_ID,
|
|
236
|
+
filter: ['in', ['get', 'techBandKey'], ['literal', bands]],
|
|
237
|
+
paint: {
|
|
238
|
+
'fill-color': ['get', 'techBandColor'],
|
|
239
|
+
'fill-opacity': store.fillOpacity
|
|
240
|
+
},
|
|
241
|
+
metadata: {
|
|
242
|
+
zIndex: REPEATER_FILL_Z_INDEX + zIndex
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
} else {
|
|
246
|
+
// Update fill opacity
|
|
247
|
+
map.setPaintProperty(fillLayerId, 'fill-opacity', store.fillOpacity);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Add line layer if not exists
|
|
251
|
+
if (!map.getLayer(lineLayerId)) {
|
|
252
|
+
console.log(`RepeatersLayer: Creating line layer for z-index ${zIndex}`);
|
|
253
|
+
map.addLayer({
|
|
254
|
+
id: lineLayerId,
|
|
255
|
+
type: 'line',
|
|
256
|
+
source: SOURCE_ID,
|
|
257
|
+
filter: ['in', ['get', 'techBandKey'], ['literal', bands]],
|
|
258
|
+
paint: {
|
|
259
|
+
'line-color': ['get', 'lineColor'],
|
|
260
|
+
'line-width': store.lineWidth,
|
|
261
|
+
'line-opacity': ['get', 'lineOpacity']
|
|
262
|
+
},
|
|
263
|
+
metadata: {
|
|
264
|
+
zIndex: REPEATER_LINE_Z_INDEX + zIndex
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
} else {
|
|
268
|
+
// Update line width
|
|
269
|
+
map.setPaintProperty(lineLayerId, 'line-width', store.lineWidth);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
256
272
|
});
|
|
257
273
|
</script>
|
|
258
274
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { createArcPolygon } from '../../cells/utils/arcGeometry';
|
|
10
10
|
import { getRepeaterRadiusMultiplier } from '../constants/radiusMultipliers';
|
|
11
|
-
import {
|
|
11
|
+
import { Z_INDEX_BY_BAND } from '../constants/techBandZOrder';
|
|
12
12
|
/** Fixed beamwidth for all repeaters */
|
|
13
13
|
const REPEATER_BEAMWIDTH = 30;
|
|
14
14
|
/** Reference zoom level where base radius is used as-is */
|
|
@@ -26,6 +26,18 @@ const REFERENCE_ZOOM = 12;
|
|
|
26
26
|
function getZoomFactor(currentZoom) {
|
|
27
27
|
return Math.pow(2, (REFERENCE_ZOOM - currentZoom) / 2);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse repeater tech and fband into a TechnologyBandKey
|
|
31
|
+
* Repeaters use format like tech="4G", fband="LTE1800" or "1800"
|
|
32
|
+
* We need to convert to "4G_1800" format
|
|
33
|
+
*/
|
|
34
|
+
function parseTechBand(tech, fband) {
|
|
35
|
+
// Extract numeric band from fband (handles "LTE1800", "1800", "GSM900", "900", etc.)
|
|
36
|
+
const bandMatch = fband.match(/(\d+)/);
|
|
37
|
+
const band = bandMatch ? bandMatch[1] : fband;
|
|
38
|
+
// Build TechnologyBandKey
|
|
39
|
+
return `${tech}_${band}`;
|
|
40
|
+
}
|
|
29
41
|
/**
|
|
30
42
|
* Convert repeaters to GeoJSON FeatureCollection with arc geometries
|
|
31
43
|
*
|
|
@@ -37,16 +49,18 @@ function getZoomFactor(currentZoom) {
|
|
|
37
49
|
*/
|
|
38
50
|
export function repeatersToGeoJSON(repeaters, currentZoom, baseRadius = 500, techBandColorMap) {
|
|
39
51
|
const features = repeaters.map((repeater) => {
|
|
40
|
-
// Get color for this tech:fband combination
|
|
41
|
-
const
|
|
42
|
-
const color = techBandColorMap.get(
|
|
52
|
+
// Get color for this tech:fband combination (old format for color map)
|
|
53
|
+
const colorKey = `${repeater.tech}:${repeater.fband}`;
|
|
54
|
+
const color = techBandColorMap.get(colorKey) || '#888888'; // Fallback gray
|
|
55
|
+
// Parse to TechnologyBandKey for z-index lookup
|
|
56
|
+
const techBandKey = parseTechBand(repeater.tech, repeater.fband);
|
|
43
57
|
// Calculate zoom-reactive radius with inverse scaling (like cells)
|
|
44
58
|
const zoomFactor = getZoomFactor(currentZoom);
|
|
45
59
|
// Apply tech-band specific radius multiplier
|
|
46
60
|
const techBandMultiplier = getRepeaterRadiusMultiplier(repeater.tech, repeater.fband);
|
|
47
61
|
const radius = baseRadius * zoomFactor * techBandMultiplier;
|
|
48
|
-
// Get z-order for layering
|
|
49
|
-
const
|
|
62
|
+
// Get z-order for layering using shared constant
|
|
63
|
+
const zIndex = Z_INDEX_BY_BAND[techBandKey] || 0;
|
|
50
64
|
// Create arc polygon geometry with fixed 30° beamwidth
|
|
51
65
|
const center = [repeater.longitude, repeater.latitude];
|
|
52
66
|
const arc = createArcPolygon(center, repeater.azimuth, REPEATER_BEAMWIDTH, radius);
|
|
@@ -73,13 +87,13 @@ export function repeatersToGeoJSON(repeaters, currentZoom, baseRadius = 500, tec
|
|
|
73
87
|
featureGroup: repeater.featureGroup || '',
|
|
74
88
|
status: repeater.status || 'active',
|
|
75
89
|
// Styling properties for Mapbox
|
|
76
|
-
techBandKey:
|
|
90
|
+
techBandKey: techBandKey, // TechnologyBandKey format for consistency
|
|
77
91
|
techBandColor: color,
|
|
78
92
|
lineColor: color,
|
|
79
93
|
lineOpacity: 0.8,
|
|
80
|
-
// Z-
|
|
81
|
-
|
|
82
|
-
sortKey:
|
|
94
|
+
// Z-index for layering (higher frequency on top)
|
|
95
|
+
zIndex: zIndex,
|
|
96
|
+
sortKey: zIndex // Used by Mapbox for layer ordering
|
|
83
97
|
}
|
|
84
98
|
};
|
|
85
99
|
});
|
package/dist/map-v2/index.d.ts
CHANGED
|
@@ -8,5 +8,5 @@ export { type MapStore, MAP_CONTEXT_KEY, MapboxProvider, ViewportSync, MapStoreB
|
|
|
8
8
|
export { MapControl, FeatureSettingsControl, addSourceIfMissing, removeSourceIfExists, addLayerIfMissing, removeLayerIfExists, updateGeoJSONSource, removeLayerAndSource, isStyleLoaded, waitForStyleLoad, setFeatureState, removeFeatureState, generateLayerId, generateSourceId } from './shared';
|
|
9
9
|
export { type Site, type SiteStoreValue, type SiteStoreContext, createSiteStore, createSiteStoreContext, SitesLayer, SiteFilterControl, SiteSelectionControl, SiteSizeSlider, sitesToGeoJSON, siteToFeature, buildSiteTree, getFilteredSites } from './features/sites';
|
|
10
10
|
export { type Cell, type CellStatus, type CellStatusStyle, type CellGroupingField, type CellGroupingLabels, type CellTreeConfig, type TechnologyBandKey, type CellStoreValue, type CellStoreContext, createCellStoreContext, CellsLayer, CellLabelsLayer, CellFilterControl, CellStyleControl, cellsToGeoJSON, buildCellTree, getFilteredCells, calculateRadius, getZoomFactor, createArcPolygon, DEFAULT_CELL_TREE_CONFIG, TECHNOLOGY_BAND_COLORS, DEFAULT_STATUS_STYLES, RADIUS_MULTIPLIER } from './features/cells';
|
|
11
|
-
export { type Repeater, type RepeaterTreeNode, type RepeaterStoreValue, type RepeaterStoreContext, createRepeaterStoreContext, RepeatersLayer, RepeaterLabelsLayer, RepeaterFilterControl, repeatersToGeoJSON, buildRepeaterTree, getFilteredRepeaters, getRepeaterRadiusMultiplier,
|
|
11
|
+
export { type Repeater, type RepeaterTreeNode, type RepeaterStoreValue, type RepeaterStoreContext, createRepeaterStoreContext, RepeatersLayer, RepeaterLabelsLayer, RepeaterFilterControl, repeatersToGeoJSON, buildRepeaterTree, getFilteredRepeaters, getRepeaterRadiusMultiplier, REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX, REPEATER_RADIUS_MULTIPLIER } from './features/repeaters';
|
|
12
12
|
export { DemoMap, demoSites, demoCells, demoRepeaters } from './demo';
|
package/dist/map-v2/index.js
CHANGED
|
@@ -23,7 +23,7 @@ export { createCellStoreContext, CellsLayer, CellLabelsLayer, CellFilterControl,
|
|
|
23
23
|
// ============================================================================
|
|
24
24
|
// REPEATER FEATURE
|
|
25
25
|
// ============================================================================
|
|
26
|
-
export { createRepeaterStoreContext, RepeatersLayer, RepeaterLabelsLayer, RepeaterFilterControl, repeatersToGeoJSON, buildRepeaterTree, getFilteredRepeaters, getRepeaterRadiusMultiplier,
|
|
26
|
+
export { createRepeaterStoreContext, RepeatersLayer, RepeaterLabelsLayer, RepeaterFilterControl, repeatersToGeoJSON, buildRepeaterTree, getFilteredRepeaters, getRepeaterRadiusMultiplier, REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX, REPEATER_RADIUS_MULTIPLIER } from './features/repeaters';
|
|
27
27
|
// ============================================================================
|
|
28
28
|
// DEMO
|
|
29
29
|
// ============================================================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartnet360/svelte-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.77",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -32,13 +32,14 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
+
"@turf/turf": "^7.2.0",
|
|
35
36
|
"bootstrap": "^5.2.3",
|
|
37
|
+
"deck.gl": "^9.1.0",
|
|
38
|
+
"@deck.gl/mapbox": "^9.2.2",
|
|
36
39
|
"dexie": "^4.0.11",
|
|
40
|
+
"mapbox-gl": "^3.0.0",
|
|
37
41
|
"plotly.js-dist-min": "^3.1.0",
|
|
38
|
-
"svelte": "^5.0.0"
|
|
39
|
-
"@turf/turf": "^7.2.0",
|
|
40
|
-
"deck.gl": "^9.1.0",
|
|
41
|
-
"mapbox-gl": "^3.0.0"
|
|
42
|
+
"svelte": "^5.0.0"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@eslint/compat": "^1.2.5",
|
|
@@ -68,6 +69,5 @@
|
|
|
68
69
|
},
|
|
69
70
|
"keywords": [
|
|
70
71
|
"svelte"
|
|
71
|
-
]
|
|
72
|
-
"dependencies": {}
|
|
72
|
+
]
|
|
73
73
|
}
|