@mytec Phase 1 - Core UI & Manual Input, Phase 2 - RF Calculation Engine, Phase 3 - Heatmap Visualization
This commit is contained in:
18
frontend/public/manifest.json
Normal file
18
frontend/public/manifest.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "RFCP - RF Coverage Planner",
|
||||
"short_name": "RFCP",
|
||||
"description": "RF Coverage Planning Tool for wireless network deployment",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#1e293b",
|
||||
"theme_color": "#2563eb",
|
||||
"orientation": "any",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/vite.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
}
|
||||
]
|
||||
}
|
||||
1
frontend/public/vite.svg
Normal file
1
frontend/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
123
frontend/public/workers/rf-worker.js
Normal file
123
frontend/public/workers/rf-worker.js
Normal file
@@ -0,0 +1,123 @@
|
||||
// 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 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 patternLoss = calculateSectorPatternLoss(
|
||||
normalizedAngle,
|
||||
site.beamwidth || 65
|
||||
);
|
||||
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
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user