@smartnet360/svelte-components 0.0.90 → 0.0.91

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.
@@ -5,6 +5,7 @@
5
5
  import { createCellDataStore } from '../features/cells/stores/cell.data.svelte';
6
6
  import { createCellRegistry } from '../features/cells/stores/cell.registry.svelte';
7
7
  import { CellDisplayStore } from '../features/cells/stores/cell.display.svelte';
8
+ import { SiteDistanceStore } from '../features/cells/stores/site.distance.svelte';
8
9
  import FeatureSettingsControl from '../core/controls/FeatureSettingsControl.svelte';
9
10
  import CellFilterControl from '../features/cells/components/CellFilterControl.svelte';
10
11
  import { createRepeaterDataStore } from '../features/repeaters/stores/repeater.data.svelte';
@@ -33,6 +34,7 @@
33
34
  const cellData = createCellDataStore();
34
35
  const cellRegistry = createCellRegistry('demo-map');
35
36
  const cellDisplay = new CellDisplayStore();
37
+ const siteDistanceStore = new SiteDistanceStore();
36
38
 
37
39
  const siteData = createSiteDataStore(cellData);
38
40
  const siteRegistry = createSiteRegistry('demo-map');
@@ -47,6 +49,9 @@
47
49
  // Need to cast or map if types slightly differ, but they should match
48
50
  cellData.setCells(demoCells as any);
49
51
  repeaterData.setRepeaters(demoRepeaters);
52
+
53
+ // Compute site distances
54
+ siteDistanceStore.updateDistances(demoCells as any);
50
55
  });
51
56
  </script>
52
57
 
@@ -90,7 +95,12 @@
90
95
 
91
96
  <SitesLayer dataStore={siteData} displayStore={siteDisplay} registry={siteRegistry} />
92
97
  <SiteLabelsLayer dataStore={siteData} displayStore={siteDisplay} registry={siteRegistry} />
93
- <CellsLayer dataStore={cellData} registry={cellRegistry} displayStore={cellDisplay} />
98
+ <CellsLayer
99
+ dataStore={cellData}
100
+ registry={cellRegistry}
101
+ displayStore={cellDisplay}
102
+ siteDistanceStore={siteDistanceStore}
103
+ />
94
104
  <CellLabelsLayer dataStore={cellData} registry={cellRegistry} displayStore={cellDisplay} />
95
105
  <RepeatersLayer dataStore={repeaterData} registry={repeaterRegistry} displayStore={repeaterDisplay} />
96
106
  <RepeaterLabelsLayer dataStore={repeaterData} registry={repeaterRegistry} displayStore={repeaterDisplay} />
@@ -83,6 +83,40 @@
83
83
  </div>
84
84
  </div>
85
85
 
