@smartnet360/svelte-components 0.0.93 → 0.0.95
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/demo/demo-cells.d.ts +2 -1
- package/dist/map-v3/demo/demo-cells.js +105 -18
- package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte +1 -40
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte +17 -33
- package/dist/map-v3/features/cells/logic/site-distance.d.ts +2 -4
- package/dist/map-v3/features/cells/logic/site-distance.js +5 -13
- package/dist/map-v3/features/cells/stores/cell.data.svelte.d.ts +2 -8
- package/dist/map-v3/features/cells/stores/cell.data.svelte.js +4 -13
- package/dist/map-v3/features/cells/stores/cell.display.svelte.d.ts +0 -2
- package/dist/map-v3/features/cells/stores/cell.display.svelte.js +0 -6
- package/dist/map-v3/features/cells/stores/site.distance.svelte.d.ts +3 -9
- package/dist/map-v3/features/cells/stores/site.distance.svelte.js +11 -46
- package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte +1 -0
- package/dist/map-v3/features/sites/layers/SitesLayer.svelte +2 -1
- package/dist/map-v3/features/sites/stores/site.data.svelte.js +4 -1
- package/dist/map-v3/features/sites/types.d.ts +1 -0
- 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();
|
|
33
34
|
const cellRegistry = createCellRegistry('demo-map');
|
|
34
35
|
const cellDisplay = new CellDisplayStore();
|
|
35
|
-
const cellData = createCellDataStore(cellDisplay);
|
|
36
36
|
|
|
37
37
|
const siteData = createSiteDataStore(cellData);
|
|
38
38
|
const siteRegistry = createSiteRegistry('demo-map');
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { Cell } from '../features/cells/types';
|
|
10
10
|
/**
|
|
11
|
-
* Generate demo cells
|
|
11
|
+
* Generate demo cells with varied density patterns in circular distribution
|
|
12
|
+
* Creates density zones radiating from center with random placement
|
|
12
13
|
*/
|
|
13
14
|
export declare const demoCells: Cell[];
|
|
@@ -8,11 +8,66 @@
|
|
|
8
8
|
*/
|
|
9
9
|
const BASE_LAT = 47.4979;
|
|
10
10
|
const BASE_LNG = 19.0402;
|
|
11
|
-
//
|
|
12
|
-
const NUM_SITES =
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
11
|
+
// Generate sites in a circular pattern with varying density
|
|
12
|
+
const NUM_SITES = 2000;
|
|
13
|
+
const RADIUS_KM = 15; // 15km radius circle
|
|
14
|
+
const RADIUS_DEGREES = RADIUS_KM / 111; // Approximate conversion
|
|
15
|
+
// Density zones (distance from center)
|
|
16
|
+
const DENSITY_ZONES = [
|
|
17
|
+
{ maxRadius: 0.3, minSpacing: 0.0008, maxSpacing: 0.0015, name: 'Very Dense Core' }, // 0-3km: 80-150m spacing
|
|
18
|
+
{ maxRadius: 0.5, minSpacing: 0.0015, maxSpacing: 0.003, name: 'Dense Inner' }, // 3-5km: 150-300m spacing
|
|
19
|
+
{ maxRadius: 0.7, minSpacing: 0.003, maxSpacing: 0.006, name: 'Medium' }, // 5-7km: 300-600m spacing
|
|
20
|
+
{ maxRadius: 0.85, minSpacing: 0.006, maxSpacing: 0.012, name: 'Sparse Suburban' }, // 7-12km: 600m-1.2km spacing
|
|
21
|
+
{ maxRadius: 1.0, minSpacing: 0.012, maxSpacing: 0.025, name: 'Very Sparse Rural' } // 12-15km: 1.2-2.5km spacing
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Get density zone for a given normalized radius
|
|
25
|
+
*/
|
|
26
|
+
function getDensityZone(normalizedRadius) {
|
|
27
|
+
for (const zone of DENSITY_ZONES) {
|
|
28
|
+
if (normalizedRadius <= zone.maxRadius) {
|
|
29
|
+
return zone;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return DENSITY_ZONES[DENSITY_ZONES.length - 1];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Generate random point within circle using polar coordinates
|
|
36
|
+
*/
|
|
37
|
+
function generateRandomPointInCircle() {
|
|
38
|
+
// Use square root for uniform distribution in circle
|
|
39
|
+
const r = Math.sqrt(Math.random()) * RADIUS_DEGREES;
|
|
40
|
+
const theta = Math.random() * 2 * Math.PI;
|
|
41
|
+
const lat = BASE_LAT + r * Math.cos(theta);
|
|
42
|
+
const lng = BASE_LNG + r * Math.sin(theta);
|
|
43
|
+
const normalizedRadius = r / RADIUS_DEGREES;
|
|
44
|
+
return { lat, lng, normalizedRadius };
|
|
45
|
+
}
|
|
46
|
+
// Cluster configuration for varied density
|
|
47
|
+
// (kept for backward compatibility but not used with circular generation)
|
|
48
|
+
const CLUSTERS = [
|
|
49
|
+
// Dense urban cluster (top-left) - very tight spacing
|
|
50
|
+
{ startRow: 0, endRow: 3, startCol: 0, endCol: 3, spacing: 0.3 },
|
|
51
|
+
// Medium density cluster (center) - normal spacing
|
|
52
|
+
{ startRow: 3, endRow: 7, startCol: 3, endCol: 7, spacing: 1.0 },
|
|
53
|
+
// Sparse rural cluster (bottom-right) - wide spacing
|
|
54
|
+
{ startRow: 7, endRow: 10, startCol: 7, endCol: 10, spacing: 2.5 },
|
|
55
|
+
// Random outliers scattered around
|
|
56
|
+
{ startRow: 0, endRow: 10, startCol: 0, endCol: 10, spacing: 1.5 }
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* Add random jitter to coordinates for natural variation
|
|
60
|
+
*/
|
|
61
|
+
function addJitter(value, maxJitter) {
|
|
62
|
+
return value + (Math.random() - 0.5) * 2 * maxJitter;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Determine if site should be skipped (for creating gaps)
|
|
66
|
+
*/
|
|
67
|
+
function shouldSkipSite(row, col) {
|
|
68
|
+
// Skip some sites randomly to create density variation (20% skip rate)
|
|
69
|
+
return Math.random() < 0.2;
|
|
70
|
+
}
|
|
16
71
|
// Standard beamwidth for sectors
|
|
17
72
|
const BEAMWIDTH = 65;
|
|
18
73
|
// Cell tech-band definitions with proper fband format
|
|
@@ -50,20 +105,51 @@ const STATUSES = [
|
|
|
50
105
|
'On_Air'
|
|
51
106
|
];
|
|
52
107
|
/**
|
|
53
|
-
* Generate demo cells
|
|
108
|
+
* Generate demo cells with varied density patterns in circular distribution
|
|
109
|
+
* Creates density zones radiating from center with random placement
|
|
54
110
|
*/
|
|
55
111
|
export const demoCells = [];
|
|
56
112
|
let cellCounter = 1;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
113
|
+
let actualSiteIndex = 0;
|
|
114
|
+
// Track used positions to maintain minimum spacing
|
|
115
|
+
const usedPositions = [];
|
|
116
|
+
/**
|
|
117
|
+
* Check if position is too close to existing sites
|
|
118
|
+
*/
|
|
119
|
+
function isTooClose(lat, lng, minSpacing) {
|
|
120
|
+
for (const pos of usedPositions) {
|
|
121
|
+
const distance = Math.sqrt(Math.pow(lat - pos.lat, 2) + Math.pow(lng - pos.lng, 2));
|
|
122
|
+
const requiredSpacing = (minSpacing + pos.minSpacing) / 2;
|
|
123
|
+
if (distance < requiredSpacing) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
// Generate sites in a circular pattern with density-based placement
|
|
130
|
+
for (let attempt = 0; attempt < NUM_SITES * 3 && actualSiteIndex < NUM_SITES; attempt++) {
|
|
131
|
+
// Generate random point in circle
|
|
132
|
+
const { lat, lng, normalizedRadius } = generateRandomPointInCircle();
|
|
133
|
+
// Get density zone for this radius
|
|
134
|
+
const zone = getDensityZone(normalizedRadius);
|
|
135
|
+
// Random spacing within zone range
|
|
136
|
+
const minSpacing = zone.minSpacing + Math.random() * (zone.maxSpacing - zone.minSpacing);
|
|
137
|
+
// Check if too close to existing sites
|
|
138
|
+
if (isTooClose(lat, lng, minSpacing)) {
|
|
139
|
+
continue; // Try another position
|
|
140
|
+
}
|
|
141
|
+
// Add random jitter for natural variation
|
|
142
|
+
const jitterAmount = minSpacing * 0.3; // 30% of spacing
|
|
143
|
+
const siteLat = addJitter(lat, jitterAmount);
|
|
144
|
+
const siteLng = addJitter(lng, jitterAmount);
|
|
145
|
+
// Record position
|
|
146
|
+
usedPositions.push({ lat: siteLat, lng: siteLng, minSpacing });
|
|
147
|
+
const siteId = `DEMO-SITE-${String(actualSiteIndex + 1).padStart(4, '0')}`;
|
|
148
|
+
actualSiteIndex++;
|
|
149
|
+
// Generate 3 sectors per site (with some random 1 or 2 sector sites)
|
|
150
|
+
const numSectors = Math.random() < 0.1 ? (Math.random() < 0.5 ? 1 : 2) : 3; // 10% chance of 1-2 sectors
|
|
151
|
+
const sectorsToGenerate = AZIMUTHS.slice(0, numSectors);
|
|
152
|
+
sectorsToGenerate.forEach((azimuth, sectorIndex) => {
|
|
67
153
|
// Generate 12 tech-bands per sector
|
|
68
154
|
TECH_BANDS.forEach((techBand, techIndex) => {
|
|
69
155
|
const cellId = `CELL-${String(cellCounter).padStart(4, '0')}`;
|
|
@@ -117,10 +203,11 @@ for (let siteIndex = 0; siteIndex < NUM_SITES; siteIndex++) {
|
|
|
117
203
|
// Other
|
|
118
204
|
other: {
|
|
119
205
|
demoCell: true,
|
|
120
|
-
siteNumber:
|
|
206
|
+
siteNumber: actualSiteIndex,
|
|
121
207
|
sector: sectorIndex + 1,
|
|
122
208
|
techBandKey: `${techBand.tech}_${techBand.band}`,
|
|
123
|
-
|
|
209
|
+
radius: normalizedRadius,
|
|
210
|
+
densityZone: zone.name
|
|
124
211
|
},
|
|
125
212
|
customSubgroup: `Sector-${sectorIndex + 1}`
|
|
126
213
|
});
|
|
@@ -75,10 +75,9 @@
|
|
|
75
75
|
class="form-select form-select-sm"
|
|
76
76
|
bind:value={displayStore.layerGrouping}
|
|
77
77
|
>
|
|
78
|
-
<option value="frequency">
|
|
78
|
+
<option value="frequency">Frequency Priority</option>
|
|
79
79
|
<option value="technology">Technology Priority</option>
|
|
80
80
|
<option value="balanced">Balanced</option>
|
|
81
|
-
<option value="ltePriority">LTE Priority</option>
|
|
82
81
|
</select>
|
|
83
82
|
</div>
|
|
84
83
|
</div>
|
|
@@ -115,44 +114,6 @@
|
|
|
115
114
|
</select>
|
|
116
115
|
</div>
|
|
117
116
|
</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>
|
|
156
117
|
{/if}
|
|
157
118
|
|
|
158
119
|
<div class="border-top my-3"></div>
|
|
@@ -102,13 +102,8 @@
|
|
|
102
102
|
|
|
103
103
|
// Events for updating
|
|
104
104
|
map.on('style.load', addLayers);
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
}
|
|
105
|
+
map.on('moveend', updateLayer);
|
|
106
|
+
map.on('zoomend', updateLayer);
|
|
112
107
|
|
|
113
108
|
// Cleanup
|
|
114
109
|
return () => {
|
|
@@ -134,8 +129,6 @@
|
|
|
134
129
|
const _layerGrouping = displayStore.layerGrouping;
|
|
135
130
|
const _useAutoSize = displayStore.useAutoSize;
|
|
136
131
|
const _autoSizeMode = displayStore.autoSizeMode;
|
|
137
|
-
const _autoSizeNeighborCount = displayStore.autoSizeNeighborCount;
|
|
138
|
-
const _autoSizeMultiplier = displayStore.autoSizeMultiplier;
|
|
139
132
|
|
|
140
133
|
updateLayer();
|
|
141
134
|
});
|
|
@@ -151,22 +144,20 @@
|
|
|
151
144
|
}
|
|
152
145
|
|
|
153
146
|
function renderCells(map: mapboxgl.Map) {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const centerLat = useAutoSize ? 0 : map.getCenter().lat;
|
|
147
|
+
const bounds = map.getBounds();
|
|
148
|
+
if (!bounds) return;
|
|
149
|
+
|
|
150
|
+
const zoom = map.getZoom();
|
|
151
|
+
const centerLat = map.getCenter().lat;
|
|
160
152
|
|
|
161
|
-
console.log(`[CellsLayer] Rendering ${
|
|
153
|
+
console.log(`[CellsLayer] Rendering.. Zoom: ${zoom.toFixed(2)}, Cells: ${dataStore.filteredCells.length}`);
|
|
162
154
|
|
|
163
|
-
// 1. Calculate base radius
|
|
164
|
-
const baseRadiusMeters =
|
|
165
|
-
|
|
166
|
-
console.log(`[CellsLayer] Base radius: ${baseRadiusMeters.toFixed(2)}m for target ${displayStore.targetPixelSize}px at zoom ${zoom.toFixed(2)}`);
|
|
167
|
-
}
|
|
155
|
+
// 1. Calculate base radius
|
|
156
|
+
const baseRadiusMeters = calculateRadiusInMeters(centerLat, zoom, displayStore.targetPixelSize);
|
|
157
|
+
console.log(`[CellsLayer] Base radius: ${baseRadiusMeters.toFixed(2)}m for target ${displayStore.targetPixelSize}px`);
|
|
168
158
|
|
|
169
|
-
// 2. Group cells
|
|
159
|
+
// 2. Group cells (Level 1=Tech, Level 2=Band for now hardcoded)
|
|
160
|
+
// In real app, this comes from a store
|
|
170
161
|
const groups = groupCells(dataStore.filteredCells, displayStore.level1, displayStore.level2);
|
|
171
162
|
console.log(`[CellsLayer] Groups: ${groups.size}`);
|
|
172
163
|
|
|
@@ -182,17 +173,13 @@
|
|
|
182
173
|
if (!style.visible) continue;
|
|
183
174
|
|
|
184
175
|
for (const cell of cells) {
|
|
185
|
-
// 4. BBox Filter (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (inView) {
|
|
176
|
+
// 4. BBox Filter (Simple point check)
|
|
177
|
+
if (bounds.contains([cell.longitude, cell.latitude])) {
|
|
189
178
|
// 5. Z-Index Lookup
|
|
190
179
|
const zIndexKey = `${cell.tech}_${cell.frq}` as TechnologyBandKey;
|
|
191
180
|
const zIndex = displayStore.currentZIndex[zIndexKey] ?? 10;
|
|
192
181
|
|
|
193
|
-
// 6. Calculate radius
|
|
194
|
-
// Auto-size: Fixed meter-based size from site density
|
|
195
|
-
// Manual: Pixel-based size that scales with zoom
|
|
182
|
+
// 6. Calculate radius with z-index scaling
|
|
196
183
|
const MAX_Z = 35;
|
|
197
184
|
let radiusMeters: number;
|
|
198
185
|
|
|
@@ -201,13 +188,10 @@
|
|
|
201
188
|
const siteDistance = dataStore.siteDistanceStore.getDistance(cell.siteId, 500);
|
|
202
189
|
const autoRadius = calculateAutoRadius(siteDistance, displayStore.autoSizeMode);
|
|
203
190
|
|
|
204
|
-
// Apply user's multiplier to scale the result
|
|
205
|
-
const adjustedAutoRadius = autoRadius * displayStore.autoSizeMultiplier;
|
|
206
|
-
|
|
207
191
|
// Scale based on z-index for stacking visibility
|
|
208
192
|
// Lower z-index (background) = larger, higher z-index (foreground) = smaller
|
|
209
193
|
const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08; // 8% per layer
|
|
210
|
-
radiusMeters =
|
|
194
|
+
radiusMeters = autoRadius * scaleFactor;
|
|
211
195
|
} else {
|
|
212
196
|
// Manual mode: base from pixel size, then scale by z-index
|
|
213
197
|
const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08;
|
|
@@ -26,11 +26,9 @@ 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
|
|
30
29
|
* @param siteId Site to compute distance for
|
|
31
30
|
* @param siteLocation Location of the site
|
|
32
31
|
* @param allSiteLocations Map of all site locations
|
|
33
|
-
* @
|
|
34
|
-
* @returns Average distance to N nearest neighbors in meters, or Infinity if no neighbors
|
|
32
|
+
* @returns Distance to nearest neighbor in meters, or Infinity if no neighbors
|
|
35
33
|
*/
|
|
36
|
-
export declare function computeNearestNeighbor(siteId: string, siteLocation: [number, number], allSiteLocations: Map<string, [number, number]
|
|
34
|
+
export declare function computeNearestNeighbor(siteId: string, siteLocation: [number, number], allSiteLocations: Map<string, [number, number]>): number;
|
|
@@ -54,26 +54,18 @@ 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
|
|
58
57
|
* @param siteId Site to compute distance for
|
|
59
58
|
* @param siteLocation Location of the site
|
|
60
59
|
* @param allSiteLocations Map of all site locations
|
|
61
|
-
* @
|
|
62
|
-
* @returns Average distance to N nearest neighbors in meters, or Infinity if no neighbors
|
|
60
|
+
* @returns Distance to nearest neighbor in meters, or Infinity if no neighbors
|
|
63
61
|
*/
|
|
64
|
-
export function computeNearestNeighbor(siteId, siteLocation, allSiteLocations
|
|
65
|
-
|
|
62
|
+
export function computeNearestNeighbor(siteId, siteLocation, allSiteLocations) {
|
|
63
|
+
let minDist = Infinity;
|
|
66
64
|
for (const [otherId, otherLoc] of allSiteLocations) {
|
|
67
65
|
if (siteId === otherId)
|
|
68
66
|
continue;
|
|
69
67
|
const dist = haversineDistance(siteLocation, otherLoc);
|
|
70
|
-
|
|
68
|
+
minDist = Math.min(minDist, dist);
|
|
71
69
|
}
|
|
72
|
-
|
|
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;
|
|
70
|
+
return minDist;
|
|
79
71
|
}
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import type { Cell } from '../types';
|
|
2
2
|
import { SiteDistanceStore } from './site.distance.svelte';
|
|
3
|
-
import type { CellDisplayStore } from './cell.display.svelte';
|
|
4
3
|
export declare class CellDataStore {
|
|
5
4
|
rawCells: Cell[];
|
|
6
5
|
filterOnAir: boolean;
|
|
7
6
|
siteDistanceStore: SiteDistanceStore;
|
|
8
|
-
|
|
9
|
-
constructor(displayStore?: CellDisplayStore);
|
|
7
|
+
constructor();
|
|
10
8
|
setCells(cells: Cell[]): void;
|
|
11
9
|
get filteredCells(): Cell[];
|
|
12
10
|
}
|
|
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;
|
|
11
|
+
export declare function createCellDataStore(): CellDataStore;
|
|
@@ -4,18 +4,13 @@ export class CellDataStore {
|
|
|
4
4
|
filterOnAir = $state(false);
|
|
5
5
|
// Internal site distance store for auto-sizing
|
|
6
6
|
siteDistanceStore;
|
|
7
|
-
|
|
8
|
-
displayStore;
|
|
9
|
-
constructor(displayStore) {
|
|
7
|
+
constructor() {
|
|
10
8
|
this.siteDistanceStore = new SiteDistanceStore();
|
|
11
|
-
this.displayStore = displayStore;
|
|
12
9
|
}
|
|
13
10
|
setCells(cells) {
|
|
14
11
|
this.rawCells = cells;
|
|
15
12
|
// Automatically update site distances when cells are loaded
|
|
16
|
-
|
|
17
|
-
const neighborCount = this.displayStore?.autoSizeNeighborCount ?? 5;
|
|
18
|
-
this.siteDistanceStore.updateDistances(cells, neighborCount);
|
|
13
|
+
this.siteDistanceStore.updateDistances(cells);
|
|
19
14
|
}
|
|
20
15
|
get filteredCells() {
|
|
21
16
|
if (!this.filterOnAir)
|
|
@@ -23,10 +18,6 @@ export class CellDataStore {
|
|
|
23
18
|
return this.rawCells.filter(c => c.status === 'On_Air');
|
|
24
19
|
}
|
|
25
20
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* @param displayStore Optional display store for auto-size settings
|
|
29
|
-
*/
|
|
30
|
-
export function createCellDataStore(displayStore) {
|
|
31
|
-
return new CellDataStore(displayStore);
|
|
21
|
+
export function createCellDataStore() {
|
|
22
|
+
return new CellDataStore();
|
|
32
23
|
}
|
|
@@ -9,8 +9,6 @@ export declare class CellDisplayStore {
|
|
|
9
9
|
layerGrouping: LayerGroupingPreset;
|
|
10
10
|
useAutoSize: boolean;
|
|
11
11
|
autoSizeMode: AutoSizeMode;
|
|
12
|
-
autoSizeNeighborCount: number;
|
|
13
|
-
autoSizeMultiplier: number;
|
|
14
12
|
level1: CellGroupingField;
|
|
15
13
|
level2: CellGroupingField;
|
|
16
14
|
currentZIndex: Record<string, number>;
|
|
@@ -11,8 +11,6 @@ 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)
|
|
16
14
|
// Grouping
|
|
17
15
|
level1 = $state('tech');
|
|
18
16
|
level2 = $state('fband');
|
|
@@ -42,8 +40,6 @@ export class CellDisplayStore {
|
|
|
42
40
|
this.layerGrouping = parsed.layerGrouping ?? 'frequency';
|
|
43
41
|
this.useAutoSize = parsed.useAutoSize ?? false;
|
|
44
42
|
this.autoSizeMode = parsed.autoSizeMode ?? 'logarithmic';
|
|
45
|
-
this.autoSizeNeighborCount = parsed.autoSizeNeighborCount ?? 5;
|
|
46
|
-
this.autoSizeMultiplier = parsed.autoSizeMultiplier ?? 1.0;
|
|
47
43
|
this.level1 = parsed.level1 ?? 'tech';
|
|
48
44
|
this.level2 = parsed.level2 ?? 'fband';
|
|
49
45
|
this.labelPixelDistance = parsed.labelPixelDistance ?? 60;
|
|
@@ -68,8 +64,6 @@ export class CellDisplayStore {
|
|
|
68
64
|
layerGrouping: this.layerGrouping,
|
|
69
65
|
useAutoSize: this.useAutoSize,
|
|
70
66
|
autoSizeMode: this.autoSizeMode,
|
|
71
|
-
autoSizeNeighborCount: this.autoSizeNeighborCount,
|
|
72
|
-
autoSizeMultiplier: this.autoSizeMultiplier,
|
|
73
67
|
level1: this.level1,
|
|
74
68
|
level2: this.level2,
|
|
75
69
|
labelPixelDistance: this.labelPixelDistance,
|
|
@@ -13,16 +13,10 @@ export declare class SiteDistanceStore {
|
|
|
13
13
|
computedCount: number;
|
|
14
14
|
constructor();
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @param cells Array of cells to compute distances for
|
|
19
|
-
* @param neighborCount Number of neighbors to average for density calculation
|
|
16
|
+
* Update distances for all sites in current dataset
|
|
17
|
+
* Always recomputes to ensure accuracy
|
|
20
18
|
*/
|
|
21
|
-
updateDistances(cells: Cell[]
|
|
22
|
-
/**
|
|
23
|
-
* Update existing sites if needed (when no new sites but data might have changed)
|
|
24
|
-
*/
|
|
25
|
-
private updateExistingSitesIfNeeded;
|
|
19
|
+
updateDistances(cells: Cell[]): void;
|
|
26
20
|
/**
|
|
27
21
|
* Get distance for a site, with fallback
|
|
28
22
|
*/
|
|
@@ -21,65 +21,30 @@ export class SiteDistanceStore {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* @param cells Array of cells to compute distances for
|
|
27
|
-
* @param neighborCount Number of neighbors to average for density calculation
|
|
24
|
+
* Update distances for all sites in current dataset
|
|
25
|
+
* Always recomputes to ensure accuracy
|
|
28
26
|
*/
|
|
29
|
-
updateDistances(cells
|
|
27
|
+
updateDistances(cells) {
|
|
30
28
|
if (!cells || cells.length === 0)
|
|
31
29
|
return;
|
|
32
30
|
const startTime = performance.now();
|
|
33
31
|
const siteLocations = extractSiteLocations(cells);
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
console.log(`[SiteDistance] Computing distances for ${newSites.length} new sites (${neighborCount} neighbors)`);
|
|
43
|
-
// Compute distances for NEW sites
|
|
44
|
-
for (const siteId of newSites) {
|
|
45
|
-
const location = siteLocations.get(siteId);
|
|
46
|
-
const nearestDist = computeNearestNeighbor(siteId, location, siteLocations, neighborCount);
|
|
32
|
+
// Clear existing data and recompute all sites
|
|
33
|
+
this.distances.clear();
|
|
34
|
+
this.computedSites.clear();
|
|
35
|
+
console.log(`[SiteDistance] Computing distances for ${siteLocations.size} sites`);
|
|
36
|
+
// Compute distances for ALL sites
|
|
37
|
+
for (const [siteId, location] of siteLocations) {
|
|
38
|
+
const nearestDist = computeNearestNeighbor(siteId, location, siteLocations);
|
|
47
39
|
this.distances.set(siteId, nearestDist);
|
|
48
40
|
this.computedSites.add(siteId);
|
|
49
41
|
}
|
|
50
|
-
// Update EXISTING sites if a new site is closer
|
|
51
|
-
for (const [existingId, existingLoc] of siteLocations) {
|
|
52
|
-
if (newSites.includes(existingId))
|
|
53
|
-
continue; // Skip new sites
|
|
54
|
-
// Recalculate with new sites present
|
|
55
|
-
const updatedDist = computeNearestNeighbor(existingId, existingLoc, siteLocations, neighborCount);
|
|
56
|
-
this.distances.set(existingId, updatedDist);
|
|
57
|
-
}
|
|
58
42
|
const endTime = performance.now();
|
|
59
43
|
this.lastComputeTime = endTime - startTime;
|
|
60
44
|
this.computedCount = this.distances.size;
|
|
61
|
-
console.log(`[SiteDistance] Computed ${
|
|
45
|
+
console.log(`[SiteDistance] Computed ${this.computedCount} sites in ${this.lastComputeTime.toFixed(1)}ms`);
|
|
62
46
|
this.persist();
|
|
63
47
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Update existing sites if needed (when no new sites but data might have changed)
|
|
66
|
-
*/
|
|
67
|
-
updateExistingSitesIfNeeded(siteLocations) {
|
|
68
|
-
// Check if all computed sites still exist in current data
|
|
69
|
-
const currentSiteIds = new Set(siteLocations.keys());
|
|
70
|
-
let removed = 0;
|
|
71
|
-
for (const computedId of this.computedSites) {
|
|
72
|
-
if (!currentSiteIds.has(computedId)) {
|
|
73
|
-
this.distances.delete(computedId);
|
|
74
|
-
this.computedSites.delete(computedId);
|
|
75
|
-
removed++;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (removed > 0) {
|
|
79
|
-
console.log(`[SiteDistance] Removed ${removed} stale sites from cache`);
|
|
80
|
-
this.persist();
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
48
|
/**
|
|
84
49
|
* Get distance for a site, with fallback
|
|
85
50
|
*/
|
|
@@ -10,6 +10,8 @@ export class SiteDataStore {
|
|
|
10
10
|
if (!cell.siteId)
|
|
11
11
|
continue;
|
|
12
12
|
if (!siteMap.has(cell.siteId)) {
|
|
13
|
+
// Get distance to nearest site from cell data store
|
|
14
|
+
const nearestDistance = this.cellDataStore.siteDistanceStore.getDistance(cell.siteId, Infinity);
|
|
13
15
|
siteMap.set(cell.siteId, {
|
|
14
16
|
siteId: cell.siteId,
|
|
15
17
|
siteName: cell.cellName, // Fallback to cell name if site name not available
|
|
@@ -20,7 +22,8 @@ export class SiteDataStore {
|
|
|
20
22
|
provider: 'Cetin',
|
|
21
23
|
level1: 'Cetin', // Need to check where provider comes from in V3,
|
|
22
24
|
level2: 'Unknown',
|
|
23
|
-
cellCount: 1
|
|
25
|
+
cellCount: 1,
|
|
26
|
+
nearestSiteDistance: nearestDistance !== Infinity ? Math.round(nearestDistance) : undefined
|
|
24
27
|
});
|
|
25
28
|
}
|
|
26
29
|
else {
|