@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.
@@ -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: 100 sites × 3 sectors × 12 tech-bands = 3,600 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
- // Grid parameters for distributing sites
12
- const NUM_SITES = 1700;
13
- const GRID_SIZE = 10; // 10×10 grid
14
- const LAT_SPACING = 0.01; // ~1.1 km spacing
15
- const LNG_SPACING = 0.015; // ~1.1 km spacing (adjusted for longitude)
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: 100 sites × 3 sectors × 12 tech-bands = 3,600 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
- // Generate sites in a grid pattern
58
- for (let siteIndex = 0; siteIndex < NUM_SITES; siteIndex++) {
59
- const row = Math.floor(siteIndex / GRID_SIZE);
60
- const col = siteIndex % GRID_SIZE;
61
- // Calculate site position (centered grid around base location)
62
- const siteLat = BASE_LAT + (row - GRID_SIZE / 2) * LAT_SPACING;
63
- const siteLng = BASE_LNG + (col - GRID_SIZE / 2) * LNG_SPACING;
64
- const siteId = `DEMO-SITE-${String(siteIndex + 1).padStart(3, '0')}`;
65
- // Generate 3 sectors per site
66
- AZIMUTHS.forEach((azimuth, sectorIndex) => {
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: siteIndex + 1,
206
+ siteNumber: actualSiteIndex,
121
207
  sector: sectorIndex + 1,
122
208
  techBandKey: `${techBand.tech}_${techBand.band}`,
123
- gridPosition: { row, col }
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">Classic (frequency-priority)</option>
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
- // 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
- }
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 useAutoSize = displayStore.useAutoSize;
155
-
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;
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 ${dataStore.filteredCells.length} cells (auto-size: ${useAutoSize})`);
153
+ console.log(`[CellsLayer] Rendering.. Zoom: ${zoom.toFixed(2)}, Cells: ${dataStore.filteredCells.length}`);
162
154
 
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
- }
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 (skip for auto-size - Mapbox handles culling efficiently)
186
- const inView = useAutoSize ? true : (bounds?.contains([cell.longitude, cell.latitude]) ?? false);
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 = adjustedAutoRadius * scaleFactor;
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
- * @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
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]>, neighborCount?: 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
- * @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
60
+ * @returns Distance to nearest neighbor in meters, or Infinity if no neighbors
63
61
  */
64
- export function computeNearestNeighbor(siteId, siteLocation, allSiteLocations, neighborCount = 5) {
65
- const distances = [];
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
- distances.push(dist);
68
+ minDist = Math.min(minDist, dist);
71
69
  }
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;
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
- displayStore?: CellDisplayStore;
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
- // Reference to display store for auto-size settings
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
- // Use neighborCount from displayStore if available
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
- * 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);
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
- * Incrementally update distances for new sites
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
16
+ * Update distances for all sites in current dataset
17
+ * Always recomputes to ensure accuracy
20
18
  */
21
- updateDistances(cells: Cell[], neighborCount?: number): void;
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
- * Incrementally update distances for new sites
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
24
+ * Update distances for all sites in current dataset
25
+ * Always recomputes to ensure accuracy
28
26
  */
29
- updateDistances(cells, neighborCount = 5) {
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
- // Find NEW sites (not in cache)
35
- const newSites = [...siteLocations.keys()].filter((id) => !this.computedSites.has(id));
36
- if (newSites.length === 0) {
37
- // No new sites, but check if we need to update existing sites
38
- // (in case a new site is closer than previously computed)
39
- this.updateExistingSitesIfNeeded(siteLocations);
40
- return;
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 ${newSites.length} new sites in ${this.lastComputeTime.toFixed(1)}ms (total: ${this.computedCount})`);
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
  */
@@ -139,6 +139,7 @@
139
139
  <option value="siteName">Name</option>
140
140
  <option value="provider">Provider</option>
141
141
  <option value="cellCount">Cell Count</option>
142
+ <option value="nearestSiteDistance">Nearest Site Distance</option>
142
143
  </select>
143
144
  </div>
144
145
  </div>
@@ -116,7 +116,8 @@
116
116
  properties: {
117
117
  siteId: site.siteId,
118
118
  name: site.siteName,
119
- color: color
119
+ color: color,
120
+ nearestSiteDistance: site.nearestSiteDistance
120
121
  }
121
122
  });
122
123
  }
@@ -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 {
@@ -9,4 +9,5 @@ export interface Site {
9
9
  level1: string;
10
10
  level2: string;
11
11
  cellCount: number;
12
+ nearestSiteDistance?: number;
12
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.93",
3
+ "version": "0.0.95",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",