86
+ <!-- Auto Size -->
87
+ <div class="row align-items-center g-2 mb-3">
88
+ <div class="col-4 text-secondary fw-semibold small text-uppercase">Auto Size</div>
89
+ <div class="col-3"></div>
90
+ <div class="col-5">
91
+ <div class="form-check form-switch m-0 d-flex align-items-center justify-content-end">
92
+ <input
93
+ id="cell-autosize-toggle"
94
+ type="checkbox"
95
+ class="form-check-input"
96
+ role="switch"
97
+ bind:checked={displayStore.useAutoSize}
98
+ />
99
+ </div>
100
+ </div>
101
+ </div>
102
+
103
+ {#if displayStore.useAutoSize}
104
+ <!-- Auto Size Mode -->
105
+ <div class="row align-items-center g-2 mb-3 ps-3">
106
+ <div class="col-4 text-secondary small">Mode</div>
107
+ <div class="col-8">
108
+ <select
109
+ class="form-select form-select-sm"
110
+ bind:value={displayStore.autoSizeMode}
111
+ >
112
+ <option value="logarithmic">Logarithmic (smooth)</option>
113
+ <option value="percentage">Proportional (40%)</option>
114
+ <option value="tiered">Tiered (4 levels)</option>
115
+ </select>
116
+ </div>
117
+ </div>
118
+ {/if}
119
+
86
120
  <div class="border-top my-3"></div>
87
121
 
88
122
  <!-- Show Labels -->
@@ -9,7 +9,7 @@ import type { TechnologyBandKey } from './types';
9
9
  * Layer Grouping Presets
10
10
  * Controls visual layering strategy for overlapping sectors
11
11
  */
12
- export type LayerGroupingPreset = 'frequency' | 'technology' | 'balanced';
12
+ export type LayerGroupingPreset = 'frequency' | 'technology' | 'balanced' | 'ltePriority';
13
13
  export declare const Z_INDEX_PRESETS: Record<LayerGroupingPreset, Record<TechnologyBandKey, number>>;
14
14
  export declare const BEAMWIDTH_BOOST_PRESETS: Record<LayerGroupingPreset, Record<TechnologyBandKey, number>>;
15
15
  export declare const Z_INDEX_BY_BAND: Record<TechnologyBandKey, number>;
@@ -4,8 +4,9 @@
4
4
  import type { CellDataStore } from '../stores/cell.data.svelte';
5
5
  import type { CellRegistry } from '../stores/cell.registry.svelte';
6
6
  import type { CellDisplayStore } from '../stores/cell.display.svelte';
7
+ import type { SiteDistanceStore } from '../stores/site.distance.svelte';
7
8
  import { groupCells, getColorForGroup } from '../logic/grouping';
8
- import { generateCellArc, calculateRadiusInMeters } from '../logic/geometry';
9
+ import { generateCellArc, calculateRadiusInMeters, calculateAutoRadius } from '../logic/geometry';
9
10
  import type { TechnologyBandKey } from '../types';
10
11
  import type mapboxgl from 'mapbox-gl';
11
12
 
@@ -13,9 +14,10 @@
13
14
  dataStore: CellDataStore;
14
15
  registry: CellRegistry;
15
16
  displayStore: CellDisplayStore;
17
+ siteDistanceStore: SiteDistanceStore;
16
18
  }
17
19
 
18
- let { dataStore, registry, displayStore }: Props = $props();
20
+ let { dataStore, registry, displayStore, siteDistanceStore }: Props = $props();
19
21
 
20
22
  const mapStore = getContext<MapStore>('MAP_CONTEXT');
21
23
  let sourceId = 'cells-source';
@@ -127,6 +129,8 @@
127
129
  const _l1 = displayStore.level1;
128
130
  const _l2 = displayStore.level2;
129
131
  const _layerGrouping = displayStore.layerGrouping;
132
+ const _useAutoSize = displayStore.useAutoSize;
133
+ const _autoSizeMode = displayStore.autoSizeMode;
130
134
 
131
135
  updateLayer();
132
136
  });
@@ -150,9 +154,9 @@
150
154
 
151
155
  console.log(`[CellsLayer] Rendering.. Zoom: ${zoom.toFixed(2)}, Cells: ${dataStore.filteredCells.length}`);
152
156
 
153
- // 1. Calculate Radius for this zoom
154
- const radiusMeters = calculateRadiusInMeters(centerLat, zoom, displayStore.targetPixelSize);
155
- console.log(`[CellsLayer] Radius: ${radiusMeters.toFixed(2)}m for target ${displayStore.targetPixelSize}px`);
157
+ // 1. Calculate base radius
158
+ const baseRadiusMeters = calculateRadiusInMeters(centerLat, zoom, displayStore.targetPixelSize);
159
+ console.log(`[CellsLayer] Base radius: ${baseRadiusMeters.toFixed(2)}m for target ${displayStore.targetPixelSize}px`);
156
160
 
157
161
  // 2. Group cells (Level 1=Tech, Level 2=Band for now hardcoded)
158
162
  // In real app, this comes from a store
@@ -177,20 +181,31 @@
177
181
  const zIndexKey = `${cell.tech}_${cell.frq}` as TechnologyBandKey;
