@smartnet360/svelte-components 0.0.92 → 0.0.93
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-v3/demo/DemoMap.svelte +1 -1
- package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte +38 -0
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte +33 -17
- package/dist/map-v3/features/cells/logic/site-distance.d.ts +4 -2
- package/dist/map-v3/features/cells/logic/site-distance.js +13 -5
- package/dist/map-v3/features/cells/stores/cell.data.svelte.d.ts +8 -2
- package/dist/map-v3/features/cells/stores/cell.data.svelte.js +13 -4
- package/dist/map-v3/features/cells/stores/cell.display.svelte.d.ts +2 -0
- package/dist/map-v3/features/cells/stores/cell.display.svelte.js +6 -0
- package/dist/map-v3/features/cells/stores/site.distance.svelte.d.ts +3 -1
- package/dist/map-v3/features/cells/stores/site.distance.svelte.js +8 -12
- package/package.json +1 -1
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
let { accessToken }: Props = $props();
|
|
31
31
|
|
|
32
32
|
// Initialize stores
|
|
33
|
-
const cellData = createCellDataStore();
|
|
34
33
|
const cellRegistry = createCellRegistry('demo-map');
|
|
35
34
|
const cellDisplay = new CellDisplayStore();
|
|
35
|
+
const cellData = createCellDataStore(cellDisplay);
|
|
36
36
|
|
|
37
37
|
const siteData = createSiteDataStore(cellData);
|
|
38
38
|
const siteRegistry = createSiteRegistry('demo-map');
|
|
@@ -115,6 +115,44 @@
|
|
|
115
115
|
</select>
|
|
116
116
|
</div>
|
|
117
117
|
</div>
|
|
118
|
+
|
|
119
|
+
<!-- Neighbor Count -->
|
|
120
|
+
<div class="row align-items-center g-2 mb-3 ps-3">
|
|
121
|
+
<div class="col-4 text-secondary small">Neighbors</div>
|
|
122
|
+
<div class="col-3 text-end">
|
|
123
|
+
<span class="badge bg-white text-muted border">{displayStore.autoSizeNeighborCount}</span>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="col-5">
|
|
126
|
+
<input
|
|
127
|
+
type="range"
|
|
128
|
+
class="form-range w-100"
|
|
129
|
+
min="1"
|
|
130
|
+
max="10"
|
|
131
|
+
step="1"
|
|
132
|
+
bind:value={displayStore.autoSizeNeighborCount}
|
|
133
|
+
title="Number of nearest neighbors to average for density calculation"
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<!-- Size Multiplier -->
|
|
139
|
+
<div class="row align-items-center g-2 mb-3 ps-3">
|
|
140
|
+
<div class="col-4 text-secondary small">Size Scale</div>
|
|
141
|
+
<div class="col-3 text-end">
|
|
142
|
+
<span class="badge bg-white text-muted border">{displayStore.autoSizeMultiplier.toFixed(1)}x</span>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="col-5">
|
|
145
|
+
<input
|
|
146
|
+
type="range"
|
|
147
|
+
class="form-range w-100"
|
|
148
|
+
min="0.3"
|
|
149
|
+
max="2.0"
|
|
150
|
+
step="0.1"
|
|
151
|
+
bind:value={displayStore.autoSizeMultiplier}
|
|
152
|
+
title="Scale all auto-sized cells up or down"
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
118
156
|
{/if}
|
|
119
157
|
|
|
120
158
|
<div class="border-top my-3"></div>
|
|
@@ -102,8 +102,13 @@
|
|
|
102
102
|
|
|
103
103
|
// Events for updating
|
|
104
104
|
map.on('style.load', addLayers);
|
|
105
|
-
|
|
106
|
-
map
|
|
105
|
+
|
|
106
|
+
// Only listen to map events when NOT using auto-size
|
|
107
|
+
// Auto-size uses fixed meter-based sizes that don't change with zoom
|
|
108
|
+
if (!displayStore.useAutoSize) {
|
|
109
|
+
map.on('moveend', updateLayer);
|
|
110
|
+
map.on('zoomend', updateLayer);
|
|
111
|
+
}
|
|
107
112
|
|
|
108
113
|
// Cleanup
|
|
109
114
|
return () => {
|
|
@@ -129,6 +134,8 @@
|
|
|
129
134
|
const _layerGrouping = displayStore.layerGrouping;
|
|
130
135
|
const _useAutoSize = displayStore.useAutoSize;
|
|
131
136
|
const _autoSizeMode = displayStore.autoSizeMode;
|
|
137
|
+
const _autoSizeNeighborCount = displayStore.autoSizeNeighborCount;
|
|
138
|
+
const _autoSizeMultiplier = displayStore.autoSizeMultiplier;
|
|
132
139
|
|
|
133
140
|
updateLayer();
|
|
134
141
|
});
|
|
@@ -144,20 +151,22 @@
|
|
|
144
151
|
}
|
|
145
152
|
|
|
146
153
|
function renderCells(map: mapboxgl.Map) {
|
|
147
|
-
const
|
|
148
|
-
if (!bounds) return;
|
|
149
|
-
|
|
150
|
-
const zoom = map.getZoom();
|
|
151
|
-
const centerLat = map.getCenter().lat;
|
|
154
|
+
const useAutoSize = displayStore.useAutoSize;
|
|
152
155
|
|
|
153
|
-
|
|
156
|
+
// Only need bounds checking and zoom calculations for manual mode
|
|
157
|
+
const bounds = useAutoSize ? null : map.getBounds();
|
|
158
|
+
const zoom = useAutoSize ? 0 : map.getZoom();
|
|
159
|
+
const centerLat = useAutoSize ? 0 : map.getCenter().lat;
|
|
160
|
+
|
|
161
|
+
console.log(`[CellsLayer] Rendering ${dataStore.filteredCells.length} cells (auto-size: ${useAutoSize})`);
|
|
154
162
|
|
|
155
|
-
// 1. Calculate base radius
|
|
156
|
-
const baseRadiusMeters = calculateRadiusInMeters(centerLat, zoom, displayStore.targetPixelSize);
|
|
157
|
-
|
|
163
|
+
// 1. Calculate base radius (only for manual mode)
|
|
164
|
+
const baseRadiusMeters = useAutoSize ? 0 : calculateRadiusInMeters(centerLat, zoom, displayStore.targetPixelSize);
|
|
165
|
+
if (!useAutoSize) {
|
|
166
|
+
console.log(`[CellsLayer] Base radius: ${baseRadiusMeters.toFixed(2)}m for target ${displayStore.targetPixelSize}px at zoom ${zoom.toFixed(2)}`);
|
|
167
|
+
}
|
|
158
168
|
|
|
159
|
-
// 2. Group cells
|
|
160
|
-
// In real app, this comes from a store
|
|
169
|
+
// 2. Group cells
|
|
161
170
|
const groups = groupCells(dataStore.filteredCells, displayStore.level1, displayStore.level2);
|
|
162
171
|
console.log(`[CellsLayer] Groups: ${groups.size}`);
|
|
163
172
|
|
|
@@ -173,13 +182,17 @@
|
|
|
173
182
|
if (!style.visible) continue;
|
|
174
183
|
|
|
175
184
|
for (const cell of cells) {
|
|
176
|
-
// 4. BBox Filter (
|
|
177
|
-
|
|
185
|
+
// 4. BBox Filter (skip for auto-size - Mapbox handles culling efficiently)
|
|
186
|
+
const inView = useAutoSize ? true : (bounds?.contains([cell.longitude, cell.latitude]) ?? false);
|
|
187
|
+
|
|
188
|
+
if (inView) {
|
|
178
189
|
// 5. Z-Index Lookup
|
|
179
190
|
const zIndexKey = `${cell.tech}_${cell.frq}` as TechnologyBandKey;
|
|
180
191
|
const zIndex = displayStore.currentZIndex[zIndexKey] ?? 10;
|
|
181
192
|
|
|
182
|
-
// 6. Calculate radius
|
|
193
|
+
// 6. Calculate radius
|
|
194
|
+
// Auto-size: Fixed meter-based size from site density
|
|
195
|
+
// Manual: Pixel-based size that scales with zoom
|
|
183
196
|
const MAX_Z = 35;
|
|
184
197
|
let radiusMeters: number;
|
|
185
198
|
|
|
@@ -188,10 +201,13 @@
|
|
|
188
201
|
const siteDistance = dataStore.siteDistanceStore.getDistance(cell.siteId, 500);
|
|
189
202
|
const autoRadius = calculateAutoRadius(siteDistance, displayStore.autoSizeMode);
|
|
190
203
|
|
|
204
|
+
// Apply user's multiplier to scale the result
|
|
205
|
+
const adjustedAutoRadius = autoRadius * displayStore.autoSizeMultiplier;
|
|
206
|
+
|
|
191
207
|
// Scale based on z-index for stacking visibility
|
|
192
208
|
// Lower z-index (background) = larger, higher z-index (foreground) = smaller
|
|
193
209
|
const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08; // 8% per layer
|
|
194
|
-
radiusMeters =
|
|
210
|
+
radiusMeters = adjustedAutoRadius * scaleFactor;
|
|
195
211
|
} else {
|
|
196
212
|
// Manual mode: base from pixel size, then scale by z-index
|
|
197
213
|
const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08;
|
|
@@ -26,9 +26,11 @@ export declare function groupBySite(cells: Cell[]): Map<string, Cell[]>;
|
|
|
26
26
|
export declare function extractSiteLocations(cells: Cell[]): Map<string, [number, number]>;
|
|
27
27
|
/**
|
|
28
28
|
* Compute nearest neighbor distance for a single site
|
|
29
|
+
* Can average multiple neighbors for better density estimation
|
|
29
30
|
* @param siteId Site to compute distance for
|
|
30
31
|
* @param siteLocation Location of the site
|
|
31
32
|
* @param allSiteLocations Map of all site locations
|
|
32
|
-
* @
|
|
33
|
+
* @param neighborCount Number of closest neighbors to average (default: 5)
|
|
34
|
+
* @returns Average distance to N nearest neighbors in meters, or Infinity if no neighbors
|
|
33
35
|
*/
|
|
34
|
-
export declare function computeNearestNeighbor(siteId: string, siteLocation: [number, number], allSiteLocations: Map<string, [number, number]
|
|
36
|
+
export declare function computeNearestNeighbor(siteId: string, siteLocation: [number, number], allSiteLocations: Map<string, [number, number]>, neighborCount?: number): number;
|
|
@@ -54,18 +54,26 @@ export function extractSiteLocations(cells) {
|
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
56
|
* Compute nearest neighbor distance for a single site
|
|
57
|
+
* Can average multiple neighbors for better density estimation
|
|
57
58
|
* @param siteId Site to compute distance for
|
|
58
59
|
* @param siteLocation Location of the site
|
|
59
60
|
* @param allSiteLocations Map of all site locations
|
|
60
|
-
* @
|
|
61
|
+
* @param neighborCount Number of closest neighbors to average (default: 5)
|
|
62
|
+
* @returns Average distance to N nearest neighbors in meters, or Infinity if no neighbors
|
|
61
63
|
*/
|
|
62
|
-
export function computeNearestNeighbor(siteId, siteLocation, allSiteLocations) {
|
|
63
|
-
|
|
64
|
+
export function computeNearestNeighbor(siteId, siteLocation, allSiteLocations, neighborCount = 5) {
|
|
65
|
+
const distances = [];
|
|
64
66
|
for (const [otherId, otherLoc] of allSiteLocations) {
|
|
65
67
|
if (siteId === otherId)
|
|
66
68
|
continue;
|
|
67
69
|
const dist = haversineDistance(siteLocation, otherLoc);
|
|
68
|
-
|
|
70
|
+
distances.push(dist);
|
|
69
71
|
}
|
|
70
|
-
|
|
72
|
+
if (distances.length === 0)
|
|
73
|
+
return Infinity;
|
|
74
|
+
// Sort distances and take N closest
|
|
75
|
+
distances.sort((a, b) => a - b);
|
|
76
|
+
const closestN = distances.slice(0, Math.min(neighborCount, distances.length));
|
|
77
|
+
// Return average of closest neighbors
|
|
78
|
+
return closestN.reduce((sum, d) => sum + d, 0) / closestN.length;
|
|
71
79
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { Cell } from '../types';
|
|
2
2
|
import { SiteDistanceStore } from './site.distance.svelte';
|
|
3
|
+
import type { CellDisplayStore } from './cell.display.svelte';
|
|
3
4
|
export declare class CellDataStore {
|
|
4
5
|
rawCells: Cell[];
|
|
5
6
|
filterOnAir: boolean;
|
|
6
7
|
siteDistanceStore: SiteDistanceStore;
|
|
7
|
-
|
|
8
|
+
displayStore?: CellDisplayStore;
|
|
9
|
+
constructor(displayStore?: CellDisplayStore);
|
|
8
10
|
setCells(cells: Cell[]): void;
|
|
9
11
|
get filteredCells(): Cell[];
|
|
10
12
|
}
|
|
11
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Factory function to create a new CellDataStore
|
|
15
|
+
* @param displayStore Optional display store for auto-size settings
|
|
16
|
+
*/
|
|
17
|
+
export declare function createCellDataStore(displayStore?: CellDisplayStore): CellDataStore;
|
|
@@ -4,13 +4,18 @@ export class CellDataStore {
|
|
|
4
4
|
filterOnAir = $state(false);
|
|
5
5
|
// Internal site distance store for auto-sizing
|
|
6
6
|
siteDistanceStore;
|
|
7
|
-
|
|
7
|
+
// Reference to display store for auto-size settings
|
|
8
|
+
displayStore;
|
|
9
|
+
constructor(displayStore) {
|
|
8
10
|
this.siteDistanceStore = new SiteDistanceStore();
|
|
11
|
+
this.displayStore = displayStore;
|
|
9
12
|
}
|
|
10
13
|
setCells(cells) {
|
|
11
14
|
this.rawCells = cells;
|
|
12
15
|
// Automatically update site distances when cells are loaded
|
|
13
|
-
|
|
16
|
+
// Use neighborCount from displayStore if available
|
|
17
|
+
const neighborCount = this.displayStore?.autoSizeNeighborCount ?? 5;
|
|
18
|
+
this.siteDistanceStore.updateDistances(cells, neighborCount);
|
|
14
19
|
}
|
|
15
20
|
get filteredCells() {
|
|
16
21
|
if (!this.filterOnAir)
|
|
@@ -18,6 +23,10 @@ export class CellDataStore {
|
|
|
18
23
|
return this.rawCells.filter(c => c.status === 'On_Air');
|
|
19
24
|
}
|
|
20
25
|
}
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Factory function to create a new CellDataStore
|
|
28
|
+
* @param displayStore Optional display store for auto-size settings
|
|
29
|
+
*/
|
|
30
|
+
export function createCellDataStore(displayStore) {
|
|
31
|
+
return new CellDataStore(displayStore);
|
|
23
32
|
}
|
|
@@ -9,6 +9,8 @@ export declare class CellDisplayStore {
|
|
|
9
9
|
layerGrouping: LayerGroupingPreset;
|
|
10
10
|
useAutoSize: boolean;
|
|
11
11
|
autoSizeMode: AutoSizeMode;
|
|
12
|
+
autoSizeNeighborCount: number;
|
|
13
|
+
autoSizeMultiplier: number;
|
|
12
14
|
level1: CellGroupingField;
|
|
13
15
|
level2: CellGroupingField;
|
|
14
16
|
currentZIndex: Record<string, number>;
|
|
@@ -11,6 +11,8 @@ export class CellDisplayStore {
|
|
|
11
11
|
// Auto-size settings
|
|
12
12
|
useAutoSize = $state(false);
|
|
13
13
|
autoSizeMode = $state('logarithmic');
|
|
14
|
+
autoSizeNeighborCount = $state(5); // Number of neighbors to average for density
|
|
15
|
+
autoSizeMultiplier = $state(1.0); // Scale factor for auto-size results (0.3 - 2.0)
|
|
14
16
|
// Grouping
|
|
15
17
|
level1 = $state('tech');
|
|
16
18
|
level2 = $state('fband');
|
|
@@ -40,6 +42,8 @@ export class CellDisplayStore {
|
|
|
40
42
|
this.layerGrouping = parsed.layerGrouping ?? 'frequency';
|
|
41
43
|
this.useAutoSize = parsed.useAutoSize ?? false;
|
|
42
44
|
this.autoSizeMode = parsed.autoSizeMode ?? 'logarithmic';
|
|
45
|
+
this.autoSizeNeighborCount = parsed.autoSizeNeighborCount ?? 5;
|
|
46
|
+
this.autoSizeMultiplier = parsed.autoSizeMultiplier ?? 1.0;
|
|
43
47
|
this.level1 = parsed.level1 ?? 'tech';
|
|
44
48
|
this.level2 = parsed.level2 ?? 'fband';
|
|
45
49
|
this.labelPixelDistance = parsed.labelPixelDistance ?? 60;
|
|
@@ -64,6 +68,8 @@ export class CellDisplayStore {
|
|
|
64
68
|
layerGrouping: this.layerGrouping,
|
|
65
69
|
useAutoSize: this.useAutoSize,
|
|
66
70
|
autoSizeMode: this.autoSizeMode,
|
|
71
|
+
autoSizeNeighborCount: this.autoSizeNeighborCount,
|
|
72
|
+
autoSizeMultiplier: this.autoSizeMultiplier,
|
|
67
73
|
level1: this.level1,
|
|
68
74
|
level2: this.level2,
|
|
69
75
|
labelPixelDistance: this.labelPixelDistance,
|
|
@@ -15,8 +15,10 @@ export declare class SiteDistanceStore {
|
|
|
15
15
|
/**
|
|
16
16
|
* Incrementally update distances for new sites
|
|
17
17
|
* Only computes distances for sites not in cache
|
|
18
|
+
* @param cells Array of cells to compute distances for
|
|
19
|
+
* @param neighborCount Number of neighbors to average for density calculation
|
|
18
20
|
*/
|
|
19
|
-
updateDistances(cells: Cell[]): void;
|
|
21
|
+
updateDistances(cells: Cell[], neighborCount?: number): void;
|
|
20
22
|
/**
|
|
21
23
|
* Update existing sites if needed (when no new sites but data might have changed)
|
|
22
24
|
*/
|
|
@@ -23,8 +23,10 @@ export class SiteDistanceStore {
|
|
|
23
23
|
/**
|
|
24
24
|
* Incrementally update distances for new sites
|
|
25
25
|
* Only computes distances for sites not in cache
|
|
26
|
+
* @param cells Array of cells to compute distances for
|
|
27
|
+
* @param neighborCount Number of neighbors to average for density calculation
|
|
26
28
|
*/
|
|
27
|
-
updateDistances(cells) {
|
|
29
|
+
updateDistances(cells, neighborCount = 5) {
|
|
28
30
|
if (!cells || cells.length === 0)
|
|
29
31
|
return;
|
|
30
32
|
const startTime = performance.now();
|
|
@@ -37,11 +39,11 @@ export class SiteDistanceStore {
|
|
|
37
39
|
this.updateExistingSitesIfNeeded(siteLocations);
|
|
38
40
|
return;
|
|
39
41
|
}
|
|
40
|
-
console.log(`[SiteDistance] Computing distances for ${newSites.length} new sites`);
|
|
42
|
+
console.log(`[SiteDistance] Computing distances for ${newSites.length} new sites (${neighborCount} neighbors)`);
|
|
41
43
|
// Compute distances for NEW sites
|
|
42
44
|
for (const siteId of newSites) {
|
|
43
45
|
const location = siteLocations.get(siteId);
|
|
44
|
-
const nearestDist = computeNearestNeighbor(siteId, location, siteLocations);
|
|
46
|
+
const nearestDist = computeNearestNeighbor(siteId, location, siteLocations, neighborCount);
|
|
45
47
|
this.distances.set(siteId, nearestDist);
|
|
46
48
|
this.computedSites.add(siteId);
|
|
47
49
|
}
|
|
@@ -49,15 +51,9 @@ export class SiteDistanceStore {
|
|
|
49
51
|
for (const [existingId, existingLoc] of siteLocations) {
|
|
50
52
|
if (newSites.includes(existingId))
|
|
51
53
|
continue; // Skip new sites
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const dist = haversineDistance(existingLoc, newLoc);
|
|
56
|
-
currentMin = Math.min(currentMin, dist);
|
|
57
|
-
}
|
|
58
|
-
if (currentMin < (this.distances.get(existingId) || Infinity)) {
|
|
59
|
-
this.distances.set(existingId, currentMin);
|
|
60
|
-
}
|
|
54
|
+
// Recalculate with new sites present
|
|
55
|
+
const updatedDist = computeNearestNeighbor(existingId, existingLoc, siteLocations, neighborCount);
|
|
56
|
+
this.distances.set(existingId, updatedDist);
|
|
61
57
|
}
|
|
62
58
|
const endTime = performance.now();
|
|
63
59
|
this.lastComputeTime = endTime - startTime;
|