@smartnet360/svelte-components 0.0.71 → 0.0.73
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/DemoMap.svelte +29 -1
- package/dist/map-v2/demo/demo-repeaters.d.ts +13 -0
- package/dist/map-v2/demo/demo-repeaters.js +74 -0
- package/dist/map-v2/demo/index.d.ts +1 -0
- package/dist/map-v2/demo/index.js +1 -0
- package/dist/map-v2/features/repeaters/constants/radiusMultipliers.d.ts +26 -0
- package/dist/map-v2/features/repeaters/constants/radiusMultipliers.js +40 -0
- package/dist/map-v2/features/repeaters/constants/techBandZOrder.d.ts +25 -0
- package/dist/map-v2/features/repeaters/constants/techBandZOrder.js +43 -0
- package/dist/map-v2/features/repeaters/constants/zIndex.d.ts +11 -0
- package/dist/map-v2/features/repeaters/constants/zIndex.js +11 -0
- package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte +172 -0
- package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte.d.ts +18 -0
- package/dist/map-v2/features/repeaters/index.d.ts +16 -0
- package/dist/map-v2/features/repeaters/index.js +19 -0
- package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte +301 -0
- package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte.d.ts +10 -0
- package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +259 -0
- package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte.d.ts +10 -0
- package/dist/map-v2/features/repeaters/stores/repeaterStoreContext.svelte.d.ts +69 -0
- package/dist/map-v2/features/repeaters/stores/repeaterStoreContext.svelte.js +222 -0
- package/dist/map-v2/features/repeaters/types.d.ts +59 -0
- package/dist/map-v2/features/repeaters/types.js +4 -0
- package/dist/map-v2/features/repeaters/utils/repeaterGeoJSON.d.ts +20 -0
- package/dist/map-v2/features/repeaters/utils/repeaterGeoJSON.js +90 -0
- package/dist/map-v2/features/repeaters/utils/repeaterTree.d.ts +23 -0
- package/dist/map-v2/features/repeaters/utils/repeaterTree.js +111 -0
- package/dist/map-v2/index.d.ts +2 -1
- package/dist/map-v2/index.js +5 -1
- package/dist/map-v2/shared/controls/FeatureSettingsControl.svelte +27 -21
- package/dist/map-v2/shared/controls/FeatureSettingsControl.svelte.d.ts +3 -0
- package/dist/map-v2/shared/controls/panels/RepeaterSettingsPanel.svelte +280 -5
- package/dist/map-v2/shared/controls/panels/RepeaterSettingsPanel.svelte.d.ts +17 -16
- package/package.json +1 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repeater Store Context - Reactive state for repeater feature
|
|
3
|
+
*
|
|
4
|
+
* Simplified store with fixed Tech → FBand tree structure
|
|
5
|
+
* Supports leaf-level coloring for each tech:fband combination
|
|
6
|
+
*/
|
|
7
|
+
const STORAGE_KEY = 'repeaterVisualSettings';
|
|
8
|
+
function loadSettings() {
|
|
9
|
+
if (typeof window === 'undefined')
|
|
10
|
+
return {};
|
|
11
|
+
try {
|
|
12
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
13
|
+
if (saved) {
|
|
14
|
+
return JSON.parse(saved);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.warn('Failed to load repeater settings from localStorage:', error);
|
|
19
|
+
}
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
function saveSettings(settings) {
|
|
23
|
+
if (typeof window === 'undefined')
|
|
24
|
+
return;
|
|
25
|
+
try {
|
|
26
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.warn('Failed to save repeater settings to localStorage:', error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Default colors for tech:fband combinations
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_TECH_BAND_COLORS = {
|
|
36
|
+
// 2G
|
|
37
|
+
'2G:900': '#FF6B6B',
|
|
38
|
+
'2G:1800': '#FF8E53',
|
|
39
|
+
// 4G
|
|
40
|
+
'4G:700': '#4ECDC4',
|
|
41
|
+
'4G:800': '#45B7D1',
|
|
42
|
+
'4G:900': '#96CEB4',
|
|
43
|
+
'4G:1800': '#FFEAA7',
|
|
44
|
+
'4G:2100': '#DFE6E9',
|
|
45
|
+
'4G:2600': '#74B9FF',
|
|
46
|
+
// 5G
|
|
47
|
+
'5G:2600': '#A29BFE',
|
|
48
|
+
'5G:3500': '#6C5CE7',
|
|
49
|
+
'5G:26000': '#FD79A8'
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Create a repeater store context with reactive state
|
|
53
|
+
*/
|
|
54
|
+
export function createRepeaterStoreContext(repeaters) {
|
|
55
|
+
// Load persisted settings
|
|
56
|
+
const persistedSettings = loadSettings();
|
|
57
|
+
// Convert persisted arrays/objects back to Sets/Maps
|
|
58
|
+
const initialVisibleSet = new Set(persistedSettings.visibleTechBands || []);
|
|
59
|
+
const initialColorMap = new Map();
|
|
60
|
+
if (persistedSettings.techBandColors) {
|
|
61
|
+
Object.entries(persistedSettings.techBandColors).forEach(([key, value]) => {
|
|
62
|
+
initialColorMap.set(key, value);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// If no colors persisted, use defaults
|
|
66
|
+
if (initialColorMap.size === 0) {
|
|
67
|
+
Object.entries(DEFAULT_TECH_BAND_COLORS).forEach(([key, value]) => {
|
|
68
|
+
initialColorMap.set(key, value);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// Internal reactive state
|
|
72
|
+
let state = $state({
|
|
73
|
+
repeaters,
|
|
74
|
+
visibleTechBands: initialVisibleSet,
|
|
75
|
+
techBandColorMap: initialColorMap,
|
|
76
|
+
showRepeaters: persistedSettings.showRepeaters ?? true,
|
|
77
|
+
baseRadius: persistedSettings.baseRadius ?? 500,
|
|
78
|
+
fillOpacity: persistedSettings.fillOpacity ?? 0.4,
|
|
79
|
+
lineWidth: persistedSettings.lineWidth ?? 2,
|
|
80
|
+
currentZoom: 12, // Default zoom
|
|
81
|
+
// Label settings with defaults
|
|
82
|
+
showLabels: persistedSettings.showLabels ?? true, // Default to true
|
|
83
|
+
primaryLabelField: (persistedSettings.primaryLabelField ?? 'repeaterId'),
|
|
84
|
+
secondaryLabelField: (persistedSettings.secondaryLabelField ?? 'fband'),
|
|
85
|
+
labelSize: persistedSettings.labelSize ?? 12,
|
|
86
|
+
labelColor: persistedSettings.labelColor ?? '#ffffff', // White text
|
|
87
|
+
labelOffset: persistedSettings.labelOffset ?? 300,
|
|
88
|
+
labelHaloColor: persistedSettings.labelHaloColor ?? '#000000', // Black halo
|
|
89
|
+
labelHaloWidth: persistedSettings.labelHaloWidth ?? 2,
|
|
90
|
+
minLabelZoom: persistedSettings.minLabelZoom ?? 10 // Lower default to show labels at more zoom levels
|
|
91
|
+
});
|
|
92
|
+
// Derived: Filter repeaters by visible tech:fband combinations
|
|
93
|
+
let filteredRepeaters = $derived(state.repeaters.filter(r => {
|
|
94
|
+
const key = `${r.tech}:${r.fband}`;
|
|
95
|
+
return state.visibleTechBands.has(key);
|
|
96
|
+
}));
|
|
97
|
+
// Auto-save settings when they change
|
|
98
|
+
$effect(() => {
|
|
99
|
+
// Convert Set to Array and Map to Object for serialization
|
|
100
|
+
const visibleArray = Array.from(state.visibleTechBands);
|
|
101
|
+
const colorsObj = {};
|
|
102
|
+
state.techBandColorMap.forEach((color, key) => {
|
|
103
|
+
colorsObj[key] = color;
|
|
104
|
+
});
|
|
105
|
+
const settings = {
|
|
106
|
+
visibleTechBands: visibleArray,
|
|
107
|
+
techBandColors: colorsObj,
|
|
108
|
+
showRepeaters: state.showRepeaters,
|
|
109
|
+
baseRadius: state.baseRadius,
|
|
110
|
+
fillOpacity: state.fillOpacity,
|
|
111
|
+
lineWidth: state.lineWidth,
|
|
112
|
+
// Label settings
|
|
113
|
+
showLabels: state.showLabels,
|
|
114
|
+
primaryLabelField: state.primaryLabelField,
|
|
115
|
+
secondaryLabelField: state.secondaryLabelField,
|
|
116
|
+
labelSize: state.labelSize,
|
|
117
|
+
labelColor: state.labelColor,
|
|
118
|
+
labelOffset: state.labelOffset,
|
|
119
|
+
labelHaloColor: state.labelHaloColor,
|
|
120
|
+
labelHaloWidth: state.labelHaloWidth,
|
|
121
|
+
minLabelZoom: state.minLabelZoom
|
|
122
|
+
};
|
|
123
|
+
saveSettings(settings);
|
|
124
|
+
});
|
|
125
|
+
// Return store context
|
|
126
|
+
return {
|
|
127
|
+
// Data getters
|
|
128
|
+
get repeaters() { return state.repeaters; },
|
|
129
|
+
get filteredRepeaters() { return filteredRepeaters; },
|
|
130
|
+
get visibleTechBands() { return state.visibleTechBands; },
|
|
131
|
+
get techBandColorMap() { return state.techBandColorMap; },
|
|
132
|
+
// Visual getters
|
|
133
|
+
get showRepeaters() { return state.showRepeaters; },
|
|
134
|
+
get baseRadius() { return state.baseRadius; },
|
|
135
|
+
get fillOpacity() { return state.fillOpacity; },
|
|
136
|
+
get lineWidth() { return state.lineWidth; },
|
|
137
|
+
get currentZoom() { return state.currentZoom; },
|
|
138
|
+
// Label getters
|
|
139
|
+
get showLabels() { return state.showLabels; },
|
|
140
|
+
get primaryLabelField() { return state.primaryLabelField; },
|
|
141
|
+
get secondaryLabelField() { return state.secondaryLabelField; },
|
|
142
|
+
get labelSize() { return state.labelSize; },
|
|
143
|
+
get labelColor() { return state.labelColor; },
|
|
144
|
+
get labelOffset() { return state.labelOffset; },
|
|
145
|
+
get labelHaloColor() { return state.labelHaloColor; },
|
|
146
|
+
get labelHaloWidth() { return state.labelHaloWidth; },
|
|
147
|
+
get minLabelZoom() { return state.minLabelZoom; },
|
|
148
|
+
// Data methods
|
|
149
|
+
setRepeaters(repeaters) {
|
|
150
|
+
state.repeaters = repeaters;
|
|
151
|
+
},
|
|
152
|
+
toggleTechBand(tech, fband) {
|
|
153
|
+
const key = `${tech}:${fband}`;
|
|
154
|
+
if (state.visibleTechBands.has(key)) {
|
|
155
|
+
state.visibleTechBands.delete(key);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
state.visibleTechBands.add(key);
|
|
159
|
+
}
|
|
160
|
+
// Trigger reactivity by creating new Set
|
|
161
|
+
state.visibleTechBands = new Set(state.visibleTechBands);
|
|
162
|
+
},
|
|
163
|
+
isTechBandVisible(tech, fband) {
|
|
164
|
+
const key = `${tech}:${fband}`;
|
|
165
|
+
return state.visibleTechBands.has(key);
|
|
166
|
+
},
|
|
167
|
+
getTechBandColor(tech, fband) {
|
|
168
|
+
const key = `${tech}:${fband}`;
|
|
169
|
+
return state.techBandColorMap.get(key) || '#888888'; // Fallback gray
|
|
170
|
+
},
|
|
171
|
+
setTechBandColor(tech, fband, color) {
|
|
172
|
+
const key = `${tech}:${fband}`;
|
|
173
|
+
state.techBandColorMap.set(key, color);
|
|
174
|
+
// Trigger reactivity by creating new Map
|
|
175
|
+
state.techBandColorMap = new Map(state.techBandColorMap);
|
|
176
|
+
},
|
|
177
|
+
// Visual setters
|
|
178
|
+
setShowRepeaters(value) {
|
|
179
|
+
state.showRepeaters = value;
|
|
180
|
+
},
|
|
181
|
+
setBaseRadius(value) {
|
|
182
|
+
state.baseRadius = value;
|
|
183
|
+
},
|
|
184
|
+
setFillOpacity(value) {
|
|
185
|
+
state.fillOpacity = value;
|
|
186
|
+
},
|
|
187
|
+
setLineWidth(value) {
|
|
188
|
+
state.lineWidth = value;
|
|
189
|
+
},
|
|
190
|
+
setCurrentZoom(value) {
|
|
191
|
+
state.currentZoom = value;
|
|
192
|
+
},
|
|
193
|
+
// Label setters
|
|
194
|
+
setShowLabels(value) {
|
|
195
|
+
state.showLabels = value;
|
|
196
|
+
},
|
|
197
|
+
setPrimaryLabelField(field) {
|
|
198
|
+
state.primaryLabelField = field;
|
|
199
|
+
},
|
|
200
|
+
setSecondaryLabelField(field) {
|
|
201
|
+
state.secondaryLabelField = field;
|
|
202
|
+
},
|
|
203
|
+
setLabelSize(value) {
|
|
204
|
+
state.labelSize = value;
|
|
205
|
+
},
|
|
206
|
+
setLabelColor(value) {
|
|
207
|
+
state.labelColor = value;
|
|
208
|
+
},
|
|
209
|
+
setLabelOffset(value) {
|
|
210
|
+
state.labelOffset = value;
|
|
211
|
+
},
|
|
212
|
+
setLabelHaloColor(value) {
|
|
213
|
+
state.labelHaloColor = value;
|
|
214
|
+
},
|
|
215
|
+
setLabelHaloWidth(value) {
|
|
216
|
+
state.labelHaloWidth = value;
|
|
217
|
+
},
|
|
218
|
+
setMinLabelZoom(value) {
|
|
219
|
+
state.minLabelZoom = value;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repeater type definitions
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Repeater - Represents a cellular network repeater
|
|
6
|
+
*
|
|
7
|
+
* Repeaters amplify signals from donor cells to extend coverage.
|
|
8
|
+
* They are visualized as arc sectors with a fixed 30° beamwidth.
|
|
9
|
+
*/
|
|
10
|
+
export interface Repeater {
|
|
11
|
+
/** Unique repeater identifier */
|
|
12
|
+
repeaterId: string;
|
|
13
|
+
/** ID of the donor cell providing the signal */
|
|
14
|
+
donorCellId: string;
|
|
15
|
+
/** Human-readable name of the donor cell */
|
|
16
|
+
donorCellName: string;
|
|
17
|
+
/** Frequency band (e.g., "1800", "2600") */
|
|
18
|
+
fband: string;
|
|
19
|
+
/** Technology (e.g., "2G", "4G", "5G") */
|
|
20
|
+
tech: string;
|
|
21
|
+
/** Type discriminator */
|
|
22
|
+
type: 'REPEATER';
|
|
23
|
+
/** Direction the repeater faces (0-360°) */
|
|
24
|
+
azimuth: number;
|
|
25
|
+
/** Physical height (meters) */
|
|
26
|
+
height: number;
|
|
27
|
+
/** Geographic coordinates */
|
|
28
|
+
longitude: number;
|
|
29
|
+
latitude: number;
|
|
30
|
+
/** Coverage angle (degrees) - typically 30° for repeaters */
|
|
31
|
+
beamwidth: number;
|
|
32
|
+
/** Factory/hardware number */
|
|
33
|
+
factory_nbr: string;
|
|
34
|
+
/** Optional: Network operator */
|
|
35
|
+
provider?: string;
|
|
36
|
+
/** Optional: Custom grouping */
|
|
37
|
+
featureGroup?: string;
|
|
38
|
+
/** Optional: Repeater name */
|
|
39
|
+
name?: string;
|
|
40
|
+
/** Optional: Status */
|
|
41
|
+
status?: 'active' | 'inactive' | 'planned';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* TreeNode - For the fixed Tech → FBand tree structure
|
|
45
|
+
*/
|
|
46
|
+
export interface RepeaterTreeNode {
|
|
47
|
+
/** Unique node ID (e.g., "4G", "4G:1800") */
|
|
48
|
+
id: string;
|
|
49
|
+
/** Display name */
|
|
50
|
+
name: string;
|
|
51
|
+
/** Child nodes */
|
|
52
|
+
children: RepeaterTreeNode[];
|
|
53
|
+
/** Color for leaf nodes (tech:fband combinations) */
|
|
54
|
+
color?: string;
|
|
55
|
+
/** Whether this node is visible/selected */
|
|
56
|
+
visible?: boolean;
|
|
57
|
+
/** Repeater count for this node */
|
|
58
|
+
count?: number;
|
|
59
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repeater GeoJSON Conversion
|
|
3
|
+
*
|
|
4
|
+
* Convert repeater data to GeoJSON FeatureCollection with arc geometries
|
|
5
|
+
* Fixed 30° beamwidth for all repeaters
|
|
6
|
+
* Uses inverse zoom scaling like cells (larger arcs when zoomed out)
|
|
7
|
+
* Includes tech-band specific radius multipliers and z-ordering
|
|
8
|
+
*/
|
|
9
|
+
import type { FeatureCollection, Polygon } from 'geojson';
|
|
10
|
+
import type { Repeater } from '../types';
|
|
11
|
+
/**
|
|
12
|
+
* Convert repeaters to GeoJSON FeatureCollection with arc geometries
|
|
13
|
+
*
|
|
14
|
+
* @param repeaters - Array of repeaters to convert
|
|
15
|
+
* @param currentZoom - Current map zoom level for radius calculation
|
|
16
|
+
* @param baseRadius - Base radius in meters (zoom-reactive)
|
|
17
|
+
* @param techBandColorMap - Map of tech:fband -> color
|
|
18
|
+
* @returns GeoJSON FeatureCollection with arc polygons
|
|
19
|
+
*/
|
|
20
|
+
export declare function repeatersToGeoJSON(repeaters: Repeater[], currentZoom: number, baseRadius: number | undefined, techBandColorMap: Map<string, string>): FeatureCollection<Polygon>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repeater GeoJSON Conversion
|
|
3
|
+
*
|
|
4
|
+
* Convert repeater data to GeoJSON FeatureCollection with arc geometries
|
|
5
|
+
* Fixed 30° beamwidth for all repeaters
|
|
6
|
+
* Uses inverse zoom scaling like cells (larger arcs when zoomed out)
|
|
7
|
+
* Includes tech-band specific radius multipliers and z-ordering
|
|
8
|
+
*/
|
|
9
|
+
import { createArcPolygon } from '../../cells/utils/arcGeometry';
|
|
10
|
+
import { getRepeaterRadiusMultiplier } from '../constants/radiusMultipliers';
|
|
11
|
+
import { getRepeaterZOrder } from '../constants/techBandZOrder';
|
|
12
|
+
/** Fixed beamwidth for all repeaters */
|
|
13
|
+
const REPEATER_BEAMWIDTH = 30;
|
|
14
|
+
/** Reference zoom level where base radius is used as-is */
|
|
15
|
+
const REFERENCE_ZOOM = 12;
|
|
16
|
+
/**
|
|
17
|
+
* Get zoom factor for visual size scaling (inverse scaling)
|
|
18
|
+
* Lower zoom = larger radius (to compensate for zoomed out view)
|
|
19
|
+
* Higher zoom = smaller radius (to compensate for zoomed in view)
|
|
20
|
+
*
|
|
21
|
+
* Formula: 2^((referenceZoom - currentZoom) / 2)
|
|
22
|
+
* - At zoom 12: factor = 1.0
|
|
23
|
+
* - At zoom 10: factor = 2.0 (double the radius)
|
|
24
|
+
* - At zoom 14: factor = 0.5 (half the radius)
|
|
25
|
+
*/
|
|
26
|
+
function getZoomFactor(currentZoom) {
|
|
27
|
+
return Math.pow(2, (REFERENCE_ZOOM - currentZoom) / 2);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Convert repeaters to GeoJSON FeatureCollection with arc geometries
|
|
31
|
+
*
|
|
32
|
+
* @param repeaters - Array of repeaters to convert
|
|
33
|
+
* @param currentZoom - Current map zoom level for radius calculation
|
|
34
|
+
* @param baseRadius - Base radius in meters (zoom-reactive)
|
|
35
|
+
* @param techBandColorMap - Map of tech:fband -> color
|
|
36
|
+
* @returns GeoJSON FeatureCollection with arc polygons
|
|
37
|
+
*/
|
|
38
|
+
export function repeatersToGeoJSON(repeaters, currentZoom, baseRadius = 500, techBandColorMap) {
|
|
39
|
+
const features = repeaters.map((repeater) => {
|
|
40
|
+
// Get color for this tech:fband combination
|
|
41
|
+
const key = `${repeater.tech}:${repeater.fband}`;
|
|
42
|
+
const color = techBandColorMap.get(key) || '#888888'; // Fallback gray
|
|
43
|
+
// Calculate zoom-reactive radius with inverse scaling (like cells)
|
|
44
|
+
const zoomFactor = getZoomFactor(currentZoom);
|
|
45
|
+
// Apply tech-band specific radius multiplier
|
|
46
|
+
const techBandMultiplier = getRepeaterRadiusMultiplier(repeater.tech, repeater.fband);
|
|
47
|
+
const radius = baseRadius * zoomFactor * techBandMultiplier;
|
|
48
|
+
// Get z-order for layering (higher frequency = higher z-order = on top)
|
|
49
|
+
const zOrder = getRepeaterZOrder(repeater.tech, repeater.fband);
|
|
50
|
+
// Create arc polygon geometry with fixed 30° beamwidth
|
|
51
|
+
const center = [repeater.longitude, repeater.latitude];
|
|
52
|
+
const arc = createArcPolygon(center, repeater.azimuth, REPEATER_BEAMWIDTH, radius);
|
|
53
|
+
// Build feature with properties
|
|
54
|
+
return {
|
|
55
|
+
type: 'Feature',
|
|
56
|
+
geometry: arc.geometry,
|
|
57
|
+
properties: {
|
|
58
|
+
// Repeater identification
|
|
59
|
+
repeaterId: repeater.repeaterId,
|
|
60
|
+
factory_nbr: repeater.factory_nbr,
|
|
61
|
+
name: repeater.name || repeater.repeaterId,
|
|
62
|
+
// Donor cell info
|
|
63
|
+
donorCellId: repeater.donorCellId,
|
|
64
|
+
donorCellName: repeater.donorCellName,
|
|
65
|
+
// Technical attributes
|
|
66
|
+
tech: repeater.tech,
|
|
67
|
+
fband: repeater.fband,
|
|
68
|
+
azimuth: repeater.azimuth,
|
|
69
|
+
beamwidth: REPEATER_BEAMWIDTH,
|
|
70
|
+
height: repeater.height,
|
|
71
|
+
// Optional attributes
|
|
72
|
+
provider: repeater.provider || '',
|
|
73
|
+
featureGroup: repeater.featureGroup || '',
|
|
74
|
+
status: repeater.status || 'active',
|
|
75
|
+
// Styling properties for Mapbox
|
|
76
|
+
techBandKey: key,
|
|
77
|
+
techBandColor: color,
|
|
78
|
+
lineColor: color,
|
|
79
|
+
lineOpacity: 0.8,
|
|
80
|
+
// Z-order for layering (higher frequency on top)
|
|
81
|
+
zOrder: zOrder,
|
|
82
|
+
sortKey: zOrder // Used by Mapbox for layer ordering
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
type: 'FeatureCollection',
|
|
88
|
+
features
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repeater Tree Utilities
|
|
3
|
+
*
|
|
4
|
+
* Builds a fixed Tech→FBand two-level tree structure for repeater filtering.
|
|
5
|
+
* Leaf nodes (tech:fband) can have custom colors.
|
|
6
|
+
*/
|
|
7
|
+
import type { Repeater } from '../types';
|
|
8
|
+
import type { TreeNode } from '../../../../core/TreeView/tree.model';
|
|
9
|
+
/**
|
|
10
|
+
* Build a two-level tree: Tech → FBand
|
|
11
|
+
*
|
|
12
|
+
* @param repeaters - All repeaters
|
|
13
|
+
* @param colorMap - Map of tech:fband to color
|
|
14
|
+
* @returns Root tree node
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildRepeaterTree(repeaters: Repeater[], colorMap: Map<string, string>): TreeNode;
|
|
17
|
+
/**
|
|
18
|
+
* Get set of selected tech:fband keys from checked paths
|
|
19
|
+
*
|
|
20
|
+
* @param checkedPaths - Set of checked node paths from tree
|
|
21
|
+
* @returns Set of "tech:fband" keys that are checked
|
|
22
|
+
*/
|
|
23
|
+
export declare function getFilteredRepeaters(checkedPaths: Set<string>): Set<string>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repeater Tree Utilities
|
|
3
|
+
*
|
|
4
|
+
* Builds a fixed Tech→FBand two-level tree structure for repeater filtering.
|
|
5
|
+
* Leaf nodes (tech:fband) can have custom colors.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Build a two-level tree: Tech → FBand
|
|
9
|
+
*
|
|
10
|
+
* @param repeaters - All repeaters
|
|
11
|
+
* @param colorMap - Map of tech:fband to color
|
|
12
|
+
* @returns Root tree node
|
|
13
|
+
*/
|
|
14
|
+
export function buildRepeaterTree(repeaters, colorMap) {
|
|
15
|
+
// Group repeaters by tech, then by fband
|
|
16
|
+
const techMap = new Map();
|
|
17
|
+
for (const repeater of repeaters) {
|
|
18
|
+
const { tech, fband } = repeater;
|
|
19
|
+
if (!techMap.has(tech)) {
|
|
20
|
+
techMap.set(tech, new Map());
|
|
21
|
+
}
|
|
22
|
+
const fbandMap = techMap.get(tech);
|
|
23
|
+
if (!fbandMap.has(fband)) {
|
|
24
|
+
fbandMap.set(fband, []);
|
|
25
|
+
}
|
|
26
|
+
fbandMap.get(fband).push(repeater);
|
|
27
|
+
}
|
|
28
|
+
// Build tree structure
|
|
29
|
+
const techNodes = [];
|
|
30
|
+
// Sort technologies: 2G, 4G, 5G
|
|
31
|
+
const sortedTechs = Array.from(techMap.keys()).sort((a, b) => {
|
|
32
|
+
const order = { '2G': 1, '4G': 2, '5G': 3 };
|
|
33
|
+
return (order[a] || 99) - (order[b] || 99);
|
|
34
|
+
});
|
|
35
|
+
for (const tech of sortedTechs) {
|
|
36
|
+
const fbandMap = techMap.get(tech);
|
|
37
|
+
const fbandNodes = [];
|
|
38
|
+
// Sort fbands alphabetically
|
|
39
|
+
const sortedFbands = Array.from(fbandMap.keys()).sort();
|
|
40
|
+
for (const fband of sortedFbands) {
|
|
41
|
+
const repeatersInFband = fbandMap.get(fband);
|
|
42
|
+
const techBandKey = `${tech}:${fband}`;
|
|
43
|
+
const color = colorMap.get(techBandKey) || '#808080';
|
|
44
|
+
fbandNodes.push({
|
|
45
|
+
id: techBandKey,
|
|
46
|
+
label: `${fband} (${repeatersInFband.length})`,
|
|
47
|
+
defaultChecked: true,
|
|
48
|
+
defaultExpanded: false,
|
|
49
|
+
children: [],
|
|
50
|
+
metadata: { color } // Leaf nodes have colors in metadata
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
techNodes.push({
|
|
54
|
+
id: tech,
|
|
55
|
+
label: `${tech} (${Array.from(fbandMap.values()).flat().length})`,
|
|
56
|
+
defaultChecked: true,
|
|
57
|
+
defaultExpanded: true,
|
|
58
|
+
children: fbandNodes
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// Root node
|
|
62
|
+
return {
|
|
63
|
+
id: 'root',
|
|
64
|
+
label: `All Repeaters (${repeaters.length})`,
|
|
65
|
+
defaultChecked: true,
|
|
66
|
+
defaultExpanded: true,
|
|
67
|
+
children: techNodes
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get set of selected tech:fband keys from checked paths
|
|
72
|
+
*
|
|
73
|
+
* @param checkedPaths - Set of checked node paths from tree
|
|
74
|
+
* @returns Set of "tech:fband" keys that are checked
|
|
75
|
+
*/
|
|
76
|
+
export function getFilteredRepeaters(checkedPaths) {
|
|
77
|
+
const selected = new Set();
|
|
78
|
+
console.log('getFilteredRepeaters - checkedPaths:', Array.from(checkedPaths));
|
|
79
|
+
// If only root is checked, all tech:fbands are visible
|
|
80
|
+
if (checkedPaths.has('root') && checkedPaths.size === 1) {
|
|
81
|
+
console.log('Only root checked - returning empty set for "all visible"');
|
|
82
|
+
return selected;
|
|
83
|
+
}
|
|
84
|
+
// Check each path - leaf nodes have tech:fband in their ID
|
|
85
|
+
for (const path of checkedPaths) {
|
|
86
|
+
// Paths are like "root:2G:2G:GSM900" where the node ID is the last segment
|
|
87
|
+
// The node ID for leaf nodes is "tech:fband" format
|
|
88
|
+
const parts = path.split(':');
|
|
89
|
+
console.log('Processing path:', path, 'parts:', parts);
|
|
90
|
+
// Check if this is a leaf node (contains tech:fband format)
|
|
91
|
+
// Leaf node IDs are like "2G:GSM900", "4G:LTE1800"
|
|
92
|
+
// The path would be "root:2G:2G:GSM900" (root -> tech -> tech:fband)
|
|
93
|
+
if (parts.length === 4 && parts[0] === 'root') {
|
|
94
|
+
// This is a leaf node: root:tech:tech:fband
|
|
95
|
+
const techBandKey = `${parts[2]}:${parts[3]}`;
|
|
96
|
+
console.log('Adding tech:fband key:', techBandKey);
|
|
97
|
+
selected.add(techBandKey);
|
|
98
|
+
}
|
|
99
|
+
else if (parts.length === 3 && parts[0] === 'root') {
|
|
100
|
+
// This might be "root:2G:GSM900" if node ID is "2G:GSM900"
|
|
101
|
+
const nodeId = parts[2];
|
|
102
|
+
if (nodeId && nodeId.includes(':')) {
|
|
103
|
+
// This is already in tech:fband format
|
|
104
|
+
console.log('Adding tech:fband key (alternative format):', nodeId);
|
|
105
|
+
selected.add(nodeId);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
console.log('getFilteredRepeaters - selected tech:bands:', Array.from(selected));
|
|
110
|
+
return selected;
|
|
111
|
+
}
|
package/dist/map-v2/index.d.ts
CHANGED
|
@@ -8,4 +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 {
|
|
11
|
+
export { type Repeater, type RepeaterTreeNode, type RepeaterStoreValue, type RepeaterStoreContext, createRepeaterStoreContext, RepeatersLayer, RepeaterLabelsLayer, RepeaterFilterControl, repeatersToGeoJSON, buildRepeaterTree, getFilteredRepeaters, getRepeaterRadiusMultiplier, getRepeaterZOrder, REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX, REPEATER_RADIUS_MULTIPLIER, REPEATER_TECH_BAND_Z_ORDER } from './features/repeaters';
|
|
12
|
+
export { DemoMap, demoSites, demoCells, demoRepeaters } from './demo';
|
package/dist/map-v2/index.js
CHANGED
|
@@ -21,6 +21,10 @@ export { createSiteStore, createSiteStoreContext, SitesLayer, SiteFilterControl,
|
|
|
21
21
|
// ============================================================================
|
|
22
22
|
export { 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';
|
|
23
23
|
// ============================================================================
|
|
24
|
+
// REPEATER FEATURE
|
|
25
|
+
// ============================================================================
|
|
26
|
+
export { createRepeaterStoreContext, RepeatersLayer, RepeaterLabelsLayer, RepeaterFilterControl, repeatersToGeoJSON, buildRepeaterTree, getFilteredRepeaters, getRepeaterRadiusMultiplier, getRepeaterZOrder, REPEATER_FILL_Z_INDEX, REPEATER_LINE_Z_INDEX, REPEATER_LABEL_Z_INDEX, REPEATER_RADIUS_MULTIPLIER, REPEATER_TECH_BAND_Z_ORDER } from './features/repeaters';
|
|
27
|
+
// ============================================================================
|
|
24
28
|
// DEMO
|
|
25
29
|
// ============================================================================
|
|
26
|
-
export { DemoMap, demoSites, demoCells } from './demo';
|
|
30
|
+
export { DemoMap, demoSites, demoCells, demoRepeaters } from './demo';
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import RepeaterSettingsPanel from './panels/RepeaterSettingsPanel.svelte';
|
|
13
13
|
import type { SiteStoreContext } from '../../features/sites/stores/siteStoreContext.svelte';
|
|
14
14
|
import type { CellStoreContext } from '../../features/cells/stores/cellStoreContext.svelte';
|
|
15
|
+
import type { RepeaterStoreContext } from '../../features/repeaters/stores/repeaterStoreContext.svelte';
|
|
15
16
|
import type { Cell, CellGroupingLabels } from '../../features/cells/types';
|
|
16
17
|
|
|
17
18
|
interface Props {
|
|
@@ -19,6 +20,8 @@
|
|
|
19
20
|
siteStore: SiteStoreContext;
|
|
20
21
|
/** Cell store context */
|
|
21
22
|
cellStore: CellStoreContext;
|
|
23
|
+
/** Repeater store context (optional) */
|
|
24
|
+
repeaterStore?: RepeaterStoreContext;
|
|
22
25
|
/** Available label field options for 4G/5G cells */
|
|
23
26
|
labelFieldOptions4G5G: Array<keyof Cell>;
|
|
24
27
|
/** Available label field options for 2G cells */
|
|
@@ -40,6 +43,7 @@
|
|
|
40
43
|
let {
|
|
41
44
|
siteStore,
|
|
42
45
|
cellStore,
|
|
46
|
+
repeaterStore,
|
|
43
47
|
labelFieldOptions4G5G,
|
|
44
48
|
labelFieldOptions2G,
|
|
45
49
|
fieldLabels,
|
|
@@ -111,30 +115,32 @@
|
|
|
111
115
|
</div>
|
|
112
116
|
</div>
|
|
113
117
|
|
|
114
|
-
<!-- Repeater Settings Accordion Item
|
|
115
|
-
|
|
116
|
-
<
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
<!-- Repeater Settings Accordion Item -->
|
|
119
|
+
{#if repeaterStore}
|
|
120
|
+
<div class="accordion-item">
|
|
121
|
+
<h2 class="accordion-header" id="heading-repeaters">
|
|
122
|
+
<button
|
|
123
|
+
class="accordion-button collapsed"
|
|
124
|
+
type="button"
|
|
125
|
+
data-bs-toggle="collapse"
|
|
126
|
+
data-bs-target="#collapse-repeaters"
|
|
127
|
+
aria-expanded="false"
|
|
128
|
+
aria-controls="collapse-repeaters"
|
|
129
|
+
>
|
|
130
|
+
Repeater Settings
|
|
131
|
+
</button>
|
|
132
|
+
</h2>
|
|
133
|
+
<div
|
|
134
|
+
id="collapse-repeaters"
|
|
135
|
+
class="accordion-collapse collapse"
|
|
136
|
+
aria-labelledby="heading-repeaters"
|
|
124
137
|
>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
<div
|
|
129
|
-
id="collapse-repeaters"
|
|
130
|
-
class="accordion-collapse collapse"
|
|
131
|
-
aria-labelledby="heading-repeaters"
|
|
132
|
-
>
|
|
133
|
-
<div class="accordion-body">
|
|
134
|
-
<RepeaterSettingsPanel />
|
|
138
|
+
<div class="accordion-body">
|
|
139
|
+
<RepeaterSettingsPanel store={repeaterStore} />
|
|
140
|
+
</div>
|
|
135
141
|
</div>
|
|
136
142
|
</div>
|
|
137
|
-
|
|
143
|
+
{/if}
|
|
138
144
|
</div>
|
|
139
145
|
</MapControl>
|
|
140
146
|
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { SiteStoreContext } from '../../features/sites/stores/siteStoreContext.svelte';
|
|
2
2
|
import type { CellStoreContext } from '../../features/cells/stores/cellStoreContext.svelte';
|
|
3
|
+
import type { RepeaterStoreContext } from '../../features/repeaters/stores/repeaterStoreContext.svelte';
|
|
3
4
|
import type { Cell, CellGroupingLabels } from '../../features/cells/types';
|
|
4
5
|
interface Props {
|
|
5
6
|
/** Site store context */
|
|
6
7
|
siteStore: SiteStoreContext;
|
|
7
8
|
/** Cell store context */
|
|
8
9
|
cellStore: CellStoreContext;
|
|
10
|
+
/** Repeater store context (optional) */
|
|
11
|
+
repeaterStore?: RepeaterStoreContext;
|
|
9
12
|
/** Available label field options for 4G/5G cells */
|
|
10
13
|
labelFieldOptions4G5G: Array<keyof Cell>;
|
|
11
14
|
/** Available label field options for 2G cells */
|