178
182
  const zIndex = displayStore.currentZIndex[zIndexKey] ?? 10;
179
183
 
180
- // 6. Calculate Scaled Radius based on Z-Index
181
- // Higher Z-index (Top layer) = Smaller radius
182
- // Lower Z-index (Bottom layer) = Larger radius
183
- // This ensures stacked cells are visible
184
+ // 6. Calculate radius with z-index scaling
184
185
  const MAX_Z = 35;
185
- const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08; // 8% size diff per layer
186
- const effectiveRadius = radiusMeters * scaleFactor;
186
+ let radiusMeters: number;
187
+
188
+ if (displayStore.useAutoSize) {
189
+ // Auto-size mode: get target radius for this site
190
+ const siteDistance = siteDistanceStore.getDistance(cell.siteId, 500);
191
+ const autoRadius = calculateAutoRadius(siteDistance, displayStore.autoSizeMode);
192
+
193
+ // Scale based on z-index for stacking visibility
194
+ // Lower z-index (background) = larger, higher z-index (foreground) = smaller
195
+ const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08; // 8% per layer
196
+ radiusMeters = autoRadius * scaleFactor;
197
+ } else {
198
+ // Manual mode: base from pixel size, then scale by z-index
199
+ const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08;
200
+ radiusMeters = baseRadiusMeters * scaleFactor;
201
+ }
187
202
 
188
203
  // 7. Apply beamwidth boost from displayStore preset
189
204
  const beamwidthBoost = displayStore.currentBeamwidthBoost[zIndexKey] || 0;
190
205
  const adjustedBeamwidth = cell.beamwidth + beamwidthBoost;
191
206
 
192
207
  // 8. Generate Arc
193
- const feature = generateCellArc(cell, effectiveRadius, zIndex, style.color, adjustedBeamwidth);
208
+ const feature = generateCellArc(cell, radiusMeters, zIndex, style.color, adjustedBeamwidth);
194
209
  features.push(feature);
195
210
  }
196
211
  }
@@ -1,10 +1,12 @@
1
1
  import type { CellDataStore } from '../stores/cell.data.svelte';
2
2
  import type { CellRegistry } from '../stores/cell.registry.svelte';
3
3
  import type { CellDisplayStore } from '../stores/cell.display.svelte';
4
+ import type { SiteDistanceStore } from '../stores/site.distance.svelte';
4
5
  interface Props {
5
6
  dataStore: CellDataStore;
6
7
  registry: CellRegistry;
7
8
  displayStore: CellDisplayStore;
9
+ siteDistanceStore: SiteDistanceStore;
8
10
  }
9
11
  declare const CellsLayer: import("svelte").Component<Props, {}, "">;
10
12
  type CellsLayer = ReturnType<typeof CellsLayer>;
@@ -1,4 +1,4 @@
1
- import type { Cell } from '../types';
1
+ import type { Cell, AutoSizeMode } from '../types';
2
2
  /**
3
3
  * Calculates the radius in meters required to achieve a target pixel size
4
4
  * at a specific latitude and zoom level.
@@ -6,6 +6,13 @@ import type { Cell } from '../types';
6
6
  * Formula: meters_per_pixel = 156543.03392 * cos(lat * PI / 180) / 2^zoom
7
7
  */
8
8
  export declare function calculateRadiusInMeters(latitude: number, zoom: number, targetPixelSize: number): number;
9
+ /**
10
+ * Calculate auto-size radius based on nearest site distance
11
+ * @param nearestSiteDistance Distance to nearest site in meters
12
+ * @param mode Sizing algorithm to use
13
+ * @returns Recommended radius in meters (base radius before z-index scaling)
14
+ */
15
+ export declare function calculateAutoRadius(nearestSiteDistance: number, mode?: AutoSizeMode): number;
9
16
  /**
10
17
  * Generates a sector arc GeoJSON feature for a cell
11
18
  */
