@smartnet360/svelte-components 0.0.101 → 0.0.103

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.
Files changed (81) hide show
  1. package/dist/apps/antenna-pattern/index.d.ts +1 -0
  2. package/dist/apps/antenna-pattern/index.js +1 -0
  3. package/dist/apps/antenna-pattern/utils/load-static-antennas.d.ts +17 -0
  4. package/dist/apps/antenna-pattern/utils/load-static-antennas.js +83 -0
  5. package/dist/apps/site-check/SiteCheck.svelte +4 -6
  6. package/dist/core/Charts/ChartCard.svelte +122 -12
  7. package/dist/core/Charts/ChartCard.svelte.d.ts +2 -0
  8. package/dist/core/Charts/ChartComponent.svelte +8 -6
  9. package/dist/core/CoverageMap/ai/AITools.d.ts +117 -0
  10. package/dist/core/CoverageMap/ai/AITools.js +380 -0
  11. package/dist/core/CoverageMap/core/CoverageCalculator.d.ts +138 -0
  12. package/dist/core/CoverageMap/core/CoverageCalculator.js +375 -0
  13. package/dist/core/CoverageMap/core/GridCalculator.d.ts +115 -0
  14. package/dist/core/CoverageMap/core/GridCalculator.js +484 -0
  15. package/dist/core/CoverageMap/core/PathLossModels.d.ts +253 -0
  16. package/dist/core/CoverageMap/core/PathLossModels.js +380 -0
  17. package/dist/core/CoverageMap/core/SignalProcessor.d.ts +288 -0
  18. package/dist/core/CoverageMap/core/SignalProcessor.js +424 -0
  19. package/dist/core/CoverageMap/data/AntennaStore.d.ts +165 -0
  20. package/dist/core/CoverageMap/data/AntennaStore.js +327 -0
  21. package/dist/core/CoverageMap/data/SiteStore.d.ts +155 -0
  22. package/dist/core/CoverageMap/data/SiteStore.js +355 -0
  23. package/dist/core/CoverageMap/index.d.ts +74 -0
  24. package/dist/core/CoverageMap/index.js +103 -0
  25. package/dist/core/CoverageMap/types.d.ts +252 -0
  26. package/dist/core/CoverageMap/types.js +7 -0
  27. package/dist/core/CoverageMap/utils/geoUtils.d.ts +223 -0
  28. package/dist/core/CoverageMap/utils/geoUtils.js +374 -0
  29. package/dist/core/CoverageMap/utils/rfUtils.d.ts +329 -0
  30. package/dist/core/CoverageMap/utils/rfUtils.js +434 -0
  31. package/dist/core/CoverageMap/visualization/ColorSchemes.d.ts +149 -0
  32. package/dist/core/CoverageMap/visualization/ColorSchemes.js +377 -0
  33. package/dist/core/TreeView/index.d.ts +4 -4
  34. package/dist/core/TreeView/index.js +5 -5
  35. package/dist/core/TreeView/tree-utils.d.ts +12 -0
  36. package/dist/core/TreeView/tree-utils.js +115 -6
  37. package/dist/core/TreeView/tree.store.svelte.d.ts +94 -0
  38. package/dist/core/TreeView/tree.store.svelte.js +274 -0
  39. package/dist/map-v2/features/cells/controls/CellFilterControl.svelte +16 -27
  40. package/dist/map-v2/features/cells/utils/cellGeoJSON.js +1 -0
  41. package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte +33 -42
  42. package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +12 -19
  43. package/dist/map-v3/core/components/Map.svelte +4 -0
  44. package/dist/map-v3/core/stores/map.store.svelte.js +2 -0
  45. package/dist/map-v3/demo/DemoMap.svelte +31 -5
  46. package/dist/map-v3/demo/demo-cells.js +51 -22
  47. package/dist/map-v3/features/cells/components/CellFilterControl.svelte +24 -30
  48. package/dist/map-v3/features/cells/layers/CellsLayer.svelte +29 -9
  49. package/dist/map-v3/features/cells/logic/geometry.js +3 -0
  50. package/dist/map-v3/features/cells/stores/cell.data.svelte.d.ts +27 -0
  51. package/dist/map-v3/features/cells/stores/cell.data.svelte.js +65 -0
  52. package/dist/map-v3/features/coverage/index.d.ts +12 -0
  53. package/dist/map-v3/features/coverage/index.js +16 -0
  54. package/dist/map-v3/features/coverage/layers/CoverageLayer.svelte +198 -0
  55. package/dist/map-v3/features/coverage/layers/CoverageLayer.svelte.d.ts +10 -0
  56. package/dist/map-v3/features/coverage/logic/coloring.d.ts +28 -0
  57. package/dist/map-v3/features/coverage/logic/coloring.js +77 -0
  58. package/dist/map-v3/features/coverage/logic/geometry.d.ts +33 -0
  59. package/dist/map-v3/features/coverage/logic/geometry.js +112 -0
  60. package/dist/map-v3/features/coverage/stores/coverage.data.svelte.d.ts +46 -0
  61. package/dist/map-v3/features/coverage/stores/coverage.data.svelte.js +95 -0
  62. package/dist/map-v3/features/coverage/stores/coverage.display.svelte.d.ts +33 -0
  63. package/dist/map-v3/features/coverage/stores/coverage.display.svelte.js +90 -0
  64. package/dist/map-v3/features/coverage/types.d.ts +52 -0
  65. package/dist/map-v3/features/coverage/types.js +7 -0
  66. package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +14 -20
  67. package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte +82 -65
  68. package/dist/map-v3/features/selection/components/FeatureSelectionControl.svelte.d.ts +5 -9
  69. package/dist/map-v3/features/selection/index.d.ts +1 -2
  70. package/dist/map-v3/features/selection/index.js +0 -1
  71. package/dist/map-v3/features/selection/stores/selection.store.svelte.d.ts +44 -15
  72. package/dist/map-v3/features/selection/stores/selection.store.svelte.js +163 -40
  73. package/dist/map-v3/features/selection/types.d.ts +4 -2
  74. package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +23 -33
  75. package/dist/map-v3/index.d.ts +4 -0
  76. package/dist/map-v3/index.js +5 -0
  77. package/package.json +2 -2
  78. package/dist/core/TreeView/tree.store.d.ts +0 -10
  79. package/dist/core/TreeView/tree.store.js +0 -320
  80. package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte +0 -209
  81. package/dist/map-v3/features/selection/layers/SelectionHighlightLayers.svelte.d.ts +0 -13
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Geographic Utility Functions
3
+ *
4
+ * This module provides heavily commented geographic calculations:
5
+ * - Distance calculations (Haversine formula)
6
+ * - Bearing calculations (initial and final bearings)
7
+ * - Destination point calculations (given distance and bearing)
8
+ * - Coordinate conversions
9
+ *
10
+ * All calculations assume:
11
+ * - Earth is a sphere (acceptable for ranges < 100km)
12
+ * - Radius = 6371 km (mean Earth radius)
13
+ * - Angles in degrees for input/output, radians for calculations
14
+ */
15
+ // ============================================================================
16
+ // CONSTANTS
17
+ // ============================================================================
18
+ /**
19
+ * Earth's mean radius in kilometers
20
+ * Used for all distance and bearing calculations
21
+ */
22
+ const EARTH_RADIUS_KM = 6371;
23
+ /**
24
+ * Conversion factors
25
+ */
26
+ const DEG_TO_RAD = Math.PI / 180;
27
+ const RAD_TO_DEG = 180 / Math.PI;
28
+ // ============================================================================
29
+ // DISTANCE CALCULATIONS
30
+ // ============================================================================
31
+ /**
32
+ * Calculate distance between two points using Haversine formula
33
+ *
34
+ * The Haversine formula calculates the great-circle distance between two points
35
+ * on a sphere given their longitudes and latitudes.
36
+ *
37
+ * Formula:
38
+ * a = sin²(Δlat/2) + cos(lat1) × cos(lat2) × sin²(Δlon/2)
39
+ * c = 2 × atan2(√a, √(1−a))
40
+ * d = R × c
41
+ *
42
+ * Where:
43
+ * - Δlat = lat2 - lat1 (difference in latitude)
44
+ * - Δlon = lon2 - lon1 (difference in longitude)
45
+ * - R = Earth's radius (6371 km)
46
+ * - All angles in radians for calculation
47
+ *
48
+ * Accuracy:
49
+ * - Very accurate for distances < 100 km
50
+ * - Error < 0.5% for distances up to 1000 km
51
+ * - Assumes spherical Earth (good enough for RF planning)
52
+ *
53
+ * @param point1 - First position (lat, lng in degrees)
54
+ * @param point2 - Second position (lat, lng in degrees)
55
+ * @returns Distance in kilometers
56
+ *
57
+ * @example
58
+ * const distance = calculateDistance(
59
+ * { lat: 40.7128, lng: -74.0060 }, // New York
60
+ * { lat: 51.5074, lng: -0.1278 } // London
61
+ * );
62
+ * // Returns: ~5570 km
63
+ */
64
+ export function calculateDistance(point1, point2) {
65
+ // Convert degrees to radians for trigonometric calculations
66
+ const lat1Rad = point1.lat * DEG_TO_RAD;
67
+ const lat2Rad = point2.lat * DEG_TO_RAD;
68
+ // Calculate differences in coordinates
69
+ const deltaLatRad = (point2.lat - point1.lat) * DEG_TO_RAD;
70
+ const deltaLngRad = (point2.lng - point1.lng) * DEG_TO_RAD;
71
+ // Haversine formula - Step 1: Calculate 'a'
72
+ // sin²(Δlat/2) represents the north-south component
73
+ const sinDeltaLat = Math.sin(deltaLatRad / 2);
74
+ const sinDeltaLatSquared = sinDeltaLat * sinDeltaLat;
75
+ // sin²(Δlon/2) represents the east-west component, scaled by latitude
76
+ const sinDeltaLng = Math.sin(deltaLngRad / 2);
77
+ const sinDeltaLngSquared = sinDeltaLng * sinDeltaLng;
78
+ // Combine components, accounting for Earth's curvature at different latitudes
79
+ const a = sinDeltaLatSquared + Math.cos(lat1Rad) * Math.cos(lat2Rad) * sinDeltaLngSquared;
80
+ // Haversine formula - Step 2: Calculate angular distance 'c'
81
+ // atan2 is used instead of asin for numerical stability
82
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
83
+ // Haversine formula - Step 3: Convert angular distance to linear distance
84
+ const distanceKm = EARTH_RADIUS_KM * c;
85
+ return distanceKm;
86
+ }
87
+ /**
88
+ * Calculate 3D distance including height difference
89
+ *
90
+ * For RF calculations, we often need to account for antenna height and
91
+ * target height (e.g., mobile at 1.5m). This uses Pythagoras in 3D:
92
+ *
93
+ * Formula:
94
+ * d_3d = √(d_horizontal² + Δh²)
95
+ *
96
+ * Where:
97
+ * - d_horizontal = Haversine distance (horizontal plane)
98
+ * - Δh = height difference in km (h2 - h1)
99
+ *
100
+ * Note: For typical cellular ranges (< 10km) and heights (< 100m),
101
+ * the height component is usually negligible (< 0.1% error if ignored)
102
+ *
103
+ * @param point1 - First position with height
104
+ * @param point2 - Second position (height defaults to 1.5m if not provided)
105
+ * @returns 3D distance in kilometers
106
+ */
107
+ export function calculate3DDistance(point1, point2) {
108
+ // Calculate horizontal distance using Haversine
109
+ const horizontalDistanceKm = calculateDistance(point1, point2);
110
+ // Height difference in meters (assume ground-level device if not specified)
111
+ const defaultDeviceHeight = 1.5; // meters (typical mobile phone height)
112
+ // Calculate height difference
113
+ // point2 can be Position2D or Position3D
114
+ const point2Height = point2.height ?? 0;
115
+ const heightDifferenceMeters = point1.height - point2Height; // Convert height difference to kilometers
116
+ const heightDifferenceKm = heightDifferenceMeters / 1000;
117
+ // Pythagoras: d = √(horizontal² + vertical²)
118
+ const distance3DKm = Math.sqrt(horizontalDistanceKm * horizontalDistanceKm + heightDifferenceKm * heightDifferenceKm);
119
+ return distance3DKm;
120
+ }
121
+ // ============================================================================
122
+ // BEARING CALCULATIONS
123
+ // ============================================================================
124
+ /**
125
+ * Calculate initial bearing from point1 to point2
126
+ *
127
+ * Bearing (or azimuth) is the compass direction from one point to another.
128
+ * This calculates the INITIAL bearing - the direction you would initially
129
+ * travel on a great circle route.
130
+ *
131
+ * Formula:
132
+ * θ = atan2(sin(Δlon) × cos(lat2),
133
+ * cos(lat1) × sin(lat2) - sin(lat1) × cos(lat2) × cos(Δlon))
134
+ *
135
+ * Important notes:
136
+ * - Bearing changes along a great circle path (Earth is curved!)
137
+ * - For short distances (< 100 km), this is effectively constant
138
+ * - Result is in degrees: 0° = North, 90° = East, 180° = South, 270° = West
139
+ *
140
+ * RF Planning Usage:
141
+ * Use this to determine the relative angle between antenna and target.
142
+ * Subtract antenna azimuth to get the angle relative to main beam direction.
143
+ *
144
+ * @param from - Starting position (lat, lng in degrees)
145
+ * @param to - Destination position (lat, lng in degrees)
146
+ * @returns Bearing in degrees (0-360), where 0 = North
147
+ *
148
+ * @example
149
+ * const bearing = calculateBearing(
150
+ * { lat: 40.7128, lng: -74.0060 }, // From New York
151
+ * { lat: 51.5074, lng: -0.1278 } // To London
152
+ * );
153
+ * // Returns: ~51° (northeast)
154
+ */
155
+ export function calculateBearing(from, to) {
156
+ // Convert to radians
157
+ const lat1Rad = from.lat * DEG_TO_RAD;
158
+ const lat2Rad = to.lat * DEG_TO_RAD;
159
+ const deltaLngRad = (to.lng - from.lng) * DEG_TO_RAD;
160
+ // Calculate bearing components
161
+ // y-component: East-West displacement, scaled by destination latitude
162
+ const y = Math.sin(deltaLngRad) * Math.cos(lat2Rad);
163
+ // x-component: North-South displacement, accounting for curvature
164
+ const x = Math.cos(lat1Rad) * Math.sin(lat2Rad) -
165
+ Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(deltaLngRad);
166
+ // atan2 gives angle in radians, handling all quadrants correctly
167
+ const bearingRad = Math.atan2(y, x);
168
+ // Convert to degrees and normalize to 0-360 range
169
+ let bearingDeg = bearingRad * RAD_TO_DEG;
170
+ bearingDeg = (bearingDeg + 360) % 360; // Ensure 0-360 range
171
+ return bearingDeg;
172
+ }
173
+ // ============================================================================
174
+ // DESTINATION POINT CALCULATIONS
175
+ // ============================================================================
176
+ /**
177
+ * Calculate destination point given start point, distance, and bearing
178
+ *
179
+ * This is the inverse of the bearing calculation. Given a starting point,
180
+ * a distance, and a direction, it calculates where you would end up.
181
+ *
182
+ * Formula (spherical Earth):
183
+ * lat2 = asin(sin(lat1) × cos(d/R) + cos(lat1) × sin(d/R) × cos(θ))
184
+ * lon2 = lon1 + atan2(sin(θ) × sin(d/R) × cos(lat1),
185
+ * cos(d/R) - sin(lat1) × sin(lat2))
186
+ *
187
+ * Where:
188
+ * - d/R = angular distance (distance / Earth radius)
189
+ * - θ = bearing in radians
190
+ *
191
+ * RF Planning Usage:
192
+ * This is used to project antenna coverage patterns onto the map.
193
+ * For each angle around the antenna, calculate signal range, then
194
+ * use this function to get the geographic coordinate at that range.
195
+ *
196
+ * @param start - Starting position (lat, lng in degrees)
197
+ * @param distanceKm - Distance to travel in kilometers
198
+ * @param bearingDeg - Direction to travel in degrees (0 = North, 90 = East)
199
+ * @returns Destination position (lat, lng in degrees)
200
+ *
201
+ * @example
202
+ * const destination = calculateDestinationPoint(
203
+ * { lat: 40.7128, lng: -74.0060 }, // New York
204
+ * 100, // 100 km
205
+ * 45 // Northeast
206
+ * );
207
+ * // Returns position ~100km northeast of New York
208
+ */
209
+ export function calculateDestinationPoint(start, distanceKm, bearingDeg) {
210
+ // Convert inputs to radians
211
+ const lat1Rad = start.lat * DEG_TO_RAD;
212
+ const lng1Rad = start.lng * DEG_TO_RAD;
213
+ const bearingRad = bearingDeg * DEG_TO_RAD;
214
+ // Calculate angular distance (distance on sphere)
215
+ // This converts linear distance to angle subtended at Earth's center
216
+ const angularDistance = distanceKm / EARTH_RADIUS_KM;
217
+ // Calculate destination latitude
218
+ // Formula accounts for spherical geometry - latitude changes based on:
219
+ // 1. How far north/south we travel (cos component)
220
+ // 2. Starting latitude and direction (sin component)
221
+ const lat2Rad = Math.asin(Math.sin(lat1Rad) * Math.cos(angularDistance) +
222
+ Math.cos(lat1Rad) * Math.sin(angularDistance) * Math.cos(bearingRad));
223
+ // Calculate destination longitude
224
+ // Formula accounts for:
225
+ // 1. Lines of longitude converge at poles (cos(lat) factor)
226
+ // 2. East-West component of bearing (sin(bearing) factor)
227
+ const lng2Rad = lng1Rad +
228
+ Math.atan2(Math.sin(bearingRad) * Math.sin(angularDistance) * Math.cos(lat1Rad), Math.cos(angularDistance) - Math.sin(lat1Rad) * Math.sin(lat2Rad));
229
+ // Convert back to degrees and normalize longitude to -180 to +180
230
+ const lat2 = lat2Rad * RAD_TO_DEG;
231
+ let lng2 = lng2Rad * RAD_TO_DEG;
232
+ // Normalize longitude to -180 to +180 range
233
+ lng2 = ((lng2 + 540) % 360) - 180;
234
+ return { lat: lat2, lng: lng2 };
235
+ }
236
+ // ============================================================================
237
+ // ELEVATION ANGLE CALCULATIONS
238
+ // ============================================================================
239
+ /**
240
+ * Calculate elevation angle from antenna to target
241
+ *
242
+ * Elevation angle is the vertical angle from horizontal plane to target.
243
+ * This is critical for RF calculations because:
244
+ * 1. Antenna vertical pattern varies with elevation angle
245
+ * 2. Mechanical/electrical tilt affects this relationship
246
+ *
247
+ * Formula:
248
+ * elevation = atan((h_antenna - h_target) / d_horizontal)
249
+ *
250
+ * Sign convention:
251
+ * - Positive angle = target above antenna (upward tilt needed)
252
+ * - Negative angle = target below antenna (downward tilt, typical case)
253
+ * - Zero angle = target at same height as antenna
254
+ *
255
+ * Typical Values (cellular networks):
256
+ * - Antenna at 30m, mobile at 1.5m, distance 1km → elevation ≈ -1.6°
257
+ * - Antenna at 30m, mobile at 1.5m, distance 5km → elevation ≈ -0.3°
258
+ * - Most cellular targets are within ±10° elevation
259
+ *
260
+ * @param antennaPos - Antenna position with height (meters above ground)
261
+ * @param targetPos - Target position (lat, lng)
262
+ * @param targetHeight - Target height in meters (default: 1.5m for mobile)
263
+ * @returns Elevation angle in degrees (negative = below horizon)
264
+ *
265
+ * @example
266
+ * const elevation = calculateElevationAngle(
267
+ * { lat: 40.7128, lng: -74.0060, height: 30 }, // 30m antenna
268
+ * { lat: 40.7150, lng: -74.0070 }, // Target location
269
+ * 1.5 // 1.5m mobile height
270
+ * );
271
+ * // Returns: ~-1.5° (target below antenna)
272
+ */
273
+ export function calculateElevationAngle(antennaPos, targetPos, targetHeight = 1.5) {
274
+ // Calculate horizontal distance (ignoring height)
275
+ const horizontalDistanceKm = calculateDistance(antennaPos, targetPos);
276
+ const horizontalDistanceMeters = horizontalDistanceKm * 1000;
277
+ // Calculate height difference (positive = target above antenna)
278
+ const heightDifferenceMeters = targetHeight - antennaPos.height;
279
+ // Calculate elevation angle using arctangent
280
+ // atan gives angle whose tangent is (opposite/adjacent)
281
+ // opposite = height difference
282
+ // adjacent = horizontal distance
283
+ const elevationRad = Math.atan(heightDifferenceMeters / horizontalDistanceMeters);
284
+ const elevationDeg = elevationRad * RAD_TO_DEG;
285
+ return elevationDeg;
286
+ }
287
+ // ============================================================================
288
+ // BOUNDING BOX CALCULATIONS
289
+ // ============================================================================
290
+ /**
291
+ * Calculate bounding box for a square grid around a center point
292
+ *
293
+ * Given a center point and a radius, calculates the geographic bounds
294
+ * (north, south, east, west) that fully contain a square grid.
295
+ *
296
+ * This is used to:
297
+ * 1. Determine grid extents for coverage calculations
298
+ * 2. Set map viewport to show entire coverage area
299
+ * 3. Optimize calculations (skip points outside bounds)
300
+ *
301
+ * Note: Creates a square box, not a circle, for simplicity
302
+ *
303
+ * @param center - Center position (lat, lng)
304
+ * @param radiusKm - Radius from center in kilometers
305
+ * @returns Bounding box (north, south, east, west in degrees)
306
+ */
307
+ export function calculateBounds(center, radiusKm) {
308
+ // Calculate corner points by projecting radius in cardinal directions
309
+ const north = calculateDestinationPoint(center, radiusKm, 0); // North (0°)
310
+ const south = calculateDestinationPoint(center, radiusKm, 180); // South (180°)
311
+ const east = calculateDestinationPoint(center, radiusKm, 90); // East (90°)
312
+ const west = calculateDestinationPoint(center, radiusKm, 270); // West (270°)
313
+ return {
314
+ north: north.lat,
315
+ south: south.lat,
316
+ east: east.lng,
317
+ west: west.lng
318
+ };
319
+ }
320
+ /**
321
+ * Calculate the area of a geographic bounding box
322
+ *
323
+ * Approximates area using spherical geometry.
324
+ * For small areas (< 100 km²), treats as rectangle on flat surface.
325
+ *
326
+ * @param bounds - Geographic bounds
327
+ * @returns Area in square kilometers
328
+ */
329
+ export function calculateBoundsArea(bounds) {
330
+ // Calculate north-south distance
331
+ const nsDistance = calculateDistance({ lat: bounds.south, lng: (bounds.east + bounds.west) / 2 }, { lat: bounds.north, lng: (bounds.east + bounds.west) / 2 });
332
+ // Calculate east-west distance at center latitude
333
+ const centerLat = (bounds.north + bounds.south) / 2;
334
+ const ewDistance = calculateDistance({ lat: centerLat, lng: bounds.west }, { lat: centerLat, lng: bounds.east });
335
+ // Area = width × height
336
+ return nsDistance * ewDistance;
337
+ }
338
+ // ============================================================================
339
+ // UTILITY FUNCTIONS
340
+ // ============================================================================
341
+ /**
342
+ * Normalize angle to 0-360 range
343
+ *
344
+ * @param angle - Angle in degrees (any value)
345
+ * @returns Normalized angle (0-360)
346
+ */
347
+ export function normalizeAngle(angle) {
348
+ return ((angle % 360) + 360) % 360;
349
+ }
350
+ /**
351
+ * Calculate angular difference between two bearings
352
+ * Always returns the smallest angle between them (0-180)
353
+ *
354
+ * @param bearing1 - First bearing in degrees
355
+ * @param bearing2 - Second bearing in degrees
356
+ * @returns Angular difference in degrees (0-180)
357
+ */
358
+ export function angleDifference(bearing1, bearing2) {
359
+ const diff = Math.abs(bearing1 - bearing2);
360
+ return diff > 180 ? 360 - diff : diff;
361
+ }
362
+ /**
363
+ * Check if a point is within a bounding box
364
+ *
365
+ * @param point - Position to check
366
+ * @param bounds - Bounding box
367
+ * @returns true if point is inside bounds
368
+ */
369
+ export function isPointInBounds(point, bounds) {
370
+ return (point.lat >= bounds.south &&
371
+ point.lat <= bounds.north &&
372
+ point.lng >= bounds.west &&
373
+ point.lng <= bounds.east);
374
+ }
@@ -0,0 +1,329 @@
1
+ /**
2
+ * RF Utility Functions
3
+ *
4
+ * This module provides heavily commented RF (Radio Frequency) calculations:
5
+ * - dB arithmetic (addition, subtraction, conversion)
6
+ * - EIRP (Effective Isotropic Radiated Power) calculations
7
+ * - Link budget calculations
8
+ * - Power conversions (dBm, watts, dBd, dBi)
9
+ *
10
+ * RF calculations are done in dB (decibel) domain because:
11
+ * 1. Multiplication becomes addition (easier math)
12
+ * 2. Large dynamic range (can represent 1mW to 1MW easily)
13
+ * 3. Industry standard
14
+ */
15
+ /**
16
+ * Conversion factor between dBd and dBi
17
+ *
18
+ * dBd = gain relative to dipole antenna
19
+ * dBi = gain relative to isotropic antenna (theoretical point source)
20
+ *
21
+ * Relationship: dBi = dBd + 2.15
22
+ *
23
+ * Example:
24
+ * A 10 dBd antenna = 12.15 dBi
25
+ * Most commercial specs use dBd, calculations often need dBi
26
+ */
27
+ export declare const DBD_TO_DBI = 2.15;
28
+ /**
29
+ * Convert dBm to watts (linear power)
30
+ *
31
+ * dBm is power relative to 1 milliwatt on a logarithmic scale:
32
+ * P_dBm = 10 × log₁₀(P_mW / 1mW)
33
+ *
34
+ * To convert back to watts:
35
+ * P_watts = 10^(P_dBm / 10) / 1000
36
+ *
37
+ * Common values to remember:
38
+ * 0 dBm = 1 mW = 0.001 W
39
+ * 10 dBm = 10 mW = 0.01 W
40
+ * 20 dBm = 100 mW = 0.1 W
41
+ * 30 dBm = 1 W
42
+ * 40 dBm = 10 W
43
+ * 43 dBm = 20 W (typical cellular base station)
44
+ *
45
+ * @param dBm - Power in dBm
46
+ * @returns Power in watts
47
+ *
48
+ * @example
49
+ * const power = dBmToWatts(43); // Typical cell site TX power
50
+ * // Returns: 19.95 watts (≈ 20W)
51
+ */
52
+ export declare function dBmToWatts(dBm: number): number;
53
+ /**
54
+ * Convert watts to dBm
55
+ *
56
+ * @param watts - Power in watts
57
+ * @returns Power in dBm
58
+ *
59
+ * @example
60
+ * const dBm = wattsTodBm(20); // 20 watt transmitter
61
+ * // Returns: 43.01 dBm
62
+ */
63
+ export declare function wattsTodBm(watts: number): number;
64
+ /**
65
+ * Convert dBd to dBi
66
+ *
67
+ * dBd = gain relative to half-wave dipole antenna
68
+ * dBi = gain relative to isotropic radiator
69
+ *
70
+ * An isotropic antenna radiates equally in all directions (theoretical).
71
+ * A dipole has slight directionality, giving it 2.15 dB gain over isotropic.
72
+ *
73
+ * Therefore: dBi = dBd + 2.15
74
+ *
75
+ * @param dBd - Gain in dBd
76
+ * @returns Gain in dBi
77
+ */
78
+ export declare function dBdTodBi(dBd: number): number;
79
+ /**
80
+ * Convert dBi to dBd
81
+ *
82
+ * @param dBi - Gain in dBi
83
+ * @returns Gain in dBd
84
+ */
85
+ export declare function dBiTodBd(dBi: number): number;
86
+ /**
87
+ * Add two power values in dB domain
88
+ *
89
+ * IMPORTANT: You cannot just add dB values to combine powers!
90
+ *
91
+ * Correct method:
92
+ * 1. Convert both dB values to linear (watts)
93
+ * 2. Add the linear values
94
+ * 3. Convert back to dB
95
+ *
96
+ * Formula:
97
+ * P_total_dB = 10 × log₁₀(10^(P1_dB/10) + 10^(P2_dB/10))
98
+ *
99
+ * Special cases:
100
+ * - Two equal powers: P_total = P1 + 3 dB (doubling power = +3 dB)
101
+ * - Powers differ by >10 dB: P_total ≈ larger value (small contribution ignored)
102
+ *
103
+ * @param dB1 - First power in dB
104
+ * @param dB2 - Second power in dB
105
+ * @returns Combined power in dB
106
+ *
107
+ * @example
108
+ * const combined = addPowersdB(20, 20); // Two 20 dBm sources
109
+ * // Returns: 23 dBm (not 40!)
110
+ */
111
+ export declare function addPowersdB(dB1: number, dB2: number): number;
112
+ /**
113
+ * Subtract two power values in dB domain (for interference calculations)
114
+ *
115
+ * This calculates the power remaining after subtracting one power from another.
116
+ *
117
+ * Formula:
118
+ * P_diff_dB = 10 × log₁₀(10^(P1_dB/10) - 10^(P2_dB/10))
119
+ *
120
+ * Note: Returns -Infinity if P2 >= P1 (can't subtract more than you have)
121
+ *
122
+ * @param dB1 - First power in dB (larger)
123
+ * @param dB2 - Second power in dB (smaller)
124
+ * @returns Difference in dB
125
+ */
126
+ export declare function subtractPowersdB(dB1: number, dB2: number): number;
127
+ /**
128
+ * Calculate EIRP (Effective Isotropic Radiated Power)
129
+ *
130
+ * EIRP is the apparent power radiated by an antenna in the direction of peak gain.
131
+ * It combines:
132
+ * 1. Transmitter power
133
+ * 2. Cable/connector losses (reduces power)
134
+ * 3. Antenna gain (increases power in specific direction)
135
+ *
136
+ * Formula (in dB domain - simple addition!):
137
+ * EIRP = P_tx + G_antenna - L_cable
138
+ *
139
+ * Where:
140
+ * - P_tx = Transmit power (dBm)
141
+ * - G_antenna = Antenna gain (dBi) - must be in dBi, not dBd!
142
+ * - L_cable = Cable and connector losses (dB) - positive value
143
+ *
144
+ * In linear domain, this would be multiplication/division:
145
+ * EIRP = P_tx × G_antenna / L_cable
146
+ *
147
+ * Why EIRP matters:
148
+ * - Determines maximum signal strength at receiver
149
+ * - Regulatory limits often specified as EIRP
150
+ * - Used in link budget calculations
151
+ *
152
+ * Typical values:
153
+ * - Small cell: 30-40 dBm EIRP
154
+ * - Macro cell: 50-65 dBm EIRP
155
+ * - Microwave link: 40-50 dBm EIRP
156
+ *
157
+ * @param txPowerdBm - Transmit power in dBm
158
+ * @param antennaGaindBi - Antenna gain in dBi (not dBd!)
159
+ * @param cableLossdB - Cable and connector losses in dB (default: 0.5 dB)
160
+ * @returns EIRP in dBm
161
+ *
162
+ * @example
163
+ * const eirp = calculateEIRP(43, 15, 1);
164
+ * // 43 dBm TX power (20W)
165
+ * // + 15 dBi antenna gain
166
+ * // - 1 dB cable loss
167
+ * // = 57 dBm EIRP (500W equivalent isotropic)
168
+ */
169
+ export declare function calculateEIRP(txPowerdBm: number, antennaGaindBi: number, cableLossdB?: number): number;
170
+ /**
171
+ * Calculate effective EIRP in a specific direction
172
+ *
173
+ * Antennas don't radiate equally in all directions - they have patterns.
174
+ * This calculates the EIRP in a specific direction by:
175
+ * 1. Starting with maximum EIRP (boresight direction)
176
+ * 2. Subtracting the pattern attenuation for the given direction
177
+ *
178
+ * Formula:
179
+ * EIRP_direction = EIRP_max - Attenuation(angle)
180
+ *
181
+ * Where:
182
+ * - EIRP_max = Maximum EIRP (boresight)
183
+ * - Attenuation(angle) = Pattern loss at specific angle (dB)
184
+ *
185
+ * Example:
186
+ * - EIRP_max = 60 dBm
187
+ * - At 30° off boresight, pattern shows -3 dB
188
+ * - EIRP at 30° = 60 - 3 = 57 dBm
189
+ *
190
+ * @param maxEIRP - Maximum EIRP (dBm)
191
+ * @param patternAttenuation - Pattern attenuation at angle (dB, positive value)
192
+ * @returns Effective EIRP in direction (dBm)
193
+ */
194
+ export declare function calculateEffectiveEIRP(maxEIRP: number, patternAttenuation: number): number;
195
+ /**
196
+ * Calculate received signal strength
197
+ *
198
+ * This is the fundamental equation for RF coverage prediction:
199
+ *
200
+ * P_received = EIRP - PathLoss + RX_Gain - RX_Losses
201
+ *
202
+ * Where (all in dB):
203
+ * - EIRP = Effective Isotropic Radiated Power (dBm)
204
+ * - PathLoss = Free space or propagation loss (dB) - positive value
205
+ * - RX_Gain = Receiver antenna gain (dBi) - usually small for mobiles
206
+ * - RX_Losses = Receiver losses, body loss, etc. (dB) - positive value
207
+ *
208
+ * For mobile devices:
209
+ * - RX_Gain ≈ 0 dBi (omnidirectional antenna)
210
+ * - RX_Losses ≈ 3 dB (body loss, hand proximity)
211
+ * - Net effect: -3 dB
212
+ *
213
+ * Interpretation:
214
+ * - P_received > -70 dBm: Excellent signal
215
+ * - P_received > -85 dBm: Good signal
216
+ * - P_received > -95 dBm: Fair signal (data services OK)
217
+ * - P_received > -105 dBm: Poor signal (voice calls only)
218
+ * - P_received < -105 dBm: No service
219
+ *
220
+ * @param eirp - Effective radiated power in direction (dBm)
221
+ * @param pathLoss - Path loss in dB (positive value)
222
+ * @param rxGain - Receiver antenna gain in dBi (default: 0)
223
+ * @param rxLosses - Receiver losses in dB (default: 3)
224
+ * @returns Received signal strength in dBm
225
+ *
226
+ * @example
227
+ * const rxPower = calculateReceivedPower(57, 110, 0, 3);
228
+ * // 57 dBm EIRP
229
+ * // - 110 dB path loss (1 km at 700 MHz)
230
+ * // + 0 dBi receiver gain
231
+ * // - 3 dB receiver losses
232
+ * // = -56 dBm (excellent signal)
233
+ */
234
+ export declare function calculateReceivedPower(eirp: number, pathLoss: number, rxGain?: number, rxLosses?: number): number;
235
+ /**
236
+ * Calculate maximum range for a given signal threshold
237
+ *
238
+ * This inverts the link budget equation to find the distance where
239
+ * received power equals a specific threshold (e.g., -95 dBm).
240
+ *
241
+ * Starting from:
242
+ * P_rx = EIRP - PathLoss(d)
243
+ *
244
+ * Rearranging:
245
+ * PathLoss(d) = EIRP - P_rx_threshold
246
+ *
247
+ * Then using path loss model (e.g., FSPL):
248
+ * FSPL = 32.45 + 20log₁₀(d_km) + 20log₁₀(f_MHz)
249
+ *
250
+ * Solving for d:
251
+ * d_km = 10^((FSPL - 32.45 - 20log₁₀(f_MHz)) / 20)
252
+ *
253
+ * This gives us the theoretical maximum range in ideal conditions.
254
+ *
255
+ * @param eirp - Effective radiated power (dBm)
256
+ * @param frequencyMHz - Frequency in MHz
257
+ * @param threshold - Minimum received power (dBm), e.g., -95
258
+ * @param rxGain - Receiver gain (dBi), default: 0
259
+ * @param rxLosses - Receiver losses (dB), default: 3
260
+ * @returns Maximum range in kilometers
261
+ */
262
+ export declare function calculateMaxRange(eirp: number, frequencyMHz: number, threshold: number, rxGain?: number, rxLosses?: number): number;
263
+ /**
264
+ * Convert antenna pattern attenuation to gain
265
+ *
266
+ * Antenna pattern files typically store attenuation values:
267
+ * - 0 dB = maximum gain (boresight direction)
268
+ * - Positive values = attenuation (less gain)
269
+ * - Example: 3 dB attenuation = 3 dB below maximum
270
+ *
271
+ * To get actual gain in a direction:
272
+ * Gain(angle) = MaxGain - Attenuation(angle)
273
+ *
274
+ * @param maxGain - Maximum antenna gain (dBi or dBd)
275
+ * @param attenuation - Pattern attenuation at angle (dB)
276
+ * @returns Effective gain at angle
277
+ *
278
+ * @example
279
+ * const gain = attenuationToGain(15, 3);
280
+ * // 15 dBi max gain
281
+ * // - 3 dB attenuation at 30°
282
+ * // = 12 dBi gain at 30°
283
+ */
284
+ export declare function attenuationToGain(maxGain: number, attenuation: number): number;
285
+ /**
286
+ * Interpolate antenna pattern value for non-integer angles
287
+ *
288
+ * Antenna patterns typically have 360 samples (one per degree).
289
+ * For angles between samples, use linear interpolation.
290
+ *
291
+ * @param pattern - Array of 360 pattern values
292
+ * @param angle - Angle in degrees (can be non-integer)
293
+ * @returns Interpolated pattern value
294
+ */
295
+ export declare function interpolatePattern(pattern: number[], angle: number): number;
296
+ /**
297
+ * Classify signal quality based on received power
298
+ *
299
+ * @param rssi - Received signal strength in dBm
300
+ * @returns Quality category
301
+ */
302
+ export declare function classifySignalQuality(rssi: number): 'excellent' | 'good' | 'fair' | 'poor' | 'no-signal';
303
+ /**
304
+ * Calculate signal-to-noise ratio (SNR)
305
+ *
306
+ * SNR = Signal Power - Noise Power (both in dBm)
307
+ *
308
+ * Typical noise floor: -100 to -120 dBm depending on bandwidth
309
+ *
310
+ * @param signalPower - Signal power in dBm
311
+ * @param noisePower - Noise power in dBm
312
+ * @returns SNR in dB
313
+ */
314
+ export declare function calculateSNR(signalPower: number, noisePower: number): number;
315
+ /**
316
+ * Calculate thermal noise floor
317
+ *
318
+ * Formula: N = -174 + 10log₁₀(BW) + NF
319
+ *
320
+ * Where:
321
+ * - -174 dBm/Hz is thermal noise density at room temperature
322
+ * - BW is bandwidth in Hz
323
+ * - NF is receiver noise figure in dB (typically 5-9 dB)
324
+ *
325
+ * @param bandwidthHz - Signal bandwidth in Hz
326
+ * @param noiseFigure - Receiver noise figure in dB (default: 7)
327
+ * @returns Noise floor in dBm
328
+ */
329
+ export declare function calculateNoiseFloor(bandwidthHz: number, noiseFigure?: number): number;