@smartnet360/svelte-components 0.0.105 → 0.0.107

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.
@@ -1,246 +1,23 @@
1
1
  /**
2
- * Demo Cell Data
2
+ * Demo Cell Data - Map V3
3
3
  *
4
- * 100 sites across San Francisco Bay Area
5
- * Each site has 3 sectors (azimuths: 0°, 120°, 240°)
6
- * Each sector has 12 cells (all tech-band combinations)
7
- * Total: 100 sites × 3 sectors × 12 tech-bands = 3,600 cells
8
- */
9
- const BASE_LAT = 47.4979;
10
- const BASE_LNG = 19.0402;
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
- }
71
- // Standard beamwidth for sectors
72
- const BEAMWIDTH = 65;
73
- // Cell tech-band definitions with proper fband format
74
- const TECH_BANDS = [
75
- // 2G bands
76
- { tech: '2G', band: '900', fband: 'GSM900' },
77
- { tech: '2G', band: '1800', fband: 'GSM1800' },
78
- // 4G bands
79
- { tech: '4G', band: '700', fband: 'LTE700' },
80
- { tech: '4G', band: '800', fband: 'LTE800' },
81
- { tech: '4G', band: '900', fband: 'LTE900' },
82
- { tech: '4G', band: '1800', fband: 'LTE1800' },
83
- { tech: '4G', band: '2100', fband: 'LTE2100' },
84
- { tech: '4G', band: '2600', fband: 'LTE2600' },
85
- // 5G bands
86
- { tech: '5G', band: '700', fband: '5G-700' },
87
- { tech: '5G', band: '2100', fband: '5G-2100' },
88
- { tech: '5G', band: '3500', fband: '5G-3500' }
89
- ];
90
- // Three sector azimuths with sector numbers
91
- // Sector 1 = 0°, Sector 2 = 120°, Sector 3 = 240°
92
- const SECTORS = [
93
- { azimuth: 0, sectorNum: 1 },
94
- { azimuth: 120, sectorNum: 2 },
95
- { azimuth: 240, sectorNum: 3 }
96
- ];
97
- // Status rotation for variety
98
- const STATUSES = [
99
- 'On_Air',
100
- 'On_Air',
101
- 'On_Air',
102
- 'On_Air',
103
- 'On_Air_UNDER_CONSTRUCTION',
104
- 'On_Air_Locked',
105
- 'RF_Plan_Ready',
106
- 'RF_Plan_Ready',
107
- 'Re-Planned_RF_Plan_Ready',
108
- 'Tavlati_RF_Plan_Ready',
109
- 'On_Air',
110
- 'On_Air'
111
- ];
112
- /**
113
- * Generate 7-digit cellName from site and sector and band index
114
- * Format: SSSS S BB
115
- * SSSS = 4-digit site ID
116
- * S = 1-digit sector number (1, 2, or 3)
117
- * BB = 2-digit band/cell index (41-52 for 11 tech-bands)
118
- */
119
- function generateCellName(siteNum, sectorNum, bandIndex) {
120
- const siteId = String(siteNum + 1000).padStart(4, '0'); // Start sites from 1000
121
- const cellSuffix = String(41 + bandIndex).padStart(2, '0'); // Bands: 41-52
122
- return `${siteId}${sectorNum}${cellSuffix}`;
123
- }
124
- /**
125
- * Generate demo cells with varied density patterns in circular distribution
126
- * Creates density zones radiating from center with random placement
127
- */
128
- export const demoCells = [];
129
- let cellCounter = 1;
130
- let actualSiteIndex = 0;
131
- // Track used positions to maintain minimum spacing
132
- const usedPositions = [];
133
- /**
134
- * Check if position is too close to existing sites
135
- */
136
- function isTooClose(lat, lng, minSpacing) {
137
- for (const pos of usedPositions) {
138
- const distance = Math.sqrt(Math.pow(lat - pos.lat, 2) + Math.pow(lng - pos.lng, 2));
139
- const requiredSpacing = (minSpacing + pos.minSpacing) / 2;
140
- if (distance < requiredSpacing) {
141
- return true;
142
- }
143
- }
144
- return false;
145
- }
146
- // Generate sites in a circular pattern with density-based placement
147
- for (let attempt = 0; attempt < NUM_SITES * 3 && actualSiteIndex < NUM_SITES; attempt++) {
148
- // Generate random point in circle
149
- const { lat, lng, normalizedRadius } = generateRandomPointInCircle();
150
- // Get density zone for this radius
151
- const zone = getDensityZone(normalizedRadius);
152
- // Random spacing within zone range
153
- const minSpacing = zone.minSpacing + Math.random() * (zone.maxSpacing - zone.minSpacing);
154
- // Check if too close to existing sites
155
- if (isTooClose(lat, lng, minSpacing)) {
156
- continue; // Try another position
157
- }
158
- // Add random jitter for natural variation
159
- const jitterAmount = minSpacing * 0.3; // 30% of spacing
160
- const siteLat = addJitter(lat, jitterAmount);
161
- const siteLng = addJitter(lng, jitterAmount);
162
- // Record position
163
- usedPositions.push({ lat: siteLat, lng: siteLng, minSpacing });
164
- // 4-digit site ID (starting from 1000)
165
- const siteNum = actualSiteIndex;
166
- const siteId = String(siteNum + 1000).padStart(4, '0');
167
- actualSiteIndex++;
168
- // Generate 3 sectors per site (with some random 1 or 2 sector sites)
169
- const numSectors = Math.random() < 0.1 ? (Math.random() < 0.5 ? 1 : 2) : 3; // 10% chance of 1-2 sectors
170
- const sectorsToGenerate = SECTORS.slice(0, numSectors);
171
- sectorsToGenerate.forEach((sector) => {
172
- // Generate 11 tech-bands per sector (indexes 0-10)
173
- TECH_BANDS.forEach((techBand, bandIndex) => {
174
- // Generate 7-digit cellName: SSSS + S + BB (e.g., "1000141")
175
- const cellName = generateCellName(siteNum, sector.sectorNum, bandIndex);
176
- const status = STATUSES[bandIndex];
177
- demoCells.push({
178
- // Core properties
179
- id: cellName,
180
- txId: cellName,
181
- cellID: cellName,
182
- cellID2G: techBand.tech === '2G' ? cellName : '',
183
- cellName: cellName,
184
- siteId: siteId,
185
- tech: techBand.tech,
186
- fband: techBand.fband,
187
- frq: techBand.band,
188
- type: 'MACRO',
189
- status: status,
190
- onAirDate: '2024-01-15',
191
- // 2G specific
192
- bcch: techBand.tech === '2G' ? 100 + bandIndex : 0,
193
- ctrlid: techBand.tech === '2G' ? `CTRL-${cellName}` : '',
194
- // 4G specific
195
- dlEarfn: techBand.tech === '4G' ? 6200 + bandIndex * 100 : 0,
196
- // Physical properties
197
- antenna: 'DEMO-ANTENNA-MODEL',
198
- azimuth: sector.azimuth,
199
- height: 30, // 30 meters antenna height
200
- electricalTilt: '3',
201
- beamwidth: BEAMWIDTH,
202
- latitude: siteLat,
203
- longitude: siteLng,
204
- dx: 0,
205
- dy: 0,
206
- siteLatitude: siteLat,
207
- siteLongitude: siteLng,
208
- // Planning
209
- comment: `Demo ${techBand.tech} ${techBand.band} cell at azimuth ${sector.azimuth}°`,
210
- planner: 'Demo User',
211
- // Atoll properties
212
- atollETP: 43.0,
213
- atollPW: 20.0,
214
- atollRS: 500.0 + (techBand.band === '700' ? 200 : 0), // Lower freq = longer range
215
- atollBW: parseFloat(techBand.band) / 100, // Simplified bandwidth
216
- // Network properties
217
- cellId3: `${cellName}-3G`,
218
- nwtP1: 20,
219
- nwtP2: 40,
220
- pci1: (cellCounter % 504), // Physical Cell ID for LTE
221
- nwtRS: 450.0,
222
- nwtBW: 10.0,
223
- // Other
224
- other: {
225
- demoCell: true,
226
- siteNumber: actualSiteIndex,
227
- sector: sector.sectorNum,
228
- techBandKey: `${techBand.tech}_${techBand.band}`,
229
- radius: normalizedRadius,
230
- densityZone: zone.name
231
- },
232
- customSubgroup: `Sector-${sector.sectorNum}`
233
- });
234
- cellCounter++;
235
- });
236
- });
237
- }
238
- // Summary of generated data structure
239
- console.log(`[Demo Data] Generated ${demoCells.length} cells across ${actualSiteIndex} sites`);
240
- console.log(`[Demo Data] CellName format: 7-digit numeric (e.g., "1000141")`);
241
- console.log(`[Demo Data] Structure: SSSS (site) + S (sector 1-3) + BB (band 41-51)`);
242
- console.log(`[Demo Data] Site ID range: 1000-${1000 + actualSiteIndex - 1}`);
243
- console.log(`[Demo Data] Example Site 1000:`);
244
- console.log(` - Sector 1 (0°): 1000141-1000151 (11 bands)`);
245
- console.log(` - Sector 2 (120°): 1000241-1000251 (11 bands)`);
246
- console.log(` - Sector 3 (240°): 1000341-1000351 (11 bands)`);
4
+ * Uses the shared cell generator to create demo data.
5
+ * Default: 2000 sites, ~60k cells across Budapest area.
6
+ *
7
+ * Structure:
8
+ * - CellName format: 7-digit numeric (e.g., "1000141")
9
+ * - Format: SSSS (site) + S (sector 1-3) + BB (band 41-51)
10
+ * - Site ID range: 1000-2999
11
+ * - Example Site 1000:
12
+ * - Sector 1 (0°): 1000141-1000151 (11 bands)
13
+ * - Sector 2 (120°): 1000241-1000251 (11 bands)
14
+ * - Sector 3 (240°): 1000341-1000351 (11 bands)
15
+ */
16
+ import { generateCellsFromPreset, getGeneratorInfo } from '../../shared/demo';
17
+ // Generate default demo cells (xlarge preset = 2000 sites, ~60k cells)
18
+ // Using seed 42 for reproducible results
19
+ export const demoCells = generateCellsFromPreset('xlarge', 42);
20
+ // Log summary of generated data
21
+ const info = getGeneratorInfo(demoCells);
22
+ console.log(`[Demo Data] Generated ${info.totalCells} cells across ${info.totalSites} sites`);
23
+ console.log(`[Demo Data] Tech breakdown:`, info.techBreakdown);
@@ -1,65 +1,14 @@
1
1
  /**
2
2
  * Cell Feature - Type Definitions
3
3
  *
4
- * Core interfaces and types for cellular network visualization
4
+ * Core interfaces and types for cellular network visualization.
5
+ * Base Cell type is imported from shared module for consistency.
5
6
  */