@@ -10,6 +10,38 @@ export function calculateRadiusInMeters(latitude, zoom, targetPixelSize) {
10
10
  const metersPerPixel = (156543.03392 * Math.cos((latitude * Math.PI) / 180)) / Math.pow(2, zoom);
11
11
  return targetPixelSize * metersPerPixel;
12
12
  }
13
+ /**
14
+ * Calculate auto-size radius based on nearest site distance
15
+ * @param nearestSiteDistance Distance to nearest site in meters
16
+ * @param mode Sizing algorithm to use
17
+ * @returns Recommended radius in meters (base radius before z-index scaling)
18
+ */
19
+ export function calculateAutoRadius(nearestSiteDistance, mode = 'logarithmic') {
20
+ switch (mode) {
21
+ case 'logarithmic': {
22
+ // Logarithmic scale with clamping
23
+ // Returns BASE radius - z-index scaling will be applied on top
24
+ const base = Math.log10(Math.max(100, nearestSiteDistance)) * 30;
25
+ return Math.max(30, Math.min(base, 200));
26
+ }
27
+ case 'percentage': {
28
+ // Simple percentage of distance (20% for base, allows z-index scaling on top)
29
+ return Math.max(30, Math.min(nearestSiteDistance * 0.2, 250));
30
+ }
31
+ case 'tiered': {
32
+ // Discrete tiers for consistent sizing (base values)
33
+ if (nearestSiteDistance < 300)
34
+ return 40;
35
+ if (nearestSiteDistance < 600)
36
+ return 80;
37
+ if (nearestSiteDistance < 1200)
38
+ return 120;
39
+ return 180;
40
+ }
41
+ default:
42
+ return 80;
43
+ }
44
+ }
13
45
  /**
14
46
  * Generates a sector arc GeoJSON feature for a cell
15
47
  */
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Site Distance Calculation Logic
3
+ *
4
+ * Pure functions for computing inter-site distances
5
+ * Used for auto-sizing cells based on site density
6
+ */
7
+ import type { Cell } from '../types';
8
+ /**
9
+ * Calculate distance between two geographic points using Haversine formula
10
+ * @param loc1 [longitude, latitude] of first point
11
+ * @param loc2 [longitude, latitude] of second point
12
+ * @returns Distance in meters
13
+ */
14
+ export declare function haversineDistance(loc1: [number, number], loc2: [number, number]): number;
15
+ /**
16
+ * Group cells by site ID
17
+ * @param cells Array of cells
18
+ * @returns Map of siteId -> cells at that site
19
+ */
20
+ export declare function groupBySite(cells: Cell[]): Map<string, Cell[]>;
21
+ /**
22
+ * Extract unique site locations from cells
23
+ * @param cells Array of cells
24
+ * @returns Map of siteId -> [longitude, latitude]
25
+ */
26
+ export declare function extractSiteLocations(cells: Cell[]): Map<string, [number, number]>;
27
+ /**
28
+ * Compute nearest neighbor distance for a single site
29
+ * @param siteId Site to compute distance for
30
+ * @param siteLocation Location of the site
31
+ * @param allSiteLocations Map of all site locations
32
+ * @returns Distance to nearest neighbor in meters, or Infinity if no neighbors
33
+ */
34
+ export declare function computeNearestNeighbor(siteId: string, siteLocation: [number, number], allSiteLocations: Map<string, [number, number]>): number;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Site Distance Calculation Logic
3
+ *
4
+ * Pure functions for computing inter-site distances
5
+ * Used for auto-sizing cells based on site density
6
+ */
7
+ /**
8
+ * Calculate distance between two geographic points using Haversine formula
9
+ * @param loc1 [longitude, latitude] of first point
10
+ * @param loc2 [longitude, latitude] of second point
11
+ * @returns Distance in meters
12
+ */
13
+ export function haversineDistance(loc1, loc2) {
14
+ const [lon1, lat1] = loc1;
15
+ const [lon2, lat2] = loc2;
16
+ const R = 6371e3; // Earth radius in meters
17
+ const φ1 = (lat1 * Math.PI) / 180;
18
+ const φ2 = (lat2 * Math.PI) / 180;
19
+ const Δφ = ((lat2 - lat1) * Math.PI) / 180;
20
+ const Δλ = ((lon2 - lon1) * Math.PI) / 180;
21
+ const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
22
+ Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
23
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
24
+ return R * c; // meters
25
+ }
26
+ /**
27
+ * Group cells by site ID
28
+ * @param cells Array of cells
29
+ * @returns Map of siteId -> cells at that site
30
+ */
31
+ export function groupBySite(cells) {
32
+ const groups = new Map();
33
+ for (const cell of cells) {
34
+ if (!groups.has(cell.siteId)) {
35
+ groups.set(cell.siteId, []);
36
+ }
37
+ groups.get(cell.siteId).push(cell);
38
+ }
39
+ return groups;
40
+ }
41
+ /**
42
+ * Extract unique site locations from cells
43
+ * @param cells Array of cells
44
+ * @returns Map of siteId -> [longitude, latitude]
45
+ */
46
+ export function extractSiteLocations(cells) {
47
+ const sites = groupBySite(cells);
48
+ const locations = new Map();
49
+ for (const [siteId, siteCells] of sites) {
50
+ const first = siteCells[0];
51
+ locations.set(siteId, [first.longitude, first.latitude]);
52
+ }
53
+ return locations;
54
+ }
55
+ /**
56
+ * Compute nearest neighbor distance for a single site
57
+ * @param siteId Site to compute distance for
58
+ * @param siteLocation Location of the site
59
+ * @param allSiteLocations Map of all site locations
60
+ * @returns Distance to nearest neighbor in meters, or Infinity if no neighbors
61
+ */
62
+ export function computeNearestNeighbor(siteId, siteLocation, allSiteLocations) {
63
+ let minDist = Infinity;
64
+ for (const [otherId, otherLoc] of allSiteLocations) {
65
+ if (siteId === otherId)
66
+ continue;
67
+ const dist = haversineDistance(siteLocation, otherLoc);
68
+ minDist = Math.min(minDist, dist);
69
+ }
70
+ return minDist;
71
+ }
@@ -1,4 +1,4 @@
1
- import type { CellGroupingField } from '../types';
1
+ import type { CellGroupingField, AutoSizeMode } from '../types';
2
2
  import type { LayerGroupingPreset } from '../constants';
