@smartnet360/svelte-components 0.0.91 → 0.0.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,6 @@
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';
9
8
  import FeatureSettingsControl from '../core/controls/FeatureSettingsControl.svelte';
10
9
  import CellFilterControl from '../features/cells/components/CellFilterControl.svelte';
11
10
  import { createRepeaterDataStore } from '../features/repeaters/stores/repeater.data.svelte';
@@ -31,10 +30,9 @@
31
30
  let { accessToken }: Props = $props();
32
31
 
33
32
  // Initialize stores
34
- const cellData = createCellDataStore();
35
33
  const cellRegistry = createCellRegistry('demo-map');
36
34
  const cellDisplay = new CellDisplayStore();
37
- const siteDistanceStore = new SiteDistanceStore();
35
+ const cellData = createCellDataStore(cellDisplay);
38
36
 
39
37
  const siteData = createSiteDataStore(cellData);
40
38
  const siteRegistry = createSiteRegistry('demo-map');
@@ -49,9 +47,6 @@
49
47
  // Need to cast or map if types slightly differ, but they should match
50
48
  cellData.setCells(demoCells as any);
51
49
  repeaterData.setRepeaters(demoRepeaters);
52
-
53
- // Compute site distances
54
- siteDistanceStore.updateDistances(demoCells as any);
55
50
  });
56
51
  </script>
57
52
 
@@ -99,7 +94,6 @@
99
94
  dataStore={cellData}
100
95
  registry={cellRegistry}
101
96
  displayStore={cellDisplay}
102
- siteDistanceStore={siteDistanceStore}
103
97
  />
104
98
  <CellLabelsLayer dataStore={cellData} registry={cellRegistry} displayStore={cellDisplay} />
105
99
  <RepeatersLayer dataStore={repeaterData} registry={repeaterRegistry} displayStore={repeaterDisplay} />
@@ -115,6 +115,44 @@
115
115
  </select>
116
116
  </div>
117
117
  </div>
118
+
119
+ <!-- Neighbor Count -->
120
+ <div class="row align-items-center g-2 mb-3 ps-3">
121
+ <div class="col-4 text-secondary small">Neighbors</div>
122
+ <div class="col-3 text-end">
123
+ <span class="badge bg-white text-muted border">{displayStore.autoSizeNeighborCount}</span>
124
+ </div>
125
+ <div class="col-5">
126
+ <input
127
+ type="range"
128
+ class="form-range w-100"
129
+ min="1"
130
+ max="10"
131
+ step="1"
132
+ bind:value={displayStore.autoSizeNeighborCount}
133
+ title="Number of nearest neighbors to average for density calculation"
134
+ />
135
+ </div>
136
+ </div>
137
+
138
+ <!-- Size Multiplier -->
139
+ <div class="row align-items-center g-2 mb-3 ps-3">
140
+ <div class="col-4 text-secondary small">Size Scale</div>
141
+ <div class="col-3 text-end">
142
+ <span class="badge bg-white text-muted border">{displayStore.autoSizeMultiplier.toFixed(1)}x</span>
143
+ </div>
144
+ <div class="col-5">
145
+ <input
146
+ type="range"
147
+ class="form-range w-100"
148
+ min="0.3"
149
+ max="2.0"
150
+ step="0.1"
151
+ bind:value={displayStore.autoSizeMultiplier}
152
+ title="Scale all auto-sized cells up or down"
153
+ />
154
+ </div>
155
+ </div>
118
156
  {/if}
119
157
 
120
158
  <div class="border-top my-3"></div>
@@ -4,7 +4,6 @@
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';
8
7
  import { groupCells, getColorForGroup } from '../logic/grouping';
9
8
  import { generateCellArc, calculateRadiusInMeters, calculateAutoRadius } from '../logic/geometry';
10
9
  import type { TechnologyBandKey } from '../types';
@@ -14,10 +13,9 @@
14
13
  dataStore: CellDataStore;
15
14
  registry: CellRegistry;
16
15
  displayStore: CellDisplayStore;
17
- siteDistanceStore: SiteDistanceStore;
18
16
  }
19
17
 
20
- let { dataStore, registry, displayStore, siteDistanceStore }: Props = $props();
18
+ let { dataStore, registry, displayStore }: Props = $props();
21
19
 
22
20
  const mapStore = getContext<MapStore>('MAP_CONTEXT');
23
21
  let sourceId = 'cells-source';
@@ -104,8 +102,13 @@
104
102
 
105
103
  // Events for updating
106
104
  map.on('style.load', addLayers);
107
- map.on('moveend', updateLayer);
108
- map.on('zoomend', updateLayer);
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
+ }
109
112
 
