@smartnet360/svelte-components 0.0.84 → 0.0.86

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 (122) 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/SiteCheck.svelte +60 -80
  9. package/dist/apps/site-check/data-loader.d.ts +9 -6
  10. package/dist/apps/site-check/data-loader.js +2 -11
  11. package/dist/apps/site-check/helper.d.ts +3 -2
  12. package/dist/apps/site-check/helper.js +7 -5
  13. package/dist/apps/site-check/index.d.ts +1 -1
  14. package/dist/apps/site-check/transforms.d.ts +4 -2
  15. package/dist/apps/site-check/transforms.js +49 -10
  16. package/dist/core/Charts/GlobalControls.svelte +0 -4
  17. package/dist/core/Desktop/Grid/ResizeHandle.svelte +0 -7
  18. package/dist/core/Desktop/Grid/resizeStore.js +0 -1
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.js +2 -0
  21. package/dist/map-v2/demo/DemoMap.svelte +0 -2
  22. package/dist/map-v2/demo/demo-cells.js +0 -1
  23. package/dist/map-v2/features/cells/layers/CellsLayer.svelte +7 -26
  24. package/dist/map-v2/features/cells/utils/cellTree.js +0 -29
  25. package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte +3 -27
  26. package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +8 -25
  27. package/dist/map-v2/features/repeaters/utils/repeaterTree.js +0 -6
  28. package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +0 -8
  29. package/dist/map-v2/features/sites/utils/siteTreeUtils.js +0 -6
  30. package/dist/map-v3/core/components/Map.svelte +89 -0
  31. package/dist/map-v3/core/components/Map.svelte.d.ts +13 -0
  32. package/dist/map-v3/core/controls/FeatureSettingsControl.svelte +103 -0
  33. package/dist/map-v3/core/controls/FeatureSettingsControl.svelte.d.ts +15 -0
  34. package/dist/map-v3/core/controls/MapStyleControl.svelte +271 -0
  35. package/dist/map-v3/core/controls/MapStyleControl.svelte.d.ts +28 -0
  36. package/dist/map-v3/core/index.d.ts +3 -0
  37. package/dist/map-v3/core/index.js +3 -0
  38. package/dist/map-v3/core/stores/map.store.svelte.d.ts +8 -0
  39. package/dist/map-v3/core/stores/map.store.svelte.js +29 -0
  40. package/dist/map-v3/core/stores/viewport.store.svelte.d.ts +38 -0
  41. package/dist/map-v3/core/stores/viewport.store.svelte.js +107 -0
  42. package/dist/map-v3/demo/DemoMap.svelte +104 -0
  43. package/dist/map-v3/demo/DemoMap.svelte.d.ts +6 -0
  44. package/dist/map-v3/demo/demo-cells.d.ts +13 -0
  45. package/dist/map-v3/demo/demo-cells.js +130 -0
  46. package/dist/map-v3/demo/demo-data.d.ts +8 -0
  47. package/dist/map-v3/demo/demo-data.js +104 -0
  48. package/dist/map-v3/demo/demo-repeaters.d.ts +13 -0
  49. package/dist/map-v3/demo/demo-repeaters.js +73 -0
  50. package/dist/map-v3/features/cells/components/CellFilterControl.svelte +208 -0
  51. package/dist/map-v3/features/cells/components/CellFilterControl.svelte.d.ts +12 -0
  52. package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte +229 -0
  53. package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte.d.ts +7 -0
  54. package/dist/map-v3/features/cells/constants.d.ts +18 -0
  55. package/dist/map-v3/features/cells/constants.js +37 -0
  56. package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte +230 -0
  57. package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte.d.ts +11 -0
  58. package/dist/map-v3/features/cells/layers/CellsLayer.svelte +194 -0
  59. package/dist/map-v3/features/cells/layers/CellsLayer.svelte.d.ts +11 -0
  60. package/dist/map-v3/features/cells/layers/index.d.ts +2 -0
  61. package/dist/map-v3/features/cells/layers/index.js +2 -0
  62. package/dist/map-v3/features/cells/logic/geometry.d.ts +12 -0
  63. package/dist/map-v3/features/cells/logic/geometry.js +35 -0
  64. package/dist/map-v3/features/cells/logic/grouping.d.ts +18 -0
  65. package/dist/map-v3/features/cells/logic/grouping.js +30 -0
  66. package/dist/map-v3/features/cells/logic/tree-adapter.d.ts +11 -0
  67. package/dist/map-v3/features/cells/logic/tree-adapter.js +53 -0
  68. package/dist/map-v3/features/cells/stores/cell.data.svelte.d.ts +9 -0
  69. package/dist/map-v3/features/cells/stores/cell.data.svelte.js +16 -0
  70. package/dist/map-v3/features/cells/stores/cell.display.svelte.d.ts +25 -0
  71. package/dist/map-v3/features/cells/stores/cell.display.svelte.js +67 -0
  72. package/dist/map-v3/features/cells/stores/cell.registry.svelte.d.ts +23 -0
  73. package/dist/map-v3/features/cells/stores/cell.registry.svelte.js +68 -0
  74. package/dist/map-v3/features/cells/types.d.ts +62 -0
  75. package/dist/map-v3/features/cells/types.js +6 -0
  76. package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +148 -0
  77. package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte.d.ts +12 -0
  78. package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte +209 -0
  79. package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte.d.ts +7 -0
  80. package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte +177 -0
  81. package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte.d.ts +11 -0
  82. package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte +163 -0
  83. package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte.d.ts +11 -0
  84. package/dist/map-v3/features/repeaters/logic/geometry.d.ts +3 -0
  85. package/dist/map-v3/features/repeaters/logic/geometry.js +23 -0
  86. package/dist/map-v3/features/repeaters/logic/grouping.d.ts +8 -0
  87. package/dist/map-v3/features/repeaters/logic/grouping.js +20 -0
  88. package/dist/map-v3/features/repeaters/logic/tree-adapter.d.ts +8 -0
  89. package/dist/map-v3/features/repeaters/logic/tree-adapter.js +43 -0
  90. package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.d.ts +8 -0
  91. package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.js +13 -0
  92. package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.d.ts +21 -0
  93. package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.js +64 -0
  94. package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.d.ts +23 -0
  95. package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.js +68 -0
  96. package/dist/map-v3/features/repeaters/types.d.ts +18 -0
  97. package/dist/map-v3/features/repeaters/types.js +1 -0
  98. package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +119 -0
  99. package/dist/map-v3/features/sites/components/SiteFilterControl.svelte.d.ts +12 -0
  100. package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte +241 -0
  101. package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte.d.ts +7 -0
  102. package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte +152 -0
  103. package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte.d.ts +11 -0
  104. package/dist/map-v3/features/sites/layers/SitesLayer.svelte +132 -0
  105. package/dist/map-v3/features/sites/layers/SitesLayer.svelte.d.ts +11 -0
  106. package/dist/map-v3/features/sites/logic/tree-adapter.d.ts +9 -0
  107. package/dist/map-v3/features/sites/logic/tree-adapter.js +75 -0
  108. package/dist/map-v3/features/sites/stores/site.data.svelte.d.ts +8 -0
  109. package/dist/map-v3/features/sites/stores/site.data.svelte.js +40 -0
  110. package/dist/map-v3/features/sites/stores/site.display.svelte.d.ts +20 -0
  111. package/dist/map-v3/features/sites/stores/site.display.svelte.js +63 -0
  112. package/dist/map-v3/features/sites/stores/site.registry.svelte.d.ts +13 -0
  113. package/dist/map-v3/features/sites/stores/site.registry.svelte.js +83 -0
  114. package/dist/map-v3/features/sites/types.d.ts +12 -0
  115. package/dist/map-v3/features/sites/types.js +1 -0
  116. package/dist/map-v3/index.d.ts +26 -0
  117. package/dist/map-v3/index.js +31 -0
  118. package/dist/map-v3/shared/controls/MapControl.svelte +242 -0
  119. package/dist/map-v3/shared/controls/MapControl.svelte.d.ts +27 -0
  120. package/dist/map-v3/shared/index.d.ts +1 -0
  121. package/dist/map-v3/shared/index.js +1 -0
  122. package/package.json +1 -1
