@smartnet360/svelte-components 0.0.93 → 0.0.94
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/features/cells/components/CellSettingsPanel.svelte +40 -0
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte +3 -0
- package/dist/map-v3/features/cells/logic/grid-density.d.ts +51 -0
- package/dist/map-v3/features/cells/logic/grid-density.js +138 -0
- package/dist/map-v3/features/cells/stores/cell.data.svelte.js +5 -2
- package/dist/map-v3/features/cells/stores/cell.display.svelte.d.ts +3 -0
- package/dist/map-v3/features/cells/stores/cell.display.svelte.js +10 -0
- package/dist/map-v3/features/cells/stores/site.distance.svelte.d.ts +6 -1
- package/dist/map-v3/features/cells/stores/site.distance.svelte.js +21 -3
- package/package.json +1 -1
|
@@ -153,6 +153,46 @@
|
|
|
153
153
|
/>
|
|
154
154
|
</div>
|
|
155
155
|
</div>
|
|
156
|
+
|
|
157
|
+
<!-- Grid-Based Outlier Capping -->
|
|
158
|
+
<div class="row align-items-center g-2 mb-3 ps-3">
|
|
159
|
+
<div class="col-4 text-secondary small">Grid Capping</div>
|
|
160
|
+
<div class="col-8">
|
|
161
|
+
<div class="form-check form-switch">
|
|
162
|
+
<input
|
|
163
|
+
class="form-check-input"
|
|
164
|
+
type="checkbox"
|
|
165
|
+
id="gridCappingToggle"
|
|
166
|
+
bind:checked={displayStore.useGridCapping}
|
|
167
|
+
title="Remove outliers using grid-based percentile capping"
|
|
168
|
+
/>
|
|
169
|
+
<label class="form-check-label small" for="gridCappingToggle">
|
|
170
|
+
Remove outliers
|
|
171
|
+
</label>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
{#if displayStore.useGridCapping}
|
|
177
|
+
<!-- Grid Cap Percentile -->
|
|
178
|
+
<div class="row align-items-center g-2 mb-3 ps-3">
|
|
179
|
+
<div class="col-4 text-secondary small">Cap Percentile</div>
|
|
180
|
+
<div class="col-3 text-end">
|
|
181
|
+
<span class="badge bg-white text-muted border">{displayStore.gridCapPercentile}th</span>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="col-5">
|
|
184
|
+
<input
|
|
185
|
+
type="range"
|
|
186
|
+
class="form-range w-100"
|
|
187
|
+
min="70"
|
|
188
|
+
max="95"
|
|
189
|
+
step="5"
|
|
190
|
+
bind:value={displayStore.gridCapPercentile}
|
|
191
|
+
title="Percentile threshold for capping outliers within each grid"
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
{/if}
|
|
156
196
|
{/if}
|
|
157
197
|
|
|
158
198
|
<div class="border-top my-3"></div>
|
|
@@ -136,6 +136,9 @@
|
|
|
136
136
|
const _autoSizeMode = displayStore.autoSizeMode;
|
|
137
137
|
const _autoSizeNeighborCount = displayStore.autoSizeNeighborCount;
|
|
138
138
|
const _autoSizeMultiplier = displayStore.autoSizeMultiplier;
|
|
139
|
+
const _useGridCapping = displayStore.useGridCapping;
|
|
140
|
+
const _gridCapPercentile = displayStore.gridCapPercentile;
|
|
141
|
+
const _gridSizeKm = displayStore.gridSizeKm;
|
|
139
142
|
|
|
140
143
|
updateLayer();
|
|
141
144
|
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid-Based Density Calculation
|
|
3
|
+
*
|
|
4
|
+
* Divides map into fixed-size grids and computes density-based size caps
|
|
5
|
+
* Used to remove outliers and create visual uniformity within local areas
|
|
6
|
+
*/
|
|
7
|
+
import type { Cell } from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* Convert geographic coordinates to grid key
|
|
10
|
+
* @param lon Longitude
|
|
11
|
+
* @param lat Latitude
|
|
12
|
+
* @param gridSizeKm Size of grid cell in kilometers (default: 1km)
|
|
13
|
+
* @returns Grid key string "x_y"
|
|
14
|
+
*/
|
|
15
|
+
export declare function getGridKey(lon: number, lat: number, gridSizeKm?: number): string;
|
|
16
|
+
/**
|
|
17
|
+
* Group cells by grid
|
|
18
|
+
* @param cells Array of cells
|
|
19
|
+
* @param gridSizeKm Grid size in kilometers
|
|
20
|
+
* @returns Map of gridKey -> cells in that grid
|
|
21
|
+
*/
|
|
22
|
+
export declare function groupCellsByGrid(cells: Cell[], gridSizeKm?: number): Map<string, Cell[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Calculate percentile value from array
|
|
25
|
+
* @param values Sorted array of numbers
|
|
26
|
+
* @param percentile Percentile to calculate (0-100, e.g., 80 for 80th percentile)
|
|
27
|
+
* @returns Value at the given percentile
|
|
28
|
+
*/
|
|
29
|
+
export declare function calculatePercentile(values: number[], percentile: number): number;
|
|
30
|
+
/**
|
|
31
|
+
* Compute grid-based size caps using percentile
|
|
32
|
+
* @param cells Array of cells
|
|
33
|
+
* @param sizeMap Map of siteId -> calculated size
|
|
34
|
+
* @param percentile Percentile threshold (0-100, e.g., 80)
|
|
35
|
+
* @param gridSizeKm Grid size in kilometers
|
|
36
|
+
* @param minCellsForCapping Minimum cells in grid to apply capping (default: 3)
|
|
37
|
+
* @returns Map of siteId -> capped size
|
|
38
|
+
*/
|
|
39
|
+
export declare function computeGridBasedSizeCaps(cells: Cell[], sizeMap: Map<string, number>, percentile: number, gridSizeKm?: number, minCellsForCapping?: number): Map<string, number>;
|
|
40
|
+
/**
|
|
41
|
+
* Get statistics about grid-based capping
|
|
42
|
+
* @param originalSizes Map of original sizes
|
|
43
|
+
* @param cappedSizes Map of capped sizes
|
|
44
|
+
* @returns Object with statistics
|
|
45
|
+
*/
|
|
46
|
+
export declare function getGridCappingStats(originalSizes: Map<string, number>, cappedSizes: Map<string, number>): {
|
|
47
|
+
totalSites: number;
|
|
48
|
+
cappedSites: number;
|
|
49
|
+
cappedPercentage: number;
|
|
50
|
+
avgReduction: number;
|
|
51
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid-Based Density Calculation
|
|
3
|
+
*
|
|
4
|
+
* Divides map into fixed-size grids and computes density-based size caps
|
|
5
|
+
* Used to remove outliers and create visual uniformity within local areas
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Convert geographic coordinates to grid key
|
|
9
|
+
* @param lon Longitude
|
|
10
|
+
* @param lat Latitude
|
|
11
|
+
* @param gridSizeKm Size of grid cell in kilometers (default: 1km)
|
|
12
|
+
* @returns Grid key string "x_y"
|
|
13
|
+
*/
|
|
14
|
+
export function getGridKey(lon, lat, gridSizeKm = 1) {
|
|
15
|
+
// Approximate: 1 degree ≈ 111km at equator
|
|
16
|
+
// This is rough but sufficient for grouping
|
|
17
|
+
const degreesPerGrid = gridSizeKm / 111;
|
|
18
|
+
const gridX = Math.floor(lon / degreesPerGrid);
|
|
19
|
+
const gridY = Math.floor(lat / degreesPerGrid);
|
|
20
|
+
return `${gridX}_${gridY}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Group cells by grid
|
|
24
|
+
* @param cells Array of cells
|
|
25
|
+
* @param gridSizeKm Grid size in kilometers
|
|
26
|
+
* @returns Map of gridKey -> cells in that grid
|
|
27
|
+
*/
|
|
28
|
+
export function groupCellsByGrid(cells, gridSizeKm = 1) {
|
|
29
|
+
const grids = new Map();
|
|
30
|
+
for (const cell of cells) {
|
|
31
|
+
const gridKey = getGridKey(cell.longitude, cell.latitude, gridSizeKm);
|
|
32
|
+
if (!grids.has(gridKey)) {
|
|
33
|
+
grids.set(gridKey, []);
|
|
34
|
+
}
|
|
35
|
+
grids.get(gridKey).push(cell);
|
|
36
|
+
}
|
|
37
|
+
return grids;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Calculate percentile value from array
|
|
41
|
+
* @param values Sorted array of numbers
|
|
42
|
+
* @param percentile Percentile to calculate (0-100, e.g., 80 for 80th percentile)
|
|
43
|
+
* @returns Value at the given percentile
|
|
44
|
+
*/
|
|
45
|
+
export function calculatePercentile(values, percentile) {
|
|
46
|
+
if (values.length === 0)
|
|
47
|
+
return Infinity;
|
|
48
|
+
if (values.length === 1)
|
|
49
|
+
return values[0];
|
|
50
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
51
|
+
const index = (percentile / 100) * (sorted.length - 1);
|
|
52
|
+
const lower = Math.floor(index);
|
|
53
|
+
const upper = Math.ceil(index);
|
|
54
|
+
const weight = index % 1;
|
|
55
|
+
if (lower === upper) {
|
|
56
|
+
return sorted[lower];
|
|
57
|
+
}
|
|
58
|
+
return sorted[lower] * (1 - weight) + sorted[upper] * weight;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Compute grid-based size caps using percentile
|
|
62
|
+
* @param cells Array of cells
|
|
63
|
+
* @param sizeMap Map of siteId -> calculated size
|
|
64
|
+
* @param percentile Percentile threshold (0-100, e.g., 80)
|
|
65
|
+
* @param gridSizeKm Grid size in kilometers
|
|
66
|
+
* @param minCellsForCapping Minimum cells in grid to apply capping (default: 3)
|
|
67
|
+
* @returns Map of siteId -> capped size
|
|
68
|
+
*/
|
|
69
|
+
export function computeGridBasedSizeCaps(cells, sizeMap, percentile, gridSizeKm = 1, minCellsForCapping = 3) {
|
|
70
|
+
// Group cells by grid
|
|
71
|
+
const grids = groupCellsByGrid(cells, gridSizeKm);
|
|
72
|
+
// Calculate percentile cap for each grid
|
|
73
|
+
const gridCaps = new Map();
|
|
74
|
+
for (const [gridKey, gridCells] of grids) {
|
|
75
|
+
// Get sizes for all sites in this grid
|
|
76
|
+
const siteSizes = new Map();
|
|
77
|
+
for (const cell of gridCells) {
|
|
78
|
+
const size = sizeMap.get(cell.siteId);
|
|
79
|
+
if (size !== undefined) {
|
|
80
|
+
siteSizes.set(cell.siteId, size);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const uniqueSizes = Array.from(siteSizes.values());
|
|
84
|
+
// Only apply capping if grid has enough cells
|
|
85
|
+
if (uniqueSizes.length >= minCellsForCapping) {
|
|
86
|
+
const cap = calculatePercentile(uniqueSizes, percentile);
|
|
87
|
+
gridCaps.set(gridKey, cap);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// For small grids, use max size (no capping)
|
|
91
|
+
gridCaps.set(gridKey, Math.max(...uniqueSizes, 0));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Apply caps to each site
|
|
95
|
+
const cappedSizes = new Map();
|
|
96
|
+
const sitesProcessed = new Set();
|
|
97
|
+
for (const cell of cells) {
|
|
98
|
+
// Only process each site once
|
|
99
|
+
if (sitesProcessed.has(cell.siteId))
|
|
100
|
+
continue;
|
|
101
|
+
sitesProcessed.add(cell.siteId);
|
|
102
|
+
const originalSize = sizeMap.get(cell.siteId);
|
|
103
|
+
if (originalSize === undefined)
|
|
104
|
+
continue;
|
|
105
|
+
const gridKey = getGridKey(cell.longitude, cell.latitude, gridSizeKm);
|
|
106
|
+
const cap = gridCaps.get(gridKey) ?? originalSize;
|
|
107
|
+
// Apply cap (take minimum)
|
|
108
|
+
cappedSizes.set(cell.siteId, Math.min(originalSize, cap));
|
|
109
|
+
}
|
|
110
|
+
return cappedSizes;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get statistics about grid-based capping
|
|
114
|
+
* @param originalSizes Map of original sizes
|
|
115
|
+
* @param cappedSizes Map of capped sizes
|
|
116
|
+
* @returns Object with statistics
|
|
117
|
+
*/
|
|
118
|
+
export function getGridCappingStats(originalSizes, cappedSizes) {
|
|
119
|
+
let totalSites = 0;
|
|
120
|
+
let cappedSites = 0;
|
|
121
|
+
let totalReduction = 0;
|
|
122
|
+
for (const [siteId, originalSize] of originalSizes) {
|
|
123
|
+
const cappedSize = cappedSizes.get(siteId);
|
|
124
|
+
if (cappedSize === undefined)
|
|
125
|
+
continue;
|
|
126
|
+
totalSites++;
|
|
127
|
+
if (cappedSize < originalSize) {
|
|
128
|
+
cappedSites++;
|
|
129
|
+
totalReduction += originalSize - cappedSize;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
totalSites,
|
|
134
|
+
cappedSites,
|
|
135
|
+
cappedPercentage: totalSites > 0 ? (cappedSites / totalSites) * 100 : 0,
|
|
136
|
+
avgReduction: cappedSites > 0 ? totalReduction / cappedSites : 0
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -13,9 +13,12 @@ export class CellDataStore {
|
|
|
13
13
|
setCells(cells) {
|
|
14
14
|
this.rawCells = cells;
|
|
15
15
|
// Automatically update site distances when cells are loaded
|
|
16
|
-
//
|
|
16
|
+
// Pass auto-size and grid capping settings from displayStore
|
|
17
17
|
const neighborCount = this.displayStore?.autoSizeNeighborCount ?? 5;
|
|
18
|
-
this.
|
|
18
|
+
const useGridCapping = this.displayStore?.useGridCapping ?? true;
|
|
19
|
+
const gridCapPercentile = this.displayStore?.gridCapPercentile ?? 80;
|
|
20
|
+
const gridSizeKm = this.displayStore?.gridSizeKm ?? 1;
|
|
21
|
+
this.siteDistanceStore.updateDistances(cells, neighborCount, useGridCapping, gridCapPercentile, gridSizeKm);
|
|
19
22
|
}
|
|
20
23
|
get filteredCells() {
|
|
21
24
|
if (!this.filterOnAir)
|
|
@@ -11,6 +11,9 @@ export declare class CellDisplayStore {
|
|
|
11
11
|
autoSizeMode: AutoSizeMode;
|
|
12
12
|
autoSizeNeighborCount: number;
|
|
13
13
|
autoSizeMultiplier: number;
|
|
14
|
+
useGridCapping: boolean;
|
|
15
|
+
gridCapPercentile: number;
|
|
16
|
+
gridSizeKm: number;
|
|
14
17
|
level1: CellGroupingField;
|
|
15
18
|
level2: CellGroupingField;
|
|
16
19
|
currentZIndex: Record<string, number>;
|
|
@@ -13,6 +13,10 @@ export class CellDisplayStore {
|
|
|
13
13
|
autoSizeMode = $state('logarithmic');
|
|
14
14
|
autoSizeNeighborCount = $state(5); // Number of neighbors to average for density
|
|
15
15
|
autoSizeMultiplier = $state(1.0); // Scale factor for auto-size results (0.3 - 2.0)
|
|
16
|
+
// Grid-based outlier capping
|
|
17
|
+
useGridCapping = $state(true); // Enable grid-based outlier removal
|
|
18
|
+
gridCapPercentile = $state(80); // Percentile for capping (70-95)
|
|
19
|
+
gridSizeKm = $state(1); // Grid size in kilometers
|
|
16
20
|
// Grouping
|
|
17
21
|
level1 = $state('tech');
|
|
18
22
|
level2 = $state('fband');
|
|
@@ -44,6 +48,9 @@ export class CellDisplayStore {
|
|
|
44
48
|
this.autoSizeMode = parsed.autoSizeMode ?? 'logarithmic';
|
|
45
49
|
this.autoSizeNeighborCount = parsed.autoSizeNeighborCount ?? 5;
|
|
46
50
|
this.autoSizeMultiplier = parsed.autoSizeMultiplier ?? 1.0;
|
|
51
|
+
this.useGridCapping = parsed.useGridCapping ?? true;
|
|
52
|
+
this.gridCapPercentile = parsed.gridCapPercentile ?? 80;
|
|
53
|
+
this.gridSizeKm = parsed.gridSizeKm ?? 1;
|
|
47
54
|
this.level1 = parsed.level1 ?? 'tech';
|
|
48
55
|
this.level2 = parsed.level2 ?? 'fband';
|
|
49
56
|
this.labelPixelDistance = parsed.labelPixelDistance ?? 60;
|
|
@@ -70,6 +77,9 @@ export class CellDisplayStore {
|
|
|
70
77
|
autoSizeMode: this.autoSizeMode,
|
|
71
78
|
autoSizeNeighborCount: this.autoSizeNeighborCount,
|
|
72
79
|
autoSizeMultiplier: this.autoSizeMultiplier,
|
|
80
|
+
useGridCapping: this.useGridCapping,
|
|
81
|
+
gridCapPercentile: this.gridCapPercentile,
|
|
82
|
+
gridSizeKm: this.gridSizeKm,
|
|
73
83
|
level1: this.level1,
|
|
74
84
|
level2: this.level2,
|
|
75
85
|
labelPixelDistance: this.labelPixelDistance,
|
|
@@ -8,6 +8,7 @@ import type { Cell } from '../types';
|
|
|
8
8
|
export declare class SiteDistanceStore {
|
|
9
9
|
key: string;
|
|
10
10
|
distances: Map<string, number>;
|
|
11
|
+
cappedSizes: Map<string, number>;
|
|
11
12
|
computedSites: Set<string>;
|
|
12
13
|
lastComputeTime: number;
|
|
13
14
|
computedCount: number;
|
|
@@ -17,14 +18,18 @@ export declare class SiteDistanceStore {
|
|
|
17
18
|
* Only computes distances for sites not in cache
|
|
18
19
|
* @param cells Array of cells to compute distances for
|
|
19
20
|
* @param neighborCount Number of neighbors to average for density calculation
|
|
21
|
+
* @param useGridCapping Whether to apply grid-based outlier capping
|
|
22
|
+
* @param gridCapPercentile Percentile for grid capping (70-95)
|
|
23
|
+
* @param gridSizeKm Grid size in kilometers
|
|
20
24
|
*/
|
|
21
|
-
updateDistances(cells: Cell[], neighborCount?: number): void;
|
|
25
|
+
updateDistances(cells: Cell[], neighborCount?: number, useGridCapping?: boolean, gridCapPercentile?: number, gridSizeKm?: number): void;
|
|
22
26
|
/**
|
|
23
27
|
* Update existing sites if needed (when no new sites but data might have changed)
|
|
24
28
|
*/
|
|
25
29
|
private updateExistingSitesIfNeeded;
|
|
26
30
|
/**
|
|
27
31
|
* Get distance for a site, with fallback
|
|
32
|
+
* Returns capped size if grid capping was applied, otherwise original
|
|
28
33
|
*/
|
|
29
34
|
getDistance(siteId: string, fallback?: number): number;
|
|
30
35
|
/**
|
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { browser } from '$app/environment';
|
|
8
8
|
import { extractSiteLocations, computeNearestNeighbor, haversineDistance } from '../logic/site-distance';
|
|
9
|
+
import { computeGridBasedSizeCaps, getGridCappingStats } from '../logic/grid-density';
|
|
9
10
|
export class SiteDistanceStore {
|
|
10
11
|
key = 'map-v3-site-distances';
|
|
11
|
-
// Cached nearest neighbor distances
|
|
12
|
+
// Cached nearest neighbor distances (original calculated sizes)
|
|
12
13
|
distances = $state(new Map());
|
|
14
|
+
// Grid-capped sizes (after outlier removal)
|
|
15
|
+
cappedSizes = $state(new Map());
|
|
13
16
|
// Track which sites have been computed
|
|
14
17
|
computedSites = $state(new Set());
|
|
15
18
|
// Performance tracking
|
|
@@ -25,8 +28,11 @@ export class SiteDistanceStore {
|
|
|
25
28
|
* Only computes distances for sites not in cache
|
|
26
29
|
* @param cells Array of cells to compute distances for
|
|
27
30
|
* @param neighborCount Number of neighbors to average for density calculation
|
|
31
|
+
* @param useGridCapping Whether to apply grid-based outlier capping
|
|
32
|
+
* @param gridCapPercentile Percentile for grid capping (70-95)
|
|
33
|
+
* @param gridSizeKm Grid size in kilometers
|
|
28
34
|
*/
|
|
29
|
-
updateDistances(cells, neighborCount = 5) {
|
|
35
|
+
updateDistances(cells, neighborCount = 5, useGridCapping = true, gridCapPercentile = 80, gridSizeKm = 1) {
|
|
30
36
|
if (!cells || cells.length === 0)
|
|
31
37
|
return;
|
|
32
38
|
const startTime = performance.now();
|
|
@@ -55,6 +61,16 @@ export class SiteDistanceStore {
|
|
|
55
61
|
const updatedDist = computeNearestNeighbor(existingId, existingLoc, siteLocations, neighborCount);
|
|
56
62
|
this.distances.set(existingId, updatedDist);
|
|
57
63
|
}
|
|
64
|
+
// Apply grid-based capping if enabled
|
|
65
|
+
if (useGridCapping) {
|
|
66
|
+
this.cappedSizes = computeGridBasedSizeCaps(cells, this.distances, gridCapPercentile, gridSizeKm);
|
|
67
|
+
const stats = getGridCappingStats(this.distances, this.cappedSizes);
|
|
68
|
+
console.log(`[SiteDistance] Grid capping: ${stats.cappedSites}/${stats.totalSites} sites capped (${stats.cappedPercentage.toFixed(1)}%), avg reduction: ${stats.avgReduction.toFixed(1)}m`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// No capping - capped sizes = original sizes
|
|
72
|
+
this.cappedSizes = new Map(this.distances);
|
|
73
|
+
}
|
|
58
74
|
const endTime = performance.now();
|
|
59
75
|
this.lastComputeTime = endTime - startTime;
|
|
60
76
|
this.computedCount = this.distances.size;
|
|
@@ -82,15 +98,17 @@ export class SiteDistanceStore {
|
|
|
82
98
|
}
|
|
83
99
|
/**
|
|
84
100
|
* Get distance for a site, with fallback
|
|
101
|
+
* Returns capped size if grid capping was applied, otherwise original
|
|
85
102
|
*/
|
|
86
103
|
getDistance(siteId, fallback = 500) {
|
|
87
|
-
return this.distances.get(siteId) ?? fallback;
|
|
104
|
+
return this.cappedSizes.get(siteId) ?? this.distances.get(siteId) ?? fallback;
|
|
88
105
|
}
|
|
89
106
|
/**
|
|
90
107
|
* Clear all cached distances (useful for testing or data refresh)
|
|
91
108
|
*/
|
|
92
109
|
clear() {
|
|
93
110
|
this.distances.clear();
|
|
111
|
+
this.cappedSizes.clear();
|
|
94
112
|
this.computedSites.clear();
|
|
95
113
|
this.lastComputeTime = 0;
|
|
96
114
|
this.computedCount = 0;
|