110
113
  // Cleanup
111
114
  return () => {
@@ -131,6 +134,8 @@
131
134
  const _layerGrouping = displayStore.layerGrouping;
132
135
  const _useAutoSize = displayStore.useAutoSize;
133
136
  const _autoSizeMode = displayStore.autoSizeMode;
137
+ const _autoSizeNeighborCount = displayStore.autoSizeNeighborCount;
138
+ const _autoSizeMultiplier = displayStore.autoSizeMultiplier;
134
139
 
135
140
  updateLayer();
136
141
  });
@@ -146,20 +151,22 @@
146
151
  }
147
152
 
148
153
  function renderCells(map: mapboxgl.Map) {
149
- const bounds = map.getBounds();
150
- if (!bounds) return;
151
-
152
- const zoom = map.getZoom();
153
- const centerLat = map.getCenter().lat;
154
+ const useAutoSize = displayStore.useAutoSize;
154
155
 
155
- console.log(`[CellsLayer] Rendering.. Zoom: ${zoom.toFixed(2)}, Cells: ${dataStore.filteredCells.length}`);
156
+ // Only need bounds checking and zoom calculations for manual mode
157
+ const bounds = useAutoSize ? null : map.getBounds();
158
+ const zoom = useAutoSize ? 0 : map.getZoom();
159
+ const centerLat = useAutoSize ? 0 : map.getCenter().lat;
160
+
161
+ console.log(`[CellsLayer] Rendering ${dataStore.filteredCells.length} cells (auto-size: ${useAutoSize})`);
156
162
 
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`);
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
+ }
160
168
 
161
- // 2. Group cells (Level 1=Tech, Level 2=Band for now hardcoded)
162
- // In real app, this comes from a store
169
+ // 2. Group cells
163
170
  const groups = groupCells(dataStore.filteredCells, displayStore.level1, displayStore.level2);
164
171
  console.log(`[CellsLayer] Groups: ${groups.size}`);
165
172
 
@@ -175,25 +182,32 @@
175
182
  if (!style.visible) continue;
176
183
 
177
184
  for (const cell of cells) {
178
- // 4. BBox Filter (Simple point check)
179
- if (bounds.contains([cell.longitude, cell.latitude])) {
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) {
180
189
  // 5. Z-Index Lookup
181
190
  const zIndexKey = `${cell.tech}_${cell.frq}` as TechnologyBandKey;
182
191
  const zIndex = displayStore.currentZIndex[zIndexKey] ?? 10;
183
192
 
184
- // 6. Calculate radius with z-index scaling
193
+ // 6. Calculate radius
194
+ // Auto-size: Fixed meter-based size from site density
195
+ // Manual: Pixel-based size that scales with zoom
185
196
  const MAX_Z = 35;
186
197
  let radiusMeters: number;
187
198
 
188
199
  if (displayStore.useAutoSize) {
189
200
  // Auto-size mode: get target radius for this site
190
- const siteDistance = siteDistanceStore.getDistance(cell.siteId, 500);
201
+ const siteDistance = dataStore.siteDistanceStore.getDistance(cell.siteId, 500);
191
202
  const autoRadius = calculateAutoRadius(siteDistance, displayStore.autoSizeMode);
192
203
 
204
+ // Apply user's multiplier to scale the result
205
+ const adjustedAutoRadius = autoRadius * displayStore.autoSizeMultiplier;
206
+
193
207
  // Scale based on z-index for stacking visibility
194
208
  // Lower z-index (background) = larger, higher z-index (foreground) = smaller
195
209
  const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08; // 8% per layer
196
- radiusMeters = autoRadius * scaleFactor;
210
+ radiusMeters = adjustedAutoRadius * scaleFactor;
197
211
  } else {
198
212
  // Manual mode: base from pixel size, then scale by z-index
199
213
  const scaleFactor = 1 + Math.max(0, MAX_Z - zIndex) * 0.08;
@@ -1,12 +1,10 @@
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';
5
4
  interface Props {
6
5
  dataStore: CellDataStore;
7
6
  registry: CellRegistry;
8
7
  displayStore: CellDisplayStore;
9
- siteDistanceStore: SiteDistanceStore;
10
8
  }
11
9
  declare const CellsLayer: import("svelte").Component<Props, {}, "">;
12
10
  type CellsLayer = ReturnType<typeof CellsLayer>;
@@ -26,9 +26,11 @@ export declare function groupBySite(cells: Cell[]): Map<string, Cell[]>;
26
26
  export declare function extractSiteLocations(cells: Cell[]): Map<string, [number, number]>;
27
27
  /**
28
28
  * Compute nearest neighbor distance for a single site
29
+ * Can average multiple neighbors for better density estimation
29
30
  * @param siteId Site to compute distance for
30
31
  * @param siteLocation Location of the site
31
32
  * @param allSiteLocations Map of all site locations
32
- * @returns Distance to nearest neighbor in meters, or Infinity if no neighbors
33
+ * @param neighborCount Number of closest neighbors to average (default: 5)
34
+ * @returns Average distance to N nearest neighbors in meters, or Infinity if no neighbors
33
35
  */
34
- export declare function computeNearestNeighbor(siteId: string, siteLocation: [number, number], allSiteLocations: Map<string, [number, number]>): number;
36
+ export declare function computeNearestNeighbor(siteId: string, siteLocation: [number, number], allSiteLocations: Map<string, [number, number]>, neighborCount?: number): number;
@@ -54,18 +54,26 @@ export function extractSiteLocations(cells) {
54
54
  }
55
55
  /**
56
56
  * Compute nearest neighbor distance for a single site
57
+ * Can average multiple neighbors for better density estimation
57
58
  * @param siteId Site to compute distance for
58
59
  * @param siteLocation Location of the site
59
60
  * @param allSiteLocations Map of all site locations
60
- * @returns Distance to nearest neighbor in meters, or Infinity if no neighbors
61
+ * @param neighborCount Number of closest neighbors to average (default: 5)
62
+ * @returns Average distance to N nearest neighbors in meters, or Infinity if no neighbors
61
63
  */
62
- export function computeNearestNeighbor(siteId, siteLocation, allSiteLocations) {
63
- let minDist = Infinity;
64
+ export function computeNearestNeighbor(siteId, siteLocation, allSiteLocations, neighborCount = 5) {
65
+ const distances = [];
64
66
  for (const [otherId, otherLoc] of allSiteLocations) {
65
67
  if (siteId === otherId)
66
68
  continue;
67
69
  const dist = haversineDistance(siteLocation, otherLoc);
68
- minDist = Math.min(minDist, dist);
70
+ distances.push(dist);
69
71
  }
70
- return minDist;
72
+ if (distances.length === 0)
73
+ return Infinity;
74
+ // Sort distances and take N closest
75
+ distances.sort((a, b) => a - b);
76
+ const closestN = distances.slice(0, Math.min(neighborCount, distances.length));
77
+ // Return average of closest neighbors
78
+ return closestN.reduce((sum, d) => sum + d, 0) / closestN.length;
71
79
  }
@@ -1,9 +1,17 @@
1
1
  import type { Cell } from '../types';
2
+ import { SiteDistanceStore } from './site.distance.svelte';
3
+ import type { CellDisplayStore } from './cell.display.svelte';
2
4
  export declare class CellDataStore {
3
5
  rawCells: Cell[];
4
6
  filterOnAir: boolean;
5
- constructor();
7
+ siteDistanceStore: SiteDistanceStore;
8
+ displayStore?: CellDisplayStore;
9
+ constructor(displayStore?: CellDisplayStore);
6
10
  setCells(cells: Cell[]): void;
7
11
  get filteredCells(): Cell[];
8
12
  }
9
- export declare function createCellDataStore(): CellDataStore;
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;
@@ -1,9 +1,21 @@
1
+ import { SiteDistanceStore } from './site.distance.svelte';
1
2
  export class CellDataStore {
2
3
  rawCells = $state([]);
3
4
  filterOnAir = $state(false);
4
- constructor() { }
5
+ // Internal site distance store for auto-sizing
6
+ siteDistanceStore;
7
+ // Reference to display store for auto-size settings
8
+ displayStore;
9
+ constructor(displayStore) {
10
+ this.siteDistanceStore = new SiteDistanceStore();
11
+ this.displayStore = displayStore;
12
+ }
5
13
  setCells(cells) {
6
14
  this.rawCells = cells;
15
+ // 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);
7
19
  }
8
20
  get filteredCells() {
9
21
  if (!this.filterOnAir)
@@ -11,6 +23,10 @@ export class CellDataStore {
11
23
  return this.rawCells.filter(c => c.status === 'On_Air');
12
24
  }
13
25
  }
14
- export function createCellDataStore() {
15
- return new CellDataStore();
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);
16
32
  }
@@ -9,6 +9,8 @@ export declare class CellDisplayStore {
9
9
  layerGrouping: LayerGroupingPreset;
10
10
  useAutoSize: boolean;
11
11
  autoSizeMode: AutoSizeMode;
12
+ autoSizeNeighborCount: number;
13
+ autoSizeMultiplier: number;
12
14
  level1: CellGroupingField;
13
15
  level2: CellGroupingField;
14
16
  currentZIndex: Record<string, number>;
@@ -11,6 +11,8 @@ export class CellDisplayStore {
11
11
  // Auto-size settings
12
12
  useAutoSize = $state(false);
13
13
  autoSizeMode = $state('logarithmic');
14
+ autoSizeNeighborCount = $state(5); // Number of neighbors to average for density
15
+ autoSizeMultiplier = $state(1.0); // Scale factor for auto-size results (0.3 - 2.0)
14
16
  // Grouping
15
17
  level1 = $state('tech');
16
18
  level2 = $state('fband');
@@ -40,6 +42,8 @@ export class CellDisplayStore {
40
42
  this.layerGrouping = parsed.layerGrouping ?? 'frequency';
41
43
  this.useAutoSize = parsed.useAutoSize ?? false;
42
44
  this.autoSizeMode = parsed.autoSizeMode ?? 'logarithmic';
45
+ this.autoSizeNeighborCount = parsed.autoSizeNeighborCount ?? 5;
46
+ this.autoSizeMultiplier = parsed.autoSizeMultiplier ?? 1.0;
43
47
  this.level1 = parsed.level1 ?? 'tech';
44
48
  this.level2 = parsed.level2 ?? 'fband';
45
49
  this.labelPixelDistance = parsed.labelPixelDistance ?? 60;
@@ -64,6 +68,8 @@ export class CellDisplayStore {
64
68
  layerGrouping: this.layerGrouping,
65
69
  useAutoSize: this.useAutoSize,
66
70
  autoSizeMode: this.autoSizeMode,
71
+ autoSizeNeighborCount: this.autoSizeNeighborCount,
72
+ autoSizeMultiplier: this.autoSizeMultiplier,
67
73
  level1: this.level1,
68
74
  level2: this.level2,
69
75
  labelPixelDistance: this.labelPixelDistance,
@@ -15,8 +15,10 @@ export declare class SiteDistanceStore {
15
15
  /**
16
16
  * Incrementally update distances for new sites
17
17
  * Only computes distances for sites not in cache
18
+ * @param cells Array of cells to compute distances for
19
+ * @param neighborCount Number of neighbors to average for density calculation
18
20
  */
19
- updateDistances(cells: Cell[]): void;
21
+ updateDistances(cells: Cell[], neighborCount?: number): void;
20
22
  /**
21
23
  * Update existing sites if needed (when no new sites but data might have changed)
22
24
  */
@@ -23,8 +23,10 @@ export class SiteDistanceStore {
23
23
  /**
24
24
  * Incrementally update distances for new sites
25
25
  * Only computes distances for sites not in cache
26
+ * @param cells Array of cells to compute distances for
27
+ * @param neighborCount Number of neighbors to average for density calculation
26
28
  */
27
- updateDistances(cells) {
29
+ updateDistances(cells, neighborCount = 5) {
28
30
  if (!cells || cells.length === 0)
29
31
  return;
30
32
  const startTime = performance.now();
@@ -37,11 +39,11 @@ export class SiteDistanceStore {
37
39
  this.updateExistingSitesIfNeeded(siteLocations);
38
40
  return;
39
41
  }
40
- console.log(`[SiteDistance] Computing distances for ${newSites.length} new sites`);
42
+ console.log(`[SiteDistance] Computing distances for ${newSites.length} new sites (${neighborCount} neighbors)`);
41
43
  // Compute distances for NEW sites
42
44
  for (const siteId of newSites) {
43
45
  const location = siteLocations.get(siteId);
44
- const nearestDist = computeNearestNeighbor(siteId, location, siteLocations);
46
+ const nearestDist = computeNearestNeighbor(siteId, location, siteLocations, neighborCount);
45
47
  this.distances.set(siteId, nearestDist);
46
48
  this.computedSites.add(siteId);
47
49
  }
@@ -49,15 +51,9 @@ export class SiteDistanceStore {
49
51
  for (const [existingId, existingLoc] of siteLocations) {
50
52
  if (newSites.includes(existingId))
51
53
  continue; // Skip new sites
52
- 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
- }
54
+ // Recalculate with new sites present
55
+ const updatedDist = computeNearestNeighbor(existingId, existingLoc, siteLocations, neighborCount);
56
+ this.distances.set(existingId, updatedDist);
61
57
  }
62
58
  const endTime = performance.now();
63
59
  this.lastComputeTime = endTime - startTime;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.91",
3
+ "version": "0.0.93",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",