6
- /**
7
- * Cell data model - represents a radio cell/sector
8
- */
9
- export interface Cell {
10
- id: string;
11
- txId: string;
12
- cellID: string;
13
- cellID2G: string;
14
- cellName: string;
15
- siteId: string;
16
- tech: string;
17
- fband: string;
18
- frq: string;
19
- type: string;
20
- status: string;
21
- onAirDate: string;
22
- bcch: number;
23
- ctrlid: string;
24
- dlEarfn: number;
25
- antenna: string;
26
- azimuth: number;
27
- height: number;
28
- electricalTilt: string;
29
- beamwidth: number;
30
- latitude: number;
31
- longitude: number;
32
- dx: number;
33
- dy: number;
34
- siteLatitude: number;
35
- siteLongitude: number;
36
- comment: string;
37
- planner: string;
38
- atollETP: number;
39
- atollPW: number;
40
- atollRS: number;
41
- atollBW: number;
42
- cellId3: string;
43
- nwtP1: number;
44
- nwtP2: number;
45
- pci1: number;
46
- nwtRS: number;
47
- nwtBW: number;
48
- other?: Record<string, any>;
49
- customSubgroup: string;
50
- }
51
- /**
52
- * Supported cell sector status values
53
- */
54
- export type CellStatus = 'On_Air' | 'On_Air_UNDER_CONSTRUCTION' | 'On_Air_Locked' | 'RF_Plan_Ready' | 'Re-Planned_RF_Plan_Ready' | 'Tavlati_RF_Plan_Ready';
7
+ export type { Cell, CellStatus, CellGroupingField } from '../../../shared/demo';
55
8
  /**
56
9
  * Technology-Band combination key
57
10
  */
