132 lines
3.4 KiB
JavaScript
132 lines
3.4 KiB
JavaScript
// RF Coverage Calculation Web Worker
|
|
// Runs in a separate thread for parallel processing
|
|
|
|
self.onmessage = function (e) {
|
|
const { type, sites, points, rsrpThreshold } = e.data;
|
|
|
|
if (type === 'calculate') {
|
|
try {
|
|
const results = [];
|
|
|
|
for (let i = 0; i < points.length; i++) {
|
|
const point = points[i];
|
|
let bestRSRP = -Infinity;
|
|
let bestSiteId = '';
|
|
|
|
for (let j = 0; j < sites.length; j++) {
|
|
const site = sites[j];
|
|
const rsrp = calculatePointRSRP(site, point);
|
|
|
|
if (rsrp > bestRSRP) {
|
|
bestRSRP = rsrp;
|
|
bestSiteId = site.id;
|
|
}
|
|
}
|
|
|
|
if (bestRSRP >= rsrpThreshold) {
|
|
results.push({
|
|
lat: point.lat,
|
|
lon: point.lon,
|
|
rsrp: bestRSRP,
|
|
siteId: bestSiteId,
|
|
});
|
|
}
|
|
}
|
|
|
|
self.postMessage({ type: 'complete', results });
|
|
} catch (error) {
|
|
self.postMessage({ type: 'error', message: error.message });
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Calculate RSRP at a specific point (universal formula)
|
|
*/
|
|
function calculatePointRSRP(site, point) {
|
|
var distance = haversineDistance(site.lat, site.lon, point.lat, point.lon);
|
|
|
|
// Minimum distance to prevent -Infinity
|
|
if (distance < 0.01) distance = 0.01;
|
|
|
|
// Free space path loss (universal)
|
|
var fspl =
|
|
20 * Math.log10(distance) + 20 * Math.log10(site.frequency) + 32.45;
|
|
|
|
// Link budget: RSRP = P_tx + G_tx - FSPL
|
|
var rsrp = site.power + site.gain - fspl;
|
|
|
|
// Apply sector antenna directivity: hard cutoff + gradual pattern loss
|
|
if (site.antennaType === 'sector' && site.azimuth !== undefined) {
|
|
var bearing = calculateBearing(site.lat, site.lon, point.lat, point.lon);
|
|
var relativeAngle = Math.abs(bearing - site.azimuth);
|
|
var normalizedAngle =
|
|
relativeAngle > 180 ? 360 - relativeAngle : relativeAngle;
|
|
|
|
var beamwidth = site.beamwidth || 65;
|
|
|
|
// Hard cutoff: no signal outside beamwidth
|
|
if (normalizedAngle > beamwidth / 2) {
|
|
return -Infinity;
|
|
}
|
|
|
|
// Gradual 3GPP pattern loss within beamwidth
|
|
var patternLoss = calculateSectorPatternLoss(
|
|
normalizedAngle,
|
|
beamwidth
|
|
);
|
|
rsrp -= patternLoss;
|
|
}
|
|
|
|
return rsrp;
|
|
}
|
|
|
|
/**
|
|
* Haversine distance in km
|
|
*/
|
|
function haversineDistance(lat1, lon1, lat2, lon2) {
|
|
var R = 6371;
|
|
var dLat = ((lat2 - lat1) * Math.PI) / 180;
|
|
var dLon = ((lon2 - lon1) * Math.PI) / 180;
|
|
|
|
var a =
|
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
Math.cos((lat1 * Math.PI) / 180) *
|
|
Math.cos((lat2 * Math.PI) / 180) *
|
|
Math.sin(dLon / 2) *
|
|
Math.sin(dLon / 2);
|
|
|
|
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
return R * c;
|
|
}
|
|
|
|
/**
|
|
* Calculate bearing from A to B in degrees (0-360)
|
|
*/
|
|
function calculateBearing(lat1, lon1, lat2, lon2) {
|
|
var dLon = ((lon2 - lon1) * Math.PI) / 180;
|
|
var lat1Rad = (lat1 * Math.PI) / 180;
|
|
var lat2Rad = (lat2 * Math.PI) / 180;
|
|
|
|
var y = Math.sin(dLon) * Math.cos(lat2Rad);
|
|
var x =
|
|
Math.cos(lat1Rad) * Math.sin(lat2Rad) -
|
|
Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(dLon);
|
|
|
|
var bearing = (Math.atan2(y, x) * 180) / Math.PI;
|
|
return (bearing + 360) % 360;
|
|
}
|
|
|
|
/**
|
|
* Sector antenna pattern loss (3GPP model)
|
|
*/
|
|
function calculateSectorPatternLoss(angleOffBoresight, beamwidth) {
|
|
var theta3dB = beamwidth / 2;
|
|
var sideLobeLevel = 20;
|
|
|
|
return Math.min(
|
|
12 * Math.pow(angleOffBoresight / theta3dB, 2),
|
|
sideLobeLevel
|
|
);
|
|
}
|