@smartnet360/svelte-components 0.0.85 → 0.0.87

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.
Files changed (115) hide show
  1. package/dist/apps/antenna-pattern/components/AntennaControls.svelte +1 -106
  2. package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte +0 -36
  3. package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte +0 -2
  4. package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte +0 -22
  5. package/dist/apps/antenna-pattern/components/chart-engines/PolarAreaChart.svelte +0 -2
  6. package/dist/apps/antenna-pattern/components/chart-engines/PolarBarChart.svelte +0 -2
  7. package/dist/apps/antenna-pattern/components/chart-engines/PolarLineChart.svelte +0 -2
  8. package/dist/apps/site-check/data-loader.js +0 -8
  9. package/dist/core/Charts/GlobalControls.svelte +0 -4
  10. package/dist/core/Desktop/Grid/ResizeHandle.svelte +0 -7
  11. package/dist/core/Desktop/Grid/resizeStore.js +0 -1
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.js +2 -0
  14. package/dist/map-v2/demo/DemoMap.svelte +0 -2
  15. package/dist/map-v2/demo/demo-cells.js +0 -1
  16. package/dist/map-v2/features/cells/layers/CellsLayer.svelte +7 -26
  17. package/dist/map-v2/features/cells/utils/cellTree.js +0 -29
  18. package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte +3 -27
  19. package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +8 -25
  20. package/dist/map-v2/features/repeaters/utils/repeaterTree.js +0 -6
  21. package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +0 -8
  22. package/dist/map-v2/features/sites/utils/siteTreeUtils.js +0 -6
  23. package/dist/map-v3/core/components/Map.svelte +89 -0
  24. package/dist/map-v3/core/components/Map.svelte.d.ts +13 -0
  25. package/dist/map-v3/core/controls/FeatureSettingsControl.svelte +103 -0
  26. package/dist/map-v3/core/controls/FeatureSettingsControl.svelte.d.ts +15 -0
  27. package/dist/map-v3/core/controls/MapStyleControl.svelte +271 -0
  28. package/dist/map-v3/core/controls/MapStyleControl.svelte.d.ts +28 -0
  29. package/dist/map-v3/core/index.d.ts +6 -0
  30. package/dist/map-v3/core/index.js +5 -0
  31. package/dist/map-v3/core/stores/map.store.svelte.d.ts +8 -0
  32. package/dist/map-v3/core/stores/map.store.svelte.js +29 -0
  33. package/dist/map-v3/core/stores/viewport.store.svelte.d.ts +38 -0
  34. package/dist/map-v3/core/stores/viewport.store.svelte.js +107 -0
  35. package/dist/map-v3/demo/DemoMap.svelte +104 -0
  36. package/dist/map-v3/demo/DemoMap.svelte.d.ts +6 -0
  37. package/dist/map-v3/demo/demo-cells.d.ts +13 -0
  38. package/dist/map-v3/demo/demo-cells.js +130 -0
  39. package/dist/map-v3/demo/demo-data.d.ts +8 -0
  40. package/dist/map-v3/demo/demo-data.js +104 -0
  41. package/dist/map-v3/demo/demo-repeaters.d.ts +13 -0
  42. package/dist/map-v3/demo/demo-repeaters.js +73 -0
  43. package/dist/map-v3/features/cells/components/CellFilterControl.svelte +208 -0
  44. package/dist/map-v3/features/cells/components/CellFilterControl.svelte.d.ts +12 -0
  45. package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte +229 -0
  46. package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte.d.ts +7 -0
  47. package/dist/map-v3/features/cells/constants.d.ts +18 -0
  48. package/dist/map-v3/features/cells/constants.js +37 -0
  49. package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte +230 -0
  50. package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte.d.ts +11 -0
  51. package/dist/map-v3/features/cells/layers/CellsLayer.svelte +194 -0
  52. package/dist/map-v3/features/cells/layers/CellsLayer.svelte.d.ts +11 -0
  53. package/dist/map-v3/features/cells/layers/index.d.ts +2 -0
  54. package/dist/map-v3/features/cells/layers/index.js +2 -0
  55. package/dist/map-v3/features/cells/logic/geometry.d.ts +12 -0
  56. package/dist/map-v3/features/cells/logic/geometry.js +35 -0
  57. package/dist/map-v3/features/cells/logic/grouping.d.ts +18 -0
  58. package/dist/map-v3/features/cells/logic/grouping.js +30 -0
  59. package/dist/map-v3/features/cells/logic/tree-adapter.d.ts +11 -0
  60. package/dist/map-v3/features/cells/logic/tree-adapter.js +53 -0
  61. package/dist/map-v3/features/cells/stores/cell.data.svelte.d.ts +9 -0
  62. package/dist/map-v3/features/cells/stores/cell.data.svelte.js +16 -0
  63. package/dist/map-v3/features/cells/stores/cell.display.svelte.d.ts +25 -0
  64. package/dist/map-v3/features/cells/stores/cell.display.svelte.js +67 -0
  65. package/dist/map-v3/features/cells/stores/cell.registry.svelte.d.ts +23 -0
  66. package/dist/map-v3/features/cells/stores/cell.registry.svelte.js +68 -0
  67. package/dist/map-v3/features/cells/types.d.ts +62 -0
  68. package/dist/map-v3/features/cells/types.js +6 -0
  69. package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +148 -0
  70. package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte.d.ts +12 -0
  71. package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte +209 -0
  72. package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte.d.ts +7 -0
  73. package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte +177 -0
  74. package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte.d.ts +11 -0
  75. package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte +163 -0
  76. package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte.d.ts +11 -0
  77. package/dist/map-v3/features/repeaters/logic/geometry.d.ts +3 -0
  78. package/dist/map-v3/features/repeaters/logic/geometry.js +23 -0
  79. package/dist/map-v3/features/repeaters/logic/grouping.d.ts +8 -0
  80. package/dist/map-v3/features/repeaters/logic/grouping.js +20 -0
  81. package/dist/map-v3/features/repeaters/logic/tree-adapter.d.ts +8 -0
  82. package/dist/map-v3/features/repeaters/logic/tree-adapter.js +43 -0
  83. package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.d.ts +8 -0
  84. package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.js +13 -0
  85. package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.d.ts +21 -0
  86. package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.js +64 -0
  87. package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.d.ts +23 -0
  88. package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.js +68 -0
  89. package/dist/map-v3/features/repeaters/types.d.ts +18 -0
  90. package/dist/map-v3/features/repeaters/types.js +1 -0
  91. package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +119 -0
  92. package/dist/map-v3/features/sites/components/SiteFilterControl.svelte.d.ts +12 -0
  93. package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte +241 -0
  94. package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte.d.ts +7 -0
  95. package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte +152 -0
  96. package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte.d.ts +11 -0
  97. package/dist/map-v3/features/sites/layers/SitesLayer.svelte +132 -0
  98. package/dist/map-v3/features/sites/layers/SitesLayer.svelte.d.ts +11 -0
  99. package/dist/map-v3/features/sites/logic/tree-adapter.d.ts +9 -0
  100. package/dist/map-v3/features/sites/logic/tree-adapter.js +75 -0
  101. package/dist/map-v3/features/sites/stores/site.data.svelte.d.ts +8 -0
  102. package/dist/map-v3/features/sites/stores/site.data.svelte.js +40 -0
  103. package/dist/map-v3/features/sites/stores/site.display.svelte.d.ts +20 -0
  104. package/dist/map-v3/features/sites/stores/site.display.svelte.js +63 -0
  105. package/dist/map-v3/features/sites/stores/site.registry.svelte.d.ts +13 -0
  106. package/dist/map-v3/features/sites/stores/site.registry.svelte.js +83 -0
  107. package/dist/map-v3/features/sites/types.d.ts +12 -0
  108. package/dist/map-v3/features/sites/types.js +1 -0
  109. package/dist/map-v3/index.d.ts +30 -0
  110. package/dist/map-v3/index.js +36 -0
  111. package/dist/map-v3/shared/controls/MapControl.svelte +242 -0
  112. package/dist/map-v3/shared/controls/MapControl.svelte.d.ts +27 -0
  113. package/dist/map-v3/shared/index.d.ts +1 -0
  114. package/dist/map-v3/shared/index.js +1 -0
  115. package/package.json +1 -1