58
11
  export type TechnologyBandKey = string;
59
- /**
60
- * Grouping fields for Tree View
61
- */
62
- export type CellGroupingField = 'tech' | 'fband' | 'frq' | 'status' | 'siteId' | 'none';
63
12
  /**
64
13
  * Auto-size calculation modes
65
14
  */
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Cell Feature - Type Definitions
3
3
  *
4
- * Core interfaces and types for cellular network visualization
4
+ * Core interfaces and types for cellular network visualization.
5
+ * Base Cell type is imported from shared module for consistency.
5
6
  */
6
7
  export {};
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Cell Generator - Configurable Demo Cell Data Generator
3
+ *
4
+ * Generates realistic cell network data with configurable parameters.
5
+ * Supports density zones, multiple technologies, and various site configurations.
6
+ */
7
+ import type { Cell } from './cell-types';
8
+ /**
9
+ * Generator configuration options
10
+ */
11
+ export interface CellGeneratorConfig {
12
+ /** Number of sites to generate */
13
+ numSites: number;
14
+ /** Center latitude */
15
+ centerLat: number;
16
+ /** Center longitude */
17
+ centerLng: number;
18
+ /** Radius in kilometers */
19
+ radiusKm: number;
20
+ /** Random seed for reproducible generation (optional) */
21
+ seed?: number;
22
+ }
23
+ /**
24
+ * Preset configurations
25
+ */
26
+ export declare const GENERATOR_PRESETS: {
27
+ /** Small dataset for quick demos - 10 sites, ~300 cells */
28
+ readonly small: {
29
+ readonly numSites: 10;
30
+ readonly centerLat: 47.4979;
31
+ readonly centerLng: 19.0402;
32
+ readonly radiusKm: 3;
33
+ };
34
+ /** Medium dataset for testing - 100 sites, ~3000 cells */
35
+ readonly medium: {
36
+ readonly numSites: 100;
37
+ readonly centerLat: 47.4979;
38
+ readonly centerLng: 19.0402;
39
+ readonly radiusKm: 8;
40
+ };
41
+ /** Large dataset for performance testing - 500 sites, ~15000 cells */
42
+ readonly large: {
43
+ readonly numSites: 500;
44
+ readonly centerLat: 47.4979;
45
+ readonly centerLng: 19.0402;
46
+ readonly radiusKm: 12;
47
+ };
48
+ /** Extra large dataset - 2000 sites, ~60000 cells */
49
+ readonly xlarge: {
50
+ readonly numSites: 2000;
51
+ readonly centerLat: 47.4979;
52
+ readonly centerLng: 19.0402;
53
+ readonly radiusKm: 15;
54
+ };
55
+ };
56
+ export type GeneratorPreset = keyof typeof GENERATOR_PRESETS;
57
+ /**
58
+ * Generate demo cells with configurable parameters
59
+ */
60
+ export declare function generateCells(config: CellGeneratorConfig): Cell[];
61
+ /**
62
+ * Generate cells using a preset configuration
63
+ */
64
+ export declare function generateCellsFromPreset(preset: GeneratorPreset, seed?: number): Cell[];
65
+ /**
66
+ * Get info about generated data
67
+ */
68
+ export declare function getGeneratorInfo(cells: Cell[]): {
69
+ totalCells: number;
70
+ totalSites: number;
71
+ techBreakdown: Record<string, number>;
72
+ statusBreakdown: Record<string, number>;
73
+ };
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Cell Generator - Configurable Demo Cell Data Generator
3
+ *
4
+ * Generates realistic cell network data with configurable parameters.
5
+ * Supports density zones, multiple technologies, and various site configurations.
6
+ */
7
+ /**
8
+ * Preset configurations
9
+ */
10
+ export const GENERATOR_PRESETS = {
11
+ /** Small dataset for quick demos - 10 sites, ~300 cells */
12
+ small: {
13
+ numSites: 10,
14
+ centerLat: 47.4979,
15
+ centerLng: 19.0402,
16
+ radiusKm: 3
17
+ },
18
+ /** Medium dataset for testing - 100 sites, ~3000 cells */
19
+ medium: {
20
+ numSites: 100,
21
+ centerLat: 47.4979,
22
+ centerLng: 19.0402,
23
+ radiusKm: 8
24
+ },
25
+ /** Large dataset for performance testing - 500 sites, ~15000 cells */
26
+ large: {
27
+ numSites: 500,
28
+ centerLat: 47.4979,
29
+ centerLng: 19.0402,
30
+ radiusKm: 12
31
+ },
32
+ /** Extra large dataset - 2000 sites, ~60000 cells */
33
+ xlarge: {
34
+ numSites: 2000,
35
+ centerLat: 47.4979,
36
+ centerLng: 19.0402,
37
+ radiusKm: 15
38
+ }
39
+ };
40
+ // Density zones (distance from center as fraction of radius)
41
+ const DENSITY_ZONES = [
42
+ { maxRadius: 0.3, minSpacing: 0.0008, maxSpacing: 0.0015, name: 'Very Dense Core' },
43
+ { maxRadius: 0.5, minSpacing: 0.0015, maxSpacing: 0.003, name: 'Dense Inner' },
44
+ { maxRadius: 0.7, minSpacing: 0.003, maxSpacing: 0.006, name: 'Medium' },
45
+ { maxRadius: 0.85, minSpacing: 0.006, maxSpacing: 0.012, name: 'Sparse Suburban' },
46
+ { maxRadius: 1.0, minSpacing: 0.012, maxSpacing: 0.025, name: 'Very Sparse Rural' }
47
+ ];
48
+ // Cell tech-band definitions
49
+ const TECH_BANDS = [
50
+ { tech: '2G', band: '900', fband: 'GSM900' },
51
+ { tech: '2G', band: '1800', fband: 'GSM1800' },
52
+ { tech: '4G', band: '700', fband: 'LTE700' },
53
+ { tech: '4G', band: '800', fband: 'LTE800' },
54
+ { tech: '4G', band: '900', fband: 'LTE900' },
55
+ { tech: '4G', band: '1800', fband: 'LTE1800' },
56
+ { tech: '4G', band: '2100', fband: 'LTE2100' },
57
+ { tech: '4G', band: '2600', fband: 'LTE2600' },
58
+ { tech: '5G', band: '700', fband: '5G-700' },
59
+ { tech: '5G', band: '2100', fband: '5G-2100' },
60
+ { tech: '5G', band: '3500', fband: '5G-3500' }
61
+ ];
62
+ // Sector configurations
63
+ const SECTORS = [
64
+ { azimuth: 0, sectorNum: 1 },
65
+ { azimuth: 120, sectorNum: 2 },
66
+ { azimuth: 240, sectorNum: 3 }
67
+ ];
68
+ // Status distribution
69
+ const STATUSES = [
70
+ 'On_Air', 'On_Air', 'On_Air', 'On_Air',
71
+ 'On_Air_UNDER_CONSTRUCTION', 'On_Air_Locked',
72
+ 'RF_Plan_Ready', 'RF_Plan_Ready',
73
+ 'Re-Planned_RF_Plan_Ready', 'Tavlati_RF_Plan_Ready',
74
+ 'On_Air', 'On_Air'
75
+ ];
76
+ const BEAMWIDTH = 65;
77
+ /**
78
+ * Simple seeded random number generator
79
+ */
80
+ function createRandom(seed) {
81
+ if (seed === undefined) {
82
+ return Math.random;
83
+ }
84
+ let s = seed;
85
+ return () => {
86
+ s = (s * 1103515245 + 12345) & 0x7fffffff;
87
+ return s / 0x7fffffff;
88
+ };
89
+ }
90
+ /**
91
+ * Get density zone for a given normalized radius
92
+ */
93
+ function getDensityZone(normalizedRadius) {
94
+ for (const zone of DENSITY_ZONES) {
95
+ if (normalizedRadius <= zone.maxRadius) {
96
+ return zone;
97
+ }
98
+ }
99
+ return DENSITY_ZONES[DENSITY_ZONES.length - 1];
100
+ }
101
+ /**
102
+ * Generate cell name from site, sector, and band index
103
+ * Format: SSSS S BB (e.g., "1000141")
104
+ */
105
+ function generateCellName(siteNum, sectorNum, bandIndex) {
106
+ const siteId = String(siteNum + 1000).padStart(4, '0');
107
+ const cellSuffix = String(41 + bandIndex).padStart(2, '0');
108
+ return `${siteId}${sectorNum}${cellSuffix}`;
109
+ }
110
+ /**
111
+ * Generate demo cells with configurable parameters
112
+ */
113
+ export function generateCells(config) {
114
+ const { numSites, centerLat, centerLng, radiusKm, seed } = config;
115
+ const random = createRandom(seed);
116
+ const radiusDegrees = radiusKm / 111;
117
+ const cells = [];
118
+ const usedPositions = [];
119
+ let cellCounter = 1;
120
+ let actualSiteIndex = 0;
121
+ /**
122
+ * Generate random point within circle
123
+ */
124
+ function generateRandomPointInCircle() {
125
+ const r = Math.sqrt(random()) * radiusDegrees;
126
+ const theta = random() * 2 * Math.PI;
127
+ return {
128
+ lat: centerLat + r * Math.cos(theta),
129
+ lng: centerLng + r * Math.sin(theta),
130
+ normalizedRadius: r / radiusDegrees
131
+ };
132
+ }
133
+ /**
134
+ * Check if position is too close to existing sites
135
+ */
136
+ function isTooClose(lat, lng, minSpacing) {
137
+ for (const pos of usedPositions) {
138
+ const distance = Math.sqrt(Math.pow(lat - pos.lat, 2) + Math.pow(lng - pos.lng, 2));
139
+ const requiredSpacing = (minSpacing + pos.minSpacing) / 2;
140
+ if (distance < requiredSpacing) {
141
+ return true;
142
+ }
143
+ }
144
+ return false;
145
+ }
146
+ /**
147
+ * Add jitter to coordinate
148
+ */
149
+ function addJitter(value, maxJitter) {
150
+ return value + (random() - 0.5) * 2 * maxJitter;
151
+ }
152
+ // Generate sites
153
+ for (let attempt = 0; attempt < numSites * 3 && actualSiteIndex < numSites; attempt++) {
154
+ const { lat, lng, normalizedRadius } = generateRandomPointInCircle();
155
+ const zone = getDensityZone(normalizedRadius);
156
+ const minSpacing = zone.minSpacing + random() * (zone.maxSpacing - zone.minSpacing);
157
+ if (isTooClose(lat, lng, minSpacing)) {
158
+ continue;
159
+ }
160
+ const jitterAmount = minSpacing * 0.3;
161
+ const siteLat = addJitter(lat, jitterAmount);
162
+ const siteLng = addJitter(lng, jitterAmount);
163
+ usedPositions.push({ lat: siteLat, lng: siteLng, minSpacing });
164
+ const siteNum = actualSiteIndex;
165
+ const siteId = String(siteNum + 1000).padStart(4, '0');
166
+ actualSiteIndex++;
167
+ // 10% chance of 1-2 sectors instead of 3
168
+ const numSectors = random() < 0.1 ? (random() < 0.5 ? 1 : 2) : 3;
169
+ const sectorsToGenerate = SECTORS.slice(0, numSectors);
170
+ for (const sector of sectorsToGenerate) {
171
+ for (let bandIndex = 0; bandIndex < TECH_BANDS.length; bandIndex++) {
172
+ const techBand = TECH_BANDS[bandIndex];
173
+ const cellName = generateCellName(siteNum, sector.sectorNum, bandIndex);
174
+ const status = STATUSES[bandIndex % STATUSES.length];
175
+ cells.push({
176
+ id: cellName,
177
+ txId: cellName,
178
+ cellID: cellName,
179
+ cellId2G: techBand.tech === '2G' ? cellName : '',
180
+ cellName: cellName,
181
+ siteId: siteId,
182
+ tech: techBand.tech,
183
+ fband: techBand.fband,
184
+ frq: techBand.band,
185
+ type: 'MACRO',
186
+ status: status,
187
+ onAirDate: '2024-01-15',
188
+ bcch: techBand.tech === '2G' ? 100 + bandIndex : 0,
189
+ ctrlid: techBand.tech === '2G' ? `CTRL-${cellName}` : '',
190
+ dlEarfn: techBand.tech === '4G' ? 6200 + bandIndex * 100 : 0,
191
+ antenna: 'DEMO-ANTENNA-MODEL',
192
+ azimuth: sector.azimuth,
193
+ height: 30,
194
+ electricalTilt: '3',
195
+ beamwidth: BEAMWIDTH,
196
+ latitude: siteLat,
197
+ longitude: siteLng,
198
+ dx: 0,
199
+ dy: 0,
200
+ siteLatitude: siteLat,
201
+ siteLongitude: siteLng,
202
+ comment: `Demo ${techBand.tech} ${techBand.band} cell at azimuth ${sector.azimuth}°`,
203
+ planner: 'Demo User',
204
+ atollET: 43.0,
205
+ atollPW: 20.0,
206
+ atollRS: 500.0 + (techBand.band === '700' ? 200 : 0),
207
+ atollBW: parseFloat(techBand.band) / 100,
208
+ rru: `RRU-${siteId}-${sector.sectorNum}`,
209
+ nwtET: 40.0,
210
+ nwtPW: 20,
211
+ pci: cellCounter % 504,
212
+ nwtRS: 450.0,
213
+ nwtBW: 10.0,
214
+ other: {
215
+ city: ['Tehran', 'Shiraz', 'Isfahan', 'Mashhad', 'Tabriz'][siteNum % 5],
216
+ bcc: bandIndex % 8,
217
+ ncc: Math.floor(bandIndex / 8) % 8,
218
+ mall: random() < 0.1 ? 'Shopping Center' : undefined,
219
+ hsn: techBand.tech === '2G' ? Math.floor(random() * 64) : undefined,
220
+ demoCell: true,
221
+ siteNumber: actualSiteIndex,
222
+ sector: sector.sectorNum,
223
+ techBandKey: `${techBand.tech}_${techBand.band}`,
224
+ densityZone: zone.name
225
+ },
226
+ customSubgroup: `Sector-${sector.sectorNum}`
227
+ });
228
+ cellCounter++;
229
+ }
230
+ }
231
+ }
232
+ return cells;
233
+ }
234
+ /**
235
+ * Generate cells using a preset configuration
236
+ */
237
+ export function generateCellsFromPreset(preset, seed) {
238
+ const config = { ...GENERATOR_PRESETS[preset], seed };
239
+ return generateCells(config);
240
+ }
241
+ /**
242
+ * Get info about generated data
243
+ */
244
+ export function getGeneratorInfo(cells) {
245
+ const siteIds = new Set(cells.map(c => c.siteId));
246
+ const techBreakdown = {};
247
+ const statusBreakdown = {};
248
+ for (const cell of cells) {
249
+ techBreakdown[cell.tech] = (techBreakdown[cell.tech] || 0) + 1;
250
+ statusBreakdown[cell.status] = (statusBreakdown[cell.status] || 0) + 1;
251
+ }
252
+ return {
253
+ totalCells: cells.length,
254
+ totalSites: siteIds.size,
255
+ techBreakdown,
256
+ statusBreakdown
257
+ };
258
+ }