3
3
  export declare class CellDisplayStore {
4
4
  key: string;
@@ -7,6 +7,8 @@ export declare class CellDisplayStore {
7
7
  lineWidth: number;
8
8
  showLabels: boolean;
9
9
  layerGrouping: LayerGroupingPreset;
10
+ useAutoSize: boolean;
11
+ autoSizeMode: AutoSizeMode;
10
12
  level1: CellGroupingField;
11
13
  level2: CellGroupingField;
12
14
  currentZIndex: Record<string, number>;
@@ -8,6 +8,9 @@ export class CellDisplayStore {
8
8
  lineWidth = $state(1);
9
9
  showLabels = $state(false);
10
10
  layerGrouping = $state('frequency');
11
+ // Auto-size settings
12
+ useAutoSize = $state(false);
13
+ autoSizeMode = $state('logarithmic');
11
14
  // Grouping
12
15
  level1 = $state('tech');
13
16
  level2 = $state('fband');
@@ -35,6 +38,8 @@ export class CellDisplayStore {
35
38
  this.lineWidth = parsed.lineWidth ?? 1;
36
39
  this.showLabels = parsed.showLabels ?? false;
37
40
  this.layerGrouping = parsed.layerGrouping ?? 'frequency';
41
+ this.useAutoSize = parsed.useAutoSize ?? false;
42
+ this.autoSizeMode = parsed.autoSizeMode ?? 'logarithmic';
38
43
  this.level1 = parsed.level1 ?? 'tech';
39
44
  this.level2 = parsed.level2 ?? 'fband';
40
45
  this.labelPixelDistance = parsed.labelPixelDistance ?? 60;
@@ -57,6 +62,8 @@ export class CellDisplayStore {
57
62
  lineWidth: this.lineWidth,
58
63
  showLabels: this.showLabels,
59
64
  layerGrouping: this.layerGrouping,
65
+ useAutoSize: this.useAutoSize,
66
+ autoSizeMode: this.autoSizeMode,
60
67
  level1: this.level1,
61
68
  level2: this.level2,
62
69
  labelPixelDistance: this.labelPixelDistance,
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Site Distance Store
3
+ *
4
+ * Manages cached distances between sites for auto-sizing
5
+ * Incrementally updates when new sites are detected
6
+ */
7
+ import type { Cell } from '../types';
8
+ export declare class SiteDistanceStore {
9
+ key: string;
10
+ distances: Map<string, number>;
11
+ computedSites: Set<string>;
12
+ lastComputeTime: number;
13
+ computedCount: number;
14
+ constructor();
15
+ /**
16
+ * Incrementally update distances for new sites
17
+ * Only computes distances for sites not in cache
18
+ */
19
+ updateDistances(cells: Cell[]): void;
20
+ /**
21
+ * Update existing sites if needed (when no new sites but data might have changed)
22
+ */
23
+ private updateExistingSitesIfNeeded;
24
+ /**
25
+ * Get distance for a site, with fallback
26
+ */
27
+ getDistance(siteId: string, fallback?: number): number;
28
+ /**
29
+ * Clear all cached distances (useful for testing or data refresh)
30
+ */
31
+ clear(): void;
32
+ /**
33
+ * Persist to localStorage
34
+ */
35
+ private persist;
36
+ /**
37
+ * Load from localStorage
38
+ */
39
+ private load;
40
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Site Distance Store
3
+ *
4
+ * Manages cached distances between sites for auto-sizing
5
+ * Incrementally updates when new sites are detected
6
+ */
7
+ import { browser } from '$app/environment';
8
+ import { extractSiteLocations, computeNearestNeighbor, haversineDistance } from '../logic/site-distance';
9
+ export class SiteDistanceStore {
10
+ key = 'map-v3-site-distances';
11
+ // Cached nearest neighbor distances
12
+ distances = $state(new Map());
13
+ // Track which sites have been computed
14
+ computedSites = $state(new Set());
15
+ // Performance tracking
16
+ lastComputeTime = $state(0);
17
+ computedCount = $state(0);
18
+ constructor() {
19
+ if (browser) {
20
+ this.load();
21
+ }
22
+ }
23
+ /**
24
+ * Incrementally update distances for new sites
25
+ * Only computes distances for sites not in cache
26
+ */
27
+ updateDistances(cells) {
28
+ if (!cells || cells.length === 0)
29
+ return;
30
+ const startTime = performance.now();
31
+ const siteLocations = extractSiteLocations(cells);
32
+ // Find NEW sites (not in cache)
33
+ const newSites = [...siteLocations.keys()].filter((id) => !this.computedSites.has(id));
34
+ if (newSites.length === 0) {
35
+ // No new sites, but check if we need to update existing sites
36
+ // (in case a new site is closer than previously computed)
37
+ this.updateExistingSitesIfNeeded(siteLocations);
38
+ return;
39
+ }
40
+ console.log(`[SiteDistance] Computing distances for ${newSites.length} new sites`);
41
+ // Compute distances for NEW sites
42
+ for (const siteId of newSites) {
43
+ const location = siteLocations.get(siteId);
44
+ const nearestDist = computeNearestNeighbor(siteId, location, siteLocations);
45
+ this.distances.set(siteId, nearestDist);
46
+ this.computedSites.add(siteId);
47
+ }
48
+ // Update EXISTING sites if a new site is closer
49
+ for (const [existingId, existingLoc] of siteLocations) {
50
+ if (newSites.includes(existingId))
51
+ continue; // Skip new sites
52
+ let currentMin = this.distances.get(existingId) || Infinity;
53
+ for (const newId of newSites) {
54
+ const newLoc = siteLocations.get(newId);
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
+ }
61
+ }
62
+ const endTime = performance.now();
63
+ this.lastComputeTime = endTime - startTime;
64
+ this.computedCount = this.distances.size;
65
+ console.log(`[SiteDistance] Computed ${newSites.length} new sites in ${this.lastComputeTime.toFixed(1)}ms (total: ${this.computedCount})`);
66
+ this.persist();
67
+ }
68
+ /**
69
+ * Update existing sites if needed (when no new sites but data might have changed)
70
+ */
71
+ updateExistingSitesIfNeeded(siteLocations) {
72
+ // Check if all computed sites still exist in current data
73
+ const currentSiteIds = new Set(siteLocations.keys());
74
+ let removed = 0;
75
+ for (const computedId of this.computedSites) {
76
+ if (!currentSiteIds.has(computedId)) {
77
+ this.distances.delete(computedId);
78
+ this.computedSites.delete(computedId);
79
+ removed++;
80
+ }
81
+ }
82
+ if (removed > 0) {
83
+ console.log(`[SiteDistance] Removed ${removed} stale sites from cache`);
84
+ this.persist();
85
+ }
86
+ }
87
+ /**
88
+ * Get distance for a site, with fallback
89
+ */
90
+ getDistance(siteId, fallback = 500) {
91
+ return this.distances.get(siteId) ?? fallback;
92
+ }
93
+ /**
94
+ * Clear all cached distances (useful for testing or data refresh)
95
+ */
96
+ clear() {
97
+ this.distances.clear();
98
+ this.computedSites.clear();
99
+ this.lastComputeTime = 0;
100
+ this.computedCount = 0;
101
+ if (browser) {
102
+ localStorage.removeItem(this.key);
103
+ }
104
+ console.log('[SiteDistance] Cache cleared');
105
+ }
106
+ /**
107
+ * Persist to localStorage
108
+ */
109
+ persist() {
110
+ if (!browser)
111
+ return;
112
+ try {
113
+ const data = {
114
+ distances: Array.from(this.distances.entries()),
115
+ computedSites: Array.from(this.computedSites),
116
+ lastComputeTime: this.lastComputeTime,
117
+ computedCount: this.computedCount
118
+ };
119
+ localStorage.setItem(this.key, JSON.stringify(data));
120
+ }
121
+ catch (e) {
122
+ console.error('[SiteDistance] Failed to persist cache', e);
123
+ }
124
+ }
125
+ /**
126
+ * Load from localStorage
127
+ */
128
+ load() {
129
+ if (!browser)
130
+ return;
131
+ try {
132
+ const saved = localStorage.getItem(this.key);
133
+ if (saved) {
134
+ const data = JSON.parse(saved);
135
+ this.distances = new Map(data.distances || []);
136
+ this.computedSites = new Set(data.computedSites || []);
137
+ this.lastComputeTime = data.lastComputeTime || 0;
138
+ this.computedCount = data.computedCount || 0;
139
+ console.log(`[SiteDistance] Loaded ${this.computedCount} cached distances`);
140
+ }
141
+ }
142
+ catch (e) {
143
+ console.error('[SiteDistance] Failed to load cache', e);
144
+ }
145
+ }
146
+ }
@@ -60,3 +60,15 @@ export type TechnologyBandKey = string;
60
60
  * Grouping fields for Tree View
61
61
  */
62
62
  export type CellGroupingField = 'tech' | 'fband' | 'frq' | 'status' | 'siteId' | 'none';
63
+ /**
64
+ * Auto-size calculation modes
65
+ */
66
+ export type AutoSizeMode = 'logarithmic' | 'percentage' | 'tiered';
67
+ /**
68
+ * Site distance data for auto-sizing
69
+ */
70
+ export interface SiteDistanceData {
71
+ siteId: string;
72
+ nearestDistance: number;
73
+ computedAt: number;
74
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.90",
3
+ "version": "0.0.91",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",