@smartnet360/svelte-components 0.0.102 → 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.
- package/dist/apps/antenna-pattern/index.d.ts +1 -0
- package/dist/apps/antenna-pattern/index.js +1 -0
- package/dist/apps/antenna-pattern/utils/load-static-antennas.d.ts +17 -0
- package/dist/apps/antenna-pattern/utils/load-static-antennas.js +83 -0
- package/dist/apps/site-check/SiteCheck.svelte +4 -6
- package/dist/core/Charts/ChartCard.svelte +122 -12
- package/dist/core/Charts/ChartCard.svelte.d.ts +2 -0
- package/dist/core/Charts/ChartComponent.svelte +8 -6
- package/dist/core/CoverageMap/ai/AITools.d.ts +117 -0
- package/dist/core/CoverageMap/ai/AITools.js +380 -0
- package/dist/core/CoverageMap/core/CoverageCalculator.d.ts +138 -0
- package/dist/core/CoverageMap/core/CoverageCalculator.js +375 -0
- package/dist/core/CoverageMap/core/GridCalculator.d.ts +115 -0
- package/dist/core/CoverageMap/core/GridCalculator.js +484 -0
- package/dist/core/CoverageMap/core/PathLossModels.d.ts +253 -0
- package/dist/core/CoverageMap/core/PathLossModels.js +380 -0
- package/dist/core/CoverageMap/core/SignalProcessor.d.ts +288 -0
- package/dist/core/CoverageMap/core/SignalProcessor.js +424 -0
- package/dist/core/CoverageMap/data/AntennaStore.d.ts +165 -0
- package/dist/core/CoverageMap/data/AntennaStore.js +327 -0
- package/dist/core/CoverageMap/data/SiteStore.d.ts +155 -0
- package/dist/core/CoverageMap/data/SiteStore.js +355 -0
- package/dist/core/CoverageMap/index.d.ts +74 -0
- package/dist/core/CoverageMap/index.js +103 -0
- package/dist/core/CoverageMap/types.d.ts +252 -0
- package/dist/core/CoverageMap/types.js +7 -0
- package/dist/core/CoverageMap/utils/geoUtils.d.ts +223 -0
- package/dist/core/CoverageMap/utils/geoUtils.js +374 -0
- package/dist/core/CoverageMap/utils/rfUtils.d.ts +329 -0
- package/dist/core/CoverageMap/utils/rfUtils.js +434 -0
- package/dist/core/CoverageMap/visualization/ColorSchemes.d.ts +149 -0
- package/dist/core/CoverageMap/visualization/ColorSchemes.js +377 -0
- package/dist/core/TreeView/index.d.ts +4 -4
- package/dist/core/TreeView/index.js +5 -5
- package/dist/core/TreeView/tree-utils.d.ts +12 -0
- package/dist/core/TreeView/tree-utils.js +115 -6
- package/dist/core/TreeView/tree.store.svelte.d.ts +94 -0
- package/dist/core/TreeView/tree.store.svelte.js +274 -0
- package/dist/map-v2/features/cells/controls/CellFilterControl.svelte +16 -27
- package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte +33 -42
- package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +12 -19
- package/dist/map-v3/core/components/Map.svelte +4 -0
- package/dist/map-v3/core/stores/map.store.svelte.js +2 -0
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte +24 -30
- package/dist/map-v3/features/coverage/index.d.ts +12 -0
- package/dist/map-v3/features/coverage/index.js +16 -0
- package/dist/map-v3/features/coverage/layers/CoverageLayer.svelte +198 -0
- package/dist/map-v3/features/coverage/layers/CoverageLayer.svelte.d.ts +10 -0
- package/dist/map-v3/features/coverage/logic/coloring.d.ts +28 -0
- package/dist/map-v3/features/coverage/logic/coloring.js +77 -0
- package/dist/map-v3/features/coverage/logic/geometry.d.ts +33 -0
- package/dist/map-v3/features/coverage/logic/geometry.js +112 -0
- package/dist/map-v3/features/coverage/stores/coverage.data.svelte.d.ts +46 -0
- package/dist/map-v3/features/coverage/stores/coverage.data.svelte.js +95 -0
- package/dist/map-v3/features/coverage/stores/coverage.display.svelte.d.ts +33 -0
- package/dist/map-v3/features/coverage/stores/coverage.display.svelte.js +90 -0
- package/dist/map-v3/features/coverage/types.d.ts +52 -0
- package/dist/map-v3/features/coverage/types.js +7 -0
- package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +14 -20
- package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +23 -33
- package/dist/map-v3/index.d.ts +4 -0
- package/dist/map-v3/index.js +5 -0
- package/package.json +2 -2
- package/dist/core/TreeView/tree.store.d.ts +0 -10
- package/dist/core/TreeView/tree.store.js +0 -320
|
@@ -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;
|