@@ -0,0 +1,229 @@
1
+ <script lang="ts">
2
+ import type { CellDisplayStore } from '../stores/cell.display.svelte';
3
+
4
+ interface Props {
5
+ displayStore: CellDisplayStore;
6
+ }
7
+
8
+ let { displayStore }: Props = $props();
9
+ </script>
10
+
11
+ <div class="card border-0 shadow-sm rounded-2">
12
+ <div class="card-body bg-light p-3">
13
+ <!-- Target Pixel Size (Radius) -->
14
+ <div class="row align-items-center g-2 mb-3">
15
+ <div class="col-4 text-secondary fw-semibold small text-uppercase">Pixel Size</div>
16
+ <div class="col-3 text-end">
17
+ <span class="badge bg-white text-muted border">{displayStore.targetPixelSize}px</span>
18
+ </div>
19
+ <div class="col-5">
20
+ <input
21
+ id="cell-radius-slider"
22
+ type="range"
23
+ class="form-range w-100"
24
+ min="10"
25
+ max="200"
26
+ step="5"
27
+ bind:value={displayStore.targetPixelSize}
28
+ />
29
+ </div>
30
+ </div>
31
+
32
+ <!-- Line Width -->
33
+ <div class="row align-items-center g-2 mb-3">
34
+ <div class="col-4 text-secondary fw-semibold small text-uppercase">Line Width</div>
35
+ <div class="col-3 text-end">
36
+ <span class="badge bg-white text-muted border">{displayStore.lineWidth}px</span>
37
+ </div>
38
+ <div class="col-5">
39
+ <input
40
+ id="cell-line-width-slider"
41
+ type="range"
42
+ class="form-range w-100"
43
+ min="0.5"
44
+ max="5"
45
+ step="0.5"
46
+ bind:value={displayStore.lineWidth}
47
+ />
48
+ </div>
49
+ </div>
50
+
51
+ <!-- Fill Opacity -->
52
+ <div class="row align-items-center g-2 mb-3">
53
+ <div class="col-4 text-secondary fw-semibold small text-uppercase">Fill Opacity</div>
54
+ <div class="col-3 text-end">
55
+ <span class="badge bg-white text-muted border">{Math.round(displayStore.fillOpacity * 100)}%</span>
56
+ </div>
57
+ <div class="col-5">
58
+ <input
59
+ id="cell-opacity-slider"
60
+ type="range"
61
+ class="form-range w-100"
62
+ min="0"
63
+ max="1"
64
+ step="0.1"
65
+ bind:value={displayStore.fillOpacity}
66
+ />
67
+ </div>
68
+ </div>
69
+
70
+ <div class="border-top my-3"></div>
71
+
72
+ <!-- Show Labels -->
73
+ <div class="row align-items-center g-2 mb-3">
74
+ <div class="col-4 text-secondary fw-semibold small text-uppercase">Show Labels</div>
75
+ <div class="col-3"></div>
76
+ <div class="col-5">
77
+ <div class="form-check form-switch m-0 d-flex align-items-center justify-content-end">
78
+ <input
79
+ id="cell-labels-toggle"
80
+ type="checkbox"
81
+ class="form-check-input"
82
+ role="switch"
83
+ bind:checked={displayStore.showLabels}
84
+ />
85
+ </div>
86
+ </div>
87
+ </div>
88
+
89
+ {#if displayStore.showLabels}
90
+ <div class="ps-3 border-start border-2 mb-3">
91
+ <!-- 2G Labels -->
92
+ <div class="mb-2">
93
+ <div class="text-secondary fw-semibold small text-uppercase mb-1">2G Labels</div>
94
+ <div class="row g-1">
95
+ <div class="col-6">
96
+ <select class="form-select form-select-sm" bind:value={displayStore.labels2G.primary}>
97
+ <option value="cellName">Name</option>
98
+ <option value="cellID">Cell ID</option>
99
+ <option value="bcch">BCCH</option>
100
+ <option value="siteId">Site ID</option>
101
+ </select>
102
+ </div>
103
+ <div class="col-6">
104
+ <select class="form-select form-select-sm" bind:value={displayStore.labels2G.secondary}>
105
+ <option value="none">None</option>
106
+ <option value="bcch">BCCH</option>
107
+ <option value="cellID">Cell ID</option>
108
+ <option value="siteId">Site ID</option>
109
+ </select>
110
+ </div>
111
+ </div>
112
+ </div>
113
+
114
+ <!-- 4G/5G Labels -->
115
+ <div class="mb-2">
116
+ <div class="text-secondary fw-semibold small text-uppercase mb-1">4G/5G Labels</div>
117
+ <div class="row g-1">
118
+ <div class="col-6">
119
+ <select class="form-select form-select-sm" bind:value={displayStore.labels4G.primary}>
120
+ <option value="cellName">Name</option>
121
+ <option value="pci1">PCI</option>
122
+ <option value="dlEarfn">EARFCN</option>
123
+ <option value="cellID">Cell ID</option>
124
+ </select>
125
+ </div>
126
+ <div class="col-6">
127
+ <select class="form-select form-select-sm" bind:value={displayStore.labels4G.secondary}>
128
+ <option value="none">None</option>
129
+ <option value="pci1">PCI</option>
130
+ <option value="dlEarfn">EARFCN</option>
131
+ <option value="cellID">Cell ID</option>
132
+ </select>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ <!-- Label Distance -->
137
+ <div class="row align-items-center g-2 mb-2">
138
+ <div class="col-4 text-secondary small">Distance</div>
139
+ <div class="col-3 text-end">
140
+ <span class="badge bg-white text-muted border">{displayStore.labelPixelDistance}px</span>
141
+ </div>
142
+ <div class="col-5">
143
+ <input
144
+ type="range"
145
+ class="form-range w-100"
146
+ min="10"
147
+ max="100"
148
+ step="5"
149
+ bind:value={displayStore.labelPixelDistance}
150
+ />
151
+ </div>
152
+ </div>
153
+
154
+ <!-- Font Size -->
155
+ <div class="row align-items-center g-2 mb-3">
156
+ <div class="col-4 text-secondary small">Font Size</div>
157
+ <div class="col-3 text-end">
158
+ <span class="badge bg-white text-muted border">{displayStore.labelFontSize}px</span>
159
+ </div>
160
+ <div class="col-5">
161
+ <input
162
+ type="range"
163
+ class="form-range w-100"
164
+ min="8"
165
+ max="24"
166
+ step="1"
167
+ bind:value={displayStore.labelFontSize}
168
+ />
169
+ </div>
170
+ </div>
171
+
172
+ <!-- Label Colors -->
173
+ <div class="row align-items-center g-2 mb-2">
174
+ <div class="col-4 text-secondary small">Color</div>
175
+ <div class="col-8">
176
+ <div class="input-group input-group-sm">
177
+ <input
178
+ type="color"
179
+ class="form-control form-control-color"
180
+ bind:value={displayStore.labelColor}
181
+ title="Label Color"
182
+ />
183
+ <span class="input-group-text bg-white text-muted small flex-grow-1 font-monospace">
184
+ {displayStore.labelColor}
185
+ </span>
186
+ </div>
187
+ </div>
188
+ </div>
189
+
190
+ <!-- Halo Settings -->
191
+ <div class="row align-items-center g-2 mb-2">
192
+ <div class="col-4 text-secondary small">Halo</div>
193
+ <div class="col-8">
194
+ <div class="input-group input-group-sm">
195
+ <input
196
+ type="color"
197
+ class="form-control form-control-color"
198
+ bind:value={displayStore.labelHaloColor}
199
+ title="Halo Color"
200
+ />
201
+ <span class="input-group-text bg-white text-muted small flex-grow-1 font-monospace">
202
+ {displayStore.labelHaloColor}
203
+ </span>
204
+ </div>
205
+ </div>
206
+ </div>
207
+
208
+ <div class="row align-items-center g-2 mb-3">
209
+ <div class="col-4 text-secondary small">Halo Width</div>
210
+ <div class="col-3 text-end">
211
+ <span class="badge bg-white text-muted border">{displayStore.labelHaloWidth}px</span>
212
+ </div>
213
+ <div class="col-5">
214
+ <input
215
+ type="range"
216
+ class="form-range w-100"
217
+ min="0"
218
+ max="5"
219
+ step="0.5"
220
+ bind:value={displayStore.labelHaloWidth}
221
+ />
222
+ </div>
223
+ </div>
224
+
225
+
226
+ </div>
227
+ {/if}
228
+ </div>
229
+ </div>
@@ -0,0 +1,7 @@
1
+ import type { CellDisplayStore } from '../stores/cell.display.svelte';
2
+ interface Props {
3
+ displayStore: CellDisplayStore;
4
+ }
5
+ declare const CellSettingsPanel: import("svelte").Component<Props, {}, "">;
6
+ type CellSettingsPanel = ReturnType<typeof CellSettingsPanel>;
7
+ export default CellSettingsPanel;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Z-Index Layer Ordering by Technology-Band
3
+ *
4
+ * Controls which sectors appear on top when overlapping
5
+ * Higher frequency bands typically rendered on top
6
+ */
7
+ import type { TechnologyBandKey } from './types';
8
+ export declare const Z_INDEX_BY_BAND: Record<TechnologyBandKey, number>;
9
+ /**
10
+ * Base Z-Index for Mapbox layer ordering
11
+ * Cells should render below sites but above base map features
12
+ */
13
+ export declare const CELL_FILL_Z_INDEX = 100;
14
+ export declare const CELL_LINE_Z_INDEX = 101;
15
+ /**
16
+ * Default colors for cyclical palette
17
+ */
18
+ export declare const DEFAULT_PALETTE: string[];
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Z-Index Layer Ordering by Technology-Band
3
+ *
4
+ * Controls which sectors appear on top when overlapping
5
+ * Higher frequency bands typically rendered on top
6
+ */
7
+ export const Z_INDEX_BY_BAND = {
8
+ // 2G bands
9
+ '2G_900': 3,
10
+ '2G_1800': 7,
11
+ // 4G bands
12
+ '4G_700': 0,
13
+ '4G_800': 2,
14
+ '4G_900': 4,
15
+ '4G_1800': 8,
16
+ '4G_2100': 9,
17
+ '4G_2600': 11,
18
+ // 5G bands
19
+ '5G_700': 1,
20
+ '5G_2100': 10,
21
+ '5G_3500': 14
22
+ };
23
+ /**
24
+ * Base Z-Index for Mapbox layer ordering
25
+ * Cells should render below sites but above base map features
26
+ */
27
+ export const CELL_FILL_Z_INDEX = 100;
28
+ export const CELL_LINE_Z_INDEX = 101;
29
+ /**
30
+ * Default colors for cyclical palette
31
+ */
32
+ export const DEFAULT_PALETTE = [
33
+ '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
34
+ '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe',
35
+ '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000',
36
+ '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080'
37
+ ];
@@ -0,0 +1,230 @@
1
+ <script lang="ts">
2
+ import { getContext } from 'svelte';
3
+ import type { MapStore } from '../../../core/stores/map.store.svelte';
4
+ import type { CellDataStore } from '../stores/cell.data.svelte';
5
+ import type { CellDisplayStore } from '../stores/cell.display.svelte';
6
+ import type { CellRegistry } from '../stores/cell.registry.svelte';
7
+ import { groupCells, getColorForGroup } from '../logic/grouping';
8
+ import type { Cell } from '../types';
9
+ import type mapboxgl from 'mapbox-gl';
10
+
11
+ interface Props {
12
+ dataStore: CellDataStore;
13
+ displayStore: CellDisplayStore;
14
+ registry: CellRegistry;
15
+ }
16
+
17
+ let { dataStore, displayStore, registry }: Props = $props();
18
+
19
+ const mapStore = getContext<MapStore>('MAP_CONTEXT');
20
+ let sourceId = 'cell-labels-source';
21
+ let layerId = 'cell-labels-layer';
22
+ let updateTimeout: any;
23
+
24
+ // Helper to get label text
25
+ function getLabelText(cell: Cell, config: { primary: string, secondary: string }): string {
26
+ const pVal = cell[config.primary as keyof Cell];
27
+ const sVal = config.secondary !== 'none' ? cell[config.secondary as keyof Cell] : null;
28
+
29
+ let text = pVal != null ? String(pVal) : '';
30
+ if (sVal != null) {
31
+ text += ` | ${sVal}`;
32
+ }
33
+ return text;
34
+ }
35
+
36
+ // Helper to round azimuth
37
+ function roundAzimuth(azimuth: number, tolerance: number): number {
38
+ return Math.round(azimuth / tolerance) * tolerance;
39
+ }
40
+
41
+ // React to changes
42
+ $effect(() => {
43
+ // Read dependencies
44
+ const _cells = dataStore.filteredCells;
45
+ const _show = displayStore.showLabels;
46
+ const _dist = displayStore.labelPixelDistance;
47
+ const _size = displayStore.labelFontSize;
48
+ const _tol = displayStore.labelAzimuthTolerance;
49
+ const _l2g = displayStore.labels2G;
50
+ const _l4g = displayStore.labels4G;
51
+ const _color = displayStore.labelColor;
52
+ const _haloColor = displayStore.labelHaloColor;
53
+ const _haloWidth = displayStore.labelHaloWidth;
54
+ const _registryVersion = registry.version;
55
+
56
+ const map = mapStore.map;
57
+ if (map && map.getLayer(layerId)) {
58
+ map.setPaintProperty(layerId, 'text-color', displayStore.labelColor);
59
+ map.setPaintProperty(layerId, 'text-halo-color', displayStore.labelHaloColor);
60
+ map.setPaintProperty(layerId, 'text-halo-width', displayStore.labelHaloWidth);
61
+ }
62
+
63
+ updateLayer();
64
+ });
65
+
66
+ function updateLayer() {
67
+ const map = mapStore.map;
68
+ if (!map) return;
69
+
70
+ clearTimeout(updateTimeout);
71
+ updateTimeout = setTimeout(() => {
72
+ renderLabels(map);
73
+ }, 150);
74
+ }
75
+
76
+ function renderLabels(map: mapboxgl.Map) {
77
+ if (!displayStore.showLabels) {
78
+ if (map.getLayer(layerId)) map.setLayoutProperty(layerId, 'visibility', 'none');
79
+ return;
80
+ }
81
+
82
+ if (map.getLayer(layerId)) map.setLayoutProperty(layerId, 'visibility', 'visible');
83
+
84
+ // Update text size if changed
85
+ if (map.getLayer(layerId)) {
86
+ map.setLayoutProperty(layerId, 'text-size', displayStore.labelFontSize);
87
+ }
88
+
89
+ const bounds = map.getBounds();
90
+ if (!bounds) return;
91
+
92
+ // 1. Filter visible cells (Bounds + Registry Visibility)
93
+ const visibleCells: Cell[] = [];
94
+
95
+ // Group cells using the same logic as CellsLayer to determine visibility
96
+ const cellGroups = groupCells(dataStore.filteredCells, displayStore.level1, displayStore.level2);
97
+ let groupIndex = 0;
98
+
99
+ for (const [groupId, cells] of cellGroups) {
100
+ const defaultColor = getColorForGroup(groupIndex++);
101
+ const style = registry.getStyle(groupId, defaultColor);
102
+
103
+ if (style.visible) {
104
+ for (const cell of cells) {
105
+ if (bounds.contains([cell.longitude, cell.latitude])) {
106
+ visibleCells.push(cell);
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ // 2. Group by Site + Azimuth
113
+ const groups = new Map<string, Cell[]>();
114
+ for (const cell of visibleCells) {
115
+ const az = roundAzimuth(cell.azimuth, displayStore.labelAzimuthTolerance);
116
+ const key = `${cell.siteId}_${az}`;
117
+ if (!groups.has(key)) groups.set(key, []);
118
+ groups.get(key)?.push(cell);
119
+ }
120
+
121
+ const features: GeoJSON.Feature[] = [];
122
+ const distEm = displayStore.labelPixelDistance / displayStore.labelFontSize;
123
+
124
+ // 3. Generate Features
125
+ for (const [key, cells] of groups) {
126
+ // Sort cells (e.g. by band/tech) to have consistent order
127
+ cells.sort((a, b) => {
128
+ const ta = a.tech + a.fband;
129
+ const tb = b.tech + b.fband;
130
+ return ta.localeCompare(tb);
131
+ });
132
+
133
+ // Calculate base offset for the group
134
+ const az = roundAzimuth(cells[0].azimuth, displayStore.labelAzimuthTolerance);
135
+ const azRad = (az * Math.PI) / 180;
136
+
137
+ // Mapbox text-offset: [x, y] in ems.
138
+ // x is right, y is down.
139
+ // 0 deg (N) -> x=0, y=-1
140
+ const baseX = Math.sin(azRad) * distEm;
141
+ const baseY = -Math.cos(azRad) * distEm;
142
+
143
+ // Center the stack vertically around the base point
144
+ const totalHeight = cells.length * 1.2; // 1.2 em line height
145
+ const startY = baseY - (totalHeight / 2) + 0.6; // +0.6 to center first line
146
+
147
+ cells.forEach((cell, index) => {
148
+ const is2G = cell.tech.includes('2G');
149
+ const config = is2G ? displayStore.labels2G : displayStore.labels4G;
150
+ const text = getLabelText(cell, config);
151
+
152
+ if (!text) return;
153
+
154
+ const stackY = startY + (index * 1.2);
155
+
156
+ features.push({
157
+ type: 'Feature',
158
+ geometry: {
159
+ type: 'Point',
160
+ coordinates: [cell.longitude, cell.latitude]
161
+ },
162
+ properties: {
163
+ text,
164
+ offset: [baseX, stackY], // Array property
165
+ color: '#333333'
166
+ }
167
+ });
168
+ });
169
+ }
170
+
171
+ // 4. Update Source
172
+ const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource;
173
+ if (source) {
174
+ source.setData({
175
+ type: 'FeatureCollection',
176
+ features: features as any
177
+ });
178
+ }
179
+ }
180
+
181
+ // Initial Setup
182
+ $effect(() => {
183
+ const map = mapStore.map;
184
+ if (!map) return;
185
+
186
+ const addLayers = () => {
187
+ if (!map.getSource(sourceId)) {
188
+ map.addSource(sourceId, {
189
+ type: 'geojson',
190
+ data: { type: 'FeatureCollection', features: [] }
191
+ });
192
+ }
193
+
194
+ if (!map.getLayer(layerId)) {
195
+ map.addLayer({
196
+ id: layerId,
197
+ type: 'symbol',
198
+ source: sourceId,
199
+ layout: {
200
+ 'text-field': ['get', 'text'],
201
+ 'text-font': ['Open Sans Regular', 'Arial Unicode MS Regular'],
202
+ 'text-size': displayStore.labelFontSize,
203
+ 'text-offset': ['get', 'offset'],
204
+ 'text-allow-overlap': true,
205
+ 'text-ignore-placement': true,
206
+ 'text-anchor': 'center'
207
+ },
208
+ paint: {
209
+ 'text-color': displayStore.labelColor,
210
+ 'text-halo-color': displayStore.labelHaloColor,
211
+ 'text-halo-width': displayStore.labelHaloWidth
212
+ }
213
+ });
214
+ }
215
+ };
216
+
217
+ addLayers();
218
+ map.on('style.load', addLayers);
219
+ map.on('moveend', updateLayer);
220
+ map.on('zoomend', updateLayer);
221
+
222
+ return () => {
223
+ map.off('style.load', addLayers);
224
+ map.off('moveend', updateLayer);
225
+ map.off('zoomend', updateLayer);
226
+ if (map.getLayer(layerId)) map.removeLayer(layerId);
227
+ if (map.getSource(sourceId)) map.removeSource(sourceId);
228
+ };
229
+ });
230
+ </script>
@@ -0,0 +1,11 @@
1
+ import type { CellDataStore } from '../stores/cell.data.svelte';
2
+ import type { CellDisplayStore } from '../stores/cell.display.svelte';
3
+ import type { CellRegistry } from '../stores/cell.registry.svelte';
4
+ interface Props {
5
+ dataStore: CellDataStore;
6
+ displayStore: CellDisplayStore;
7
+ registry: CellRegistry;
8
+ }
9
+ declare const CellLabelsLayer: import("svelte").Component<Props, {}, "">;
10
+ type CellLabelsLayer = ReturnType<typeof CellLabelsLayer>;
11
+ export default CellLabelsLayer;