@@ -0,0 +1,177 @@
1
+ <script lang="ts">
2
+ import { getContext } from 'svelte';
3
+ import type { MapStore } from '../../../core/stores/map.store.svelte';
4
+ import type { RepeaterDataStore } from '../stores/repeater.data.svelte';
5
+ import type { RepeaterDisplayStore } from '../stores/repeater.display.svelte';
6
+ import type { RepeaterRegistry } from '../stores/repeater.registry.svelte';
7
+ import { groupRepeaters, getColorForGroup } from '../logic/grouping';
8
+ import type { Repeater } from '../types';
9
+ import type mapboxgl from 'mapbox-gl';
10
+
11
+ interface Props {
12
+ dataStore: RepeaterDataStore;
13
+ displayStore: RepeaterDisplayStore;
14
+ registry: RepeaterRegistry;
15
+ }
16
+
17
+ let { dataStore, displayStore, registry }: Props = $props();
18
+
19
+ const mapStore = getContext<MapStore>('MAP_CONTEXT');
20
+ let sourceId = 'repeater-labels-source';
21
+ let layerId = 'repeater-labels-layer';
22
+ let updateTimeout: any;
23
+
24
+ // Helper to get label text
25
+ function getLabelText(repeater: Repeater, config: { primary: string, secondary: string }): string {
26
+ const pVal = repeater[config.primary as keyof Repeater];
27
+ const sVal = config.secondary !== 'none' ? repeater[config.secondary as keyof Repeater] : null;
28
+
29
+ let text = pVal != null ? String(pVal) : '';
30
+ if (sVal != null) {
31
+ text += ` | ${sVal}`;
32
+ }
33
+ return text;
34
+ }
35
+
36
+ // React to changes
37
+ $effect(() => {
38
+ // Read dependencies
39
+ const _repeaters = dataStore.filteredRepeaters;
40
+ const _show = displayStore.showLabels;
41
+ const _dist = displayStore.labelPixelDistance;
42
+ const _size = displayStore.labelFontSize;
43
+ const _tol = displayStore.labelAzimuthTolerance;
44
+ // Track specific label properties for reactivity
45
+ const _lPrim = displayStore.labels.primary;
46
+ const _lSec = displayStore.labels.secondary;
47
+ const _color = displayStore.labelColor;
48
+ const _haloColor = displayStore.labelHaloColor;
49
+ const _haloWidth = displayStore.labelHaloWidth;
50
+ const _registryVersion = registry.version;
51
+
52
+ const map = mapStore.map;
53
+ if (map && map.getLayer(layerId)) {
54
+ map.setPaintProperty(layerId, 'text-color', displayStore.labelColor);
55
+ map.setPaintProperty(layerId, 'text-halo-color', displayStore.labelHaloColor);
56
+ map.setPaintProperty(layerId, 'text-halo-width', displayStore.labelHaloWidth);
57
+ }
58
+
59
+ updateLayer();
60
+ });
61
+
62
+ function updateLayer() {
63
+ const map = mapStore.map;
64
+ if (!map) return;
65
+
66
+ clearTimeout(updateTimeout);
67
+ updateTimeout = setTimeout(() => {
68
+ renderLabels(map);
69
+ }, 150);
70
+ }
71
+
72
+ function renderLabels(map: mapboxgl.Map) {
73
+ if (!displayStore.showLabels) {
74
+ if (map.getLayer(layerId)) map.setLayoutProperty(layerId, 'visibility', 'none');
75
+ return;
76
+ }
77
+
78
+ if (map.getLayer(layerId)) map.setLayoutProperty(layerId, 'visibility', 'visible');
79
+
80
+ // Update text size if changed
81
+ if (map.getLayer(layerId)) {
82
+ map.setLayoutProperty(layerId, 'text-size', displayStore.labelFontSize);
83
+ }
84
+
85
+ const bounds = map.getBounds();
86
+ if (!bounds) return;
87
+
88
+ // 1. Filter visible repeaters (Bounds + Registry Visibility)
89
+ const visibleRepeaters: Repeater[] = [];
90
+
91
+ // Group repeaters using the same logic as RepeatersLayer to determine visibility
92
+ const repeaterGroups = groupRepeaters(dataStore.filteredRepeaters, displayStore.level1, displayStore.level2);
93
+ let groupIndex = 0;
94
+
95
+ for (const [groupId, repeaters] of repeaterGroups) {
96
+ const defaultColor = getColorForGroup(groupIndex++);
97
+ const style = registry.getStyle(groupId, defaultColor);
98
+
99
+ if (style.visible) {
100
+ // Check bounds
101
+ for (const r of repeaters) {
102
+ if (bounds.contains([r.longitude, r.latitude])) {
103
+ visibleRepeaters.push(r);
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ // 2. Generate Label Features
110
+ const features: GeoJSON.Feature[] = [];
111
+
112
+ // Convert pixel distance to ems (approx 1em = 16px or labelFontSize)
113
+ const distEm = displayStore.labelPixelDistance / displayStore.labelFontSize;
114
+
115
+ for (const r of visibleRepeaters) {
116
+ const labelText = getLabelText(r, displayStore.labels);
117
+ if (!labelText) continue;
118
+
119
+ // Calculate offset based on azimuth
120
+ // Mapbox text-offset: [x, y]
121
+ // x = sin(az) * dist
122
+ // y = -cos(az) * dist (negative because y is down)
123
+ const az = r.azimuth ?? 0;
124
+ const azRad = (az * Math.PI) / 180;
125
+ const offsetX = Math.sin(azRad) * distEm;
126
+ const offsetY = -Math.cos(azRad) * distEm;
127
+
128
+ features.push({
129
+ type: 'Feature',
130
+ geometry: {
131
+ type: 'Point',
132
+ coordinates: [r.longitude, r.latitude]
133
+ },
134
+ properties: {
135
+ label: labelText,
136
+ offset: [offsetX, offsetY]
137
+ }
138
+ });
139
+ }
140
+
141
+ const geojson: GeoJSON.FeatureCollection = {
142
+ type: 'FeatureCollection',
143
+ features
144
+ };
145
+
146
+ const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource;
147
+ if (source) {
148
+ source.setData(geojson);
149
+ } else {
150
+ map.addSource(sourceId, {
151
+ type: 'geojson',
152
+ data: geojson
153
+ });
154
+
155
+ map.addLayer({
156
+ id: layerId,
157
+ type: 'symbol',
158
+ source: sourceId,
159
+ layout: {
160
+ 'text-field': ['get', 'label'],
161
+ 'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
162
+ 'text-size': displayStore.labelFontSize,
163
+ 'text-offset': ['get', 'offset'],
164
+ 'text-anchor': 'center',
165
+ 'text-justify': 'auto',
166
+ 'text-allow-overlap': false,
167
+ 'text-ignore-placement': false
168
+ },
169
+ paint: {
170
+ 'text-color': displayStore.labelColor,
171
+ 'text-halo-color': displayStore.labelHaloColor,
172
+ 'text-halo-width': displayStore.labelHaloWidth
173
+ }
174
+ });
175
+ }
176
+ }
177
+ </script>
@@ -0,0 +1,11 @@
1
+ import type { RepeaterDataStore } from '../stores/repeater.data.svelte';
2
+ import type { RepeaterDisplayStore } from '../stores/repeater.display.svelte';
3
+ import type { RepeaterRegistry } from '../stores/repeater.registry.svelte';
4
+ interface Props {
5
+ dataStore: RepeaterDataStore;
6
+ displayStore: RepeaterDisplayStore;
7
+ registry: RepeaterRegistry;
8
+ }
9
+ declare const RepeaterLabelsLayer: import("svelte").Component<Props, {}, "">;
10
+ type RepeaterLabelsLayer = ReturnType<typeof RepeaterLabelsLayer>;
11
+ export default RepeaterLabelsLayer;
@@ -0,0 +1,163 @@
1
+ <script lang="ts">
2
+ import { getContext } from 'svelte';
3
+ import type { MapStore } from '../../../core/stores/map.store.svelte';
4
+ import type { RepeaterDataStore } from '../stores/repeater.data.svelte';
5
+ import type { RepeaterRegistry } from '../stores/repeater.registry.svelte';
6
+ import type { RepeaterDisplayStore } from '../stores/repeater.display.svelte';
7
+ import { groupRepeaters, getColorForGroup } from '../logic/grouping';
8
+ import { generateRepeaterArc, calculateRadiusInMeters } from '../logic/geometry';
9
+ import { Z_INDEX_BY_BAND } from '../../cells/constants';
10
+ import type { TechnologyBandKey } from '../../cells/types';
11
+ import type mapboxgl from 'mapbox-gl';
12
+
13
+ interface Props {
14
+ dataStore: RepeaterDataStore;
15
+ registry: RepeaterRegistry;
16
+ displayStore: RepeaterDisplayStore;
17
+ }
18
+
19
+ let { dataStore, registry, displayStore }: Props = $props();
20
+
21
+ const mapStore = getContext<MapStore>('MAP_CONTEXT');
22
+ let sourceId = 'repeaters-source';
23
+ let layerId = 'repeaters-layer';
24
+ let lineLayerId = 'repeaters-line-layer';
25
+
26
+ let updateTimeout: any;
27
+
28
+ $effect(() => {
29
+ const map = mapStore.map;
30
+ if (!map) return;
31
+
32
+ if (map.getLayer(layerId)) {
33
+ map.setPaintProperty(layerId, 'fill-opacity', displayStore.fillOpacity);
34
+ }
35
+ if (map.getLayer(lineLayerId)) {
36
+ map.setPaintProperty(lineLayerId, 'line-width', displayStore.lineWidth);
37
+ }
38
+ });
39
+
40
+ $effect(() => {
41
+ const map = mapStore.map;
42
+ if (!map) return;
43
+
44
+ const addLayers = () => {
45
+ if (!map.getSource(sourceId)) {
46
+ map.addSource(sourceId, {
47
+ type: 'geojson',
48
+ data: { type: 'FeatureCollection', features: [] }
49
+ });
50
+ }
51
+
52
+ if (!map.getLayer(layerId)) {
53
+ map.addLayer({
54
+ id: layerId,
55
+ type: 'fill',
56
+ source: sourceId,
57
+ paint: {
58
+ 'fill-color': ['get', 'color'],
59
+ 'fill-opacity': displayStore.fillOpacity
60
+ },
61
+ layout: {
62
+ 'fill-sort-key': ['get', 'zIndex']
63
+ }
64
+ });
65
+ }
66
+
67
+ if (!map.getLayer(lineLayerId)) {
68
+ map.addLayer({
69
+ id: lineLayerId,
70
+ type: 'line',
71
+ source: sourceId,
72
+ paint: {
73
+ 'line-color': '#000',
74
+ 'line-width': displayStore.lineWidth,
75
+ 'line-opacity': 0.5,
76
+ 'line-dasharray': [2, 1]
77
+ }
78
+ });
79
+ }
80
+
81
+ updateLayer();
82
+ };
83
+
84
+ addLayers();
85
+ map.on('style.load', addLayers);
86
+ map.on('moveend', updateLayer);
87
+ map.on('zoomend', updateLayer);
88
+
89
+ return () => {
90
+ map.off('style.load', addLayers);
91
+ map.off('moveend', updateLayer);
92
+ map.off('zoomend', updateLayer);
93
+
94
+ if (map.getLayer(lineLayerId)) map.removeLayer(lineLayerId);
95
+ if (map.getLayer(layerId)) map.removeLayer(layerId);
96
+ if (map.getSource(sourceId)) map.removeSource(sourceId);
97
+ };
98
+ });
99
+
100
+ $effect(() => {
101
+ const _repeaters = dataStore.filteredRepeaters;
102
+ const _pixelSize = displayStore.targetPixelSize;
103
+ const _registryVersion = registry.version;
104
+ const _l1 = displayStore.level1;
105
+ const _l2 = displayStore.level2;
106
+
107
+ updateLayer();
108
+ });
109
+
110
+ function updateLayer() {
111
+ const map = mapStore.map;
112
+ if (!map) return;
113
+
114
+ clearTimeout(updateTimeout);
115
+ updateTimeout = setTimeout(() => {
116
+ renderRepeaters(map);
117
+ }, 100);
118
+ }
119
+
120
+ function renderRepeaters(map: mapboxgl.Map) {
121
+ const bounds = map.getBounds();
122
+ if (!bounds) return;
123
+
124
+ const zoom = map.getZoom();
125
+ const centerLat = map.getCenter().lat;
126
+
127
+ const radiusMeters = calculateRadiusInMeters(centerLat, zoom, displayStore.targetPixelSize);
128
+
129
+ const groups = groupRepeaters(dataStore.filteredRepeaters, displayStore.level1, displayStore.level2);
130
+
131
+ const features: GeoJSON.Feature[] = [];
132
+ let groupIndex = 0;
133
+
134
+ for (const [groupId, repeaters] of groups) {
135
+ const defaultColor = getColorForGroup(groupIndex++);
136
+ const style = registry.getStyle(groupId, defaultColor);
137
+
138
+ if (!style.visible) continue;
139
+
140
+ for (const repeater of repeaters) {
141
+ if (bounds.contains([repeater.longitude, repeater.latitude])) {
142
+ const zIndexKey = `${repeater.tech}_${repeater.fband}` as TechnologyBandKey;
143
+ const zIndex = Z_INDEX_BY_BAND[zIndexKey] || 10;
144
+
145
+ const MAX_Z = 15;
146
+ const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08;
147
+ const effectiveRadius = radiusMeters * scaleFactor;
148
+
149
+ const feature = generateRepeaterArc(repeater, effectiveRadius, zIndex, style.color);
150
+ features.push(feature);
151
+ }
152
+ }
153
+ }
154
+
155
+ const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource;
156
+ if (source) {
157
+ source.setData({
158
+ type: 'FeatureCollection',
159
+ features: features as any
160
+ });
161
+ }
162
+ }
163
+ </script>
@@ -0,0 +1,11 @@
1
+ import type { RepeaterDataStore } from '../stores/repeater.data.svelte';
2
+ import type { RepeaterRegistry } from '../stores/repeater.registry.svelte';
3
+ import type { RepeaterDisplayStore } from '../stores/repeater.display.svelte';
4
+ interface Props {
5
+ dataStore: RepeaterDataStore;
6
+ registry: RepeaterRegistry;
7
+ displayStore: RepeaterDisplayStore;
8
+ }
9
+ declare const RepeatersLayer: import("svelte").Component<Props, {}, "">;
10
+ type RepeatersLayer = ReturnType<typeof RepeatersLayer>;
11
+ export default RepeatersLayer;
@@ -0,0 +1,3 @@
1
+ import type { Repeater } from '../types';
2
+ export declare function calculateRadiusInMeters(latitude: number, zoom: number, targetPixelSize: number): number;
3
+ export declare function generateRepeaterArc(repeater: Repeater, radiusMeters: number, zIndex: number, color: string): GeoJSON.Feature<GeoJSON.Polygon>;
@@ -0,0 +1,23 @@
1
+ import * as turf from '@turf/turf';
2
+ export function calculateRadiusInMeters(latitude, zoom, targetPixelSize) {
3
+ const metersPerPixel = (156543.03392 * Math.cos((latitude * Math.PI) / 180)) / Math.pow(2, zoom);
4
+ return targetPixelSize * metersPerPixel;
5
+ }
6
+ export function generateRepeaterArc(repeater, radiusMeters, zIndex, color) {
7
+ const center = [repeater.longitude, repeater.latitude];
8
+ const bearing1 = repeater.azimuth - (repeater.beamwidth / 2);
9
+ const bearing2 = repeater.azimuth + (repeater.beamwidth / 2);
10
+ const sector = turf.sector(center, radiusMeters / 1000, bearing1, bearing2, {
11
+ steps: 10
12
+ });
13
+ sector.properties = {
14
+ id: repeater.repeaterId,
15
+ name: repeater.donorCellName,
16
+ tech: repeater.tech,
17
+ fband: repeater.fband,
18
+ zIndex: zIndex,
19
+ color: color,
20
+ type: 'repeater'
21
+ };
22
+ return sector;
23
+ }
@@ -0,0 +1,8 @@
1
+ import type { Repeater, RepeaterGroupingField } from '../types';
2
+ export interface TreeData {
3
+ nodes: Map<string, any>;
4
+ rootPaths: string[];
5
+ }
6
+ export declare function generateLeafId(level1: string, level2: string): string;
7
+ export declare function getColorForGroup(index: number): string;
8
+ export declare function groupRepeaters(repeaters: Repeater[], level1Field: RepeaterGroupingField, level2Field: RepeaterGroupingField): Map<string, Repeater[]>;
@@ -0,0 +1,20 @@
1
+ import { DEFAULT_PALETTE } from '../../cells/constants';
2
+ export function generateLeafId(level1, level2) {
3
+ return `${level1}__${level2}`;
4
+ }
5
+ export function getColorForGroup(index) {
6
+ return DEFAULT_PALETTE[index % DEFAULT_PALETTE.length];
7
+ }
8
+ export function groupRepeaters(repeaters, level1Field, level2Field) {
9
+ const groups = new Map();
10
+ for (const repeater of repeaters) {
11
+ const l1 = repeater[level1Field] || 'Unknown';
12
+ const l2 = repeater[level2Field] || 'Unknown';
13
+ const key = generateLeafId(String(l1), String(l2));
14
+ if (!groups.has(key)) {
15
+ groups.set(key, []);
16
+ }
17
+ groups.get(key)?.push(repeater);
18
+ }
19
+ return groups;
20
+ }
@@ -0,0 +1,8 @@
1
+ import type { TreeNode } from '../../../../core/TreeView/tree.model';
2
+ import type { Repeater, RepeaterGroupingField } from '../types';
3
+ import type { RepeaterRegistry } from '../stores/repeater.registry.svelte';
4
+ export declare function buildRepeaterTree(repeaters: Repeater[], registry: RepeaterRegistry, level1?: RepeaterGroupingField, level2?: RepeaterGroupingField): TreeNode<{
5
+ color: string;
6
+ count: number;
7
+ groupId: string;
8
+ }>[];
@@ -0,0 +1,43 @@
1
+ import { groupRepeaters, generateLeafId, getColorForGroup } from './grouping';
2
+ export function buildRepeaterTree(repeaters, registry, level1 = 'tech', level2 = 'fband') {
3
+ const groups = groupRepeaters(repeaters, level1, level2);
4
+ const level1Nodes = new Map();
5
+ let groupIndex = 0;
6
+ for (const [groupId, groupRepeaters] of groups) {
7
+ if (groupRepeaters.length === 0)
8
+ continue;
9
+ const sample = groupRepeaters[0];
10
+ const l1Value = String(sample[level1] || 'Unknown');
11
+ const l2Value = String(sample[level2] || 'Unknown');
12
+ if (!level1Nodes.has(l1Value)) {
13
+ level1Nodes.set(l1Value, {
14
+ id: l1Value,
15
+ label: l1Value,
16
+ children: [],
17
+ defaultExpanded: true,
18
+ });
19
+ }
20
+ const defaultColor = getColorForGroup(groupIndex++);
21
+ const style = registry.getStyle(groupId, defaultColor);
22
+ const leafNode = {
23
+ id: groupId,
24
+ label: `${l2Value} (${groupRepeaters.length})`,
25
+ metadata: {
26
+ color: style.color,
27
+ count: groupRepeaters.length,
28
+ groupId: groupId
29
+ },
30
+ defaultChecked: style.visible,
31
+ };
32
+ level1Nodes.get(l1Value)?.children?.push(leafNode);
33
+ }
34
+ const sortedNodes = Array.from(level1Nodes.values()).sort((a, b) => a.label.localeCompare(b.label));
35
+ const rootNode = {
36
+ id: 'root-repeaters',
37
+ label: `Repeaters (${repeaters.length})`,
38
+ children: sortedNodes,
39
+ defaultExpanded: true,
40
+ defaultChecked: true,
41
+ };
42
+ return [rootNode];
43
+ }
@@ -0,0 +1,8 @@
1
+ import type { Repeater } from '../types';
2
+ export declare class RepeaterDataStore {
3
+ rawRepeaters: Repeater[];
4
+ constructor();
5
+ setRepeaters(repeaters: Repeater[]): void;
6
+ get filteredRepeaters(): Repeater[];
7
+ }
8
+ export declare function createRepeaterDataStore(): RepeaterDataStore;
@@ -0,0 +1,13 @@
1
+ export class RepeaterDataStore {
2
+ rawRepeaters = $state([]);
3
+ constructor() { }
4
+ setRepeaters(repeaters) {
5
+ this.rawRepeaters = repeaters;
6
+ }
7
+ get filteredRepeaters() {
8
+ return this.rawRepeaters;
9
+ }
10
+ }
11
+ export function createRepeaterDataStore() {
12
+ return new RepeaterDataStore();
13
+ }
@@ -0,0 +1,21 @@
1
+ import type { RepeaterGroupingField } from '../types';
2
+ export declare class RepeaterDisplayStore {
3
+ key: string;
4
+ targetPixelSize: number;
5
+ fillOpacity: number;
6
+ lineWidth: number;
7
+ level1: RepeaterGroupingField;
8
+ level2: RepeaterGroupingField;
9
+ showLabels: boolean;
10
+ labelPixelDistance: number;
11
+ labelFontSize: number;
12
+ labelAzimuthTolerance: number;
13
+ labelColor: string;
14
+ labelHaloColor: string;
15
+ labelHaloWidth: number;
16
+ labels: {
17
+ primary: string;
18
+ secondary: string;
19
+ };
20
+ constructor();
21
+ }
@@ -0,0 +1,64 @@
1
+ import { browser } from '$app/environment';
2
+ export class RepeaterDisplayStore {
3
+ key = 'map-v3-repeater-display';
4
+ // State
5
+ targetPixelSize = $state(40); // Slightly smaller default than cells
6
+ fillOpacity = $state(0.6);
7
+ lineWidth = $state(1);
8
+ // Grouping
9
+ level1 = $state('tech');
10
+ level2 = $state('fband');
11
+ // Label Settings
12
+ showLabels = $state(false);
13
+ labelPixelDistance = $state(60);
14
+ labelFontSize = $state(12);
15
+ labelAzimuthTolerance = $state(10);
16
+ labelColor = $state('#333333');
17
+ labelHaloColor = $state('#ffffff');
18
+ labelHaloWidth = $state(1);
19
+ // Unified label config
20
+ labels = $state({ primary: 'repeaterId', secondary: 'none' });
21
+ constructor() {
22
+ if (browser) {
23
+ const saved = localStorage.getItem(this.key);
24
+ if (saved) {
25
+ try {
26
+ const parsed = JSON.parse(saved);
27
+ this.targetPixelSize = parsed.targetPixelSize ?? 40;
28
+ this.fillOpacity = parsed.fillOpacity ?? 0.6;
29
+ this.lineWidth = parsed.lineWidth ?? 1;
30
+ this.level1 = parsed.level1 ?? 'tech';
31
+ this.level2 = parsed.level2 ?? 'fband';
32
+ this.showLabels = parsed.showLabels ?? false;
33
+ this.labelPixelDistance = parsed.labelPixelDistance ?? 60;
34
+ this.labelFontSize = parsed.labelFontSize ?? 12;
35
+ this.labelAzimuthTolerance = parsed.labelAzimuthTolerance ?? 10;
36
+ this.labelColor = parsed.labelColor ?? '#333333';
37
+ this.labelHaloColor = parsed.labelHaloColor ?? '#ffffff';
38
+ this.labelHaloWidth = parsed.labelHaloWidth ?? 1;
39
+ this.labels = parsed.labels ?? { primary: 'repeaterId', secondary: 'none' };
40
+ }
41
+ catch (e) {
42
+ console.error('Failed to load repeater display settings', e);
43
+ }
44
+ }
45
+ $effect(() => {
46
+ localStorage.setItem(this.key, JSON.stringify({
47
+ targetPixelSize: this.targetPixelSize,
48
+ fillOpacity: this.fillOpacity,
49
+ lineWidth: this.lineWidth,
50
+ level1: this.level1,
51
+ level2: this.level2,
52
+ showLabels: this.showLabels,
53
+ labelPixelDistance: this.labelPixelDistance,
54
+ labelFontSize: this.labelFontSize,
55
+ labelAzimuthTolerance: this.labelAzimuthTolerance,
56
+ labelColor: this.labelColor,
57
+ labelHaloColor: this.labelHaloColor,
58
+ labelHaloWidth: this.labelHaloWidth,
59
+ labels: this.labels
60
+ }));
61
+ });
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Persistent store for repeater styling (colors, visibility)
3
+ * Key: Stable Group ID (e.g. "4G__LTE700")
4
+ * Value: { color: string, visible: boolean }
5
+ */
6
+ export declare class RepeaterRegistry {
7
+ state: Record<string, {
8
+ color: string;
9
+ visible: boolean;
10
+ }>;
11
+ version: number;
12
+ namespace: string;
13
+ constructor(namespace?: string);
14
+ load(): void;
15
+ save(): void;
16
+ getStyle(id: string, defaultColor: string): {
17
+ color: string;
18
+ visible: boolean;
19
+ };
20
+ toggleVisibility(id: string): void;
21
+ setColor(id: string, color: string): void;
22
+ }
23
+ export declare function createRepeaterRegistry(namespace: string): RepeaterRegistry;
@@ -0,0 +1,68 @@
1
+ import { writable } from 'svelte/store';
2
+ /**
3
+ * Persistent store for repeater styling (colors, visibility)
4
+ * Key: Stable Group ID (e.g. "4G__LTE700")
5
+ * Value: { color: string, visible: boolean }
6
+ */
7
+ export class RepeaterRegistry {
8
+ state = $state({});
9
+ version = $state(0); // Signal for reactivity
10
+ namespace;
11
+ constructor(namespace = 'default') {
12
+ this.namespace = namespace;
13
+ this.load();
14
+ $effect(() => {
15
+ this.save();
16
+ });
17
+ }
18
+ load() {
19
+ if (typeof window === 'undefined')
20
+ return;
21
+ try {
22
+ const stored = localStorage.getItem(`${this.namespace}:repeater-registry`);
23
+ if (stored) {
24
+ this.state = JSON.parse(stored);
25
+ this.version++;
26
+ }
27
+ }
28
+ catch (e) {
29
+ console.warn('Failed to load repeater registry', e);
30
+ }
31
+ }
32
+ save() {
33
+ if (typeof window === 'undefined')
34
+ return;
35
+ try {
36
+ localStorage.setItem(`${this.namespace}:repeater-registry`, JSON.stringify(this.state));
37
+ }
38
+ catch (e) {
39
+ console.warn('Failed to save repeater registry', e);
40
+ }
41
+ }
42
+ getStyle(id, defaultColor) {
43
+ if (!this.state[id]) {
44
+ // Initialize if missing
45
+ this.state[id] = { color: defaultColor, visible: true };
46
+ // No version bump here to avoid loops during render
47
+ }
48
+ return this.state[id];
49
+ }
50
+ toggleVisibility(id) {
51
+ if (this.state[id]) {
52
+ this.state[id].visible = !this.state[id].visible;
53
+ this.version++;
54
+ }
55
+ else {
56
+ console.warn(`[RepeaterRegistry] Tried to toggle missing ID: ${id}`);
57
+ }
58
+ }
59
+ setColor(id, color) {
60
+ if (this.state[id]) {
61
+ this.state[id].color = color;
62
+ this.version++;
63
+ }
64
+ }
65
+ }
66
+ export function createRepeaterRegistry(namespace) {
67
+ return new RepeaterRegistry(namespace);
68
+ }