Skip to content

Commit

Permalink
feat(overlaps): Finished lobe overlap detection implementation with t…
Browse files Browse the repository at this point in the history
…urfjs
  • Loading branch information
endevii committed Apr 6, 2024
1 parent a1706ce commit f678d97
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 81 deletions.
240 changes: 205 additions & 35 deletions app/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import {
LayerGroup,
useMap,
} from 'react-leaflet';
import L from 'leaflet';
// import L, { LatLngExpression } from 'leaflet';
// import L from 'leaflet';
import L, { LatLngExpression } from 'leaflet';
import 'leaflet/dist/leaflet.css';

import { intersect } from '@turf/intersect';
import { polygon } from '@turf/helpers';
import { Position } from 'geojson';

import Antennas from './Antennas';
import SectorLobes from './SectorLobes';
import AntennaInfo from './AntennaInfo';
Expand All @@ -22,7 +26,7 @@ import { Device } from '../accessPointTypes';
import { useAppSelector, useAppDispatch, useAppStore } from '../../lib/hooks';

// import { AccessPoint, Antenna } from '../types';
import { AccessPoint } from '../types';
import { AccessPoint, SectorlobeData } from '../types';

import { initializeActual } from '../../lib/features/actual/actualSlice';

Expand All @@ -37,6 +41,45 @@ import {
changeCurrent,
} from '../../lib/features/currentAntennas/currentAntennasSlice';

import { initializeSectorlobes } from '../../lib/features/sectorlobes/sectorlobesSlice';

function IntersectionInfo({
intersections,
setPanToCoords,
}: {
intersections: SectorlobeData[][][];
setPanToCoords: (coords: L.LatLngExpression) => void;
}) {
return (
<div className="flex flex-col justify-center text-center align-middle">
{intersections.map((intersection, index) => {
return (
<div key={index}>
{intersection.map((pair: SectorlobeData[], index: number) => {
const lobe1 = pair[0];
const lobe2 = pair[1];
return (
<div key={index} className="mx-2 flex flex-col justify-center">
<p>Frequency {lobe1.frequency} intersection between lobes </p>
<p className="mx-2">
{lobe1.id} and {lobe2.id}
</p>
<button
className="hover:text-gray-500"
onClick={() => setPanToCoords(lobe1.center)}
>
Go to
</button>
</div>
);
})}
</div>
);
})}
</div>
);
}

function DynamicCircleRadius() {
const map = useMap();

Expand Down Expand Up @@ -69,8 +112,17 @@ function DynamicCircleRadius() {
return null;
}

function RecenterMap({ panToCoords }: { panToCoords: LatLngExpression }) {
const map = useMap();
map.setView(panToCoords, map.getZoom());
return null;
}

export default function Map() {
const [toggleInfo, setToggleInfo] = useState(false);
const [intersections, setIntersections] = useState<SectorlobeData[][][]>([]);
const [panToCoords, setPanToCoords] = useState<LatLngExpression | null>(null);
const [intersectionToggle, setIntersectionToggle] = useState(false);

const [currentAntenna, setCurrentAntenna] = useState<AccessPoint | null>(
null
Expand All @@ -83,6 +135,9 @@ export default function Map() {
const actualData = useAppSelector((state) => state.actual.value);
const oldPlaygroundData = useAppSelector((state) => state.playground.old);

const sectorlobesData: SectorlobeData[] = useAppSelector(
(state) => state.sectorlobes.value
);
const antennasData = useAppSelector((state) => state.currentAntennas.value);

const dispatch = useAppDispatch();
Expand Down Expand Up @@ -128,10 +183,68 @@ export default function Map() {
})
);

const sectorlobeData: SectorlobeData[] = antennasData.map((ap) => {
const center: L.LatLngTuple = [
parseFloat(ap.lat.trim()),
parseFloat(ap.lon.trim()),
];
const heading = ap.azimuth;
const radiusInMeters = 100;
const sectorWidth = 45;
let radius: number = 0;

if (heading < 45) {
// 0-45
radius = radiusInMeters;
} else if (heading < 135) {
// 45-135
radius = radiusInMeters - (radiusInMeters / 100) * 20;
} else if (heading < 225) {
// 135-225
radius = radiusInMeters;
} else if (heading < 315) {
// 225-315
radius = radiusInMeters - (radiusInMeters / 100) * 20;
} else if (heading <= 360) {
// 315-360
radius = radiusInMeters;
}
const numberOfVertices: number = 100;
const earthCircumferenceAtLatitude =
40008000 * Math.cos((center[0] * Math.PI) / 180);

const scaleFactor = (radius / earthCircumferenceAtLatitude) * 360;
const sectorVertices: LatLngExpression[] = Array.from(
{ length: numberOfVertices + 1 },
(_, index) => {
const angle: number =
(90 +
heading -
sectorWidth / 2 +
(sectorWidth * index) / numberOfVertices) *
(Math.PI / 180);

const lat: number = center[0] + scaleFactor * Math.sin(angle);
// const lng: number = center[1] + scaleFactor * Math.cos(angle) * 0.3;
const lng: number = center[1] + scaleFactor * Math.cos(angle);

return [lat, lng];
}
);
sectorVertices.push(center);
return {
id: ap.id,
center,
sectorVertices,
frequency: ap.frequency,
};
});

if (!initialized.current) {
store.dispatch(initializeActual(antennasData));
store.dispatch(initializePlayground(antennasData));
store.dispatch(initializeCurrent(antennasData));
store.dispatch(initializeSectorlobes(sectorlobeData));
initialized.current = true;
}
}
Expand Down Expand Up @@ -161,41 +274,97 @@ export default function Map() {
fetchDataAndSetAntennasData();
}, [store]);

// function getSectorVertices(
// center: [number, number],
// radius: number,
// heading: number,
// sectorWidth: number,
// numberOfVertices: number
// ): LatLngExpression[] {
// const earthCircumferenceAtLatitude =
// 40008000 * Math.cos((center[0] * Math.PI) / 180);

// const scaleFactor = (radius / earthCircumferenceAtLatitude) * 360;

// const sectorVertices: LatLngExpression[] = Array.from(
// { length: 2 },
// (_, index) => {
// const angle: number =
// (90 +
// heading -
// sectorWidth / 2 +
// (sectorWidth * index) / numberOfVertices) *
// (Math.PI / 180);

// const lat: number = center[0] + scaleFactor * Math.sin(angle);
// // const lng: number = center[1] + scaleFactor * Math.cos(angle) * 0.3;
// const lng: number = center[1] + scaleFactor * Math.cos(angle);

// return [lat, lng];
// }
// );

// return sectorVertices;
// }
useEffect(() => {
// gather lobes into clusters by frequency
if (sectorlobesData.length > 0) {
const clusters: { [key: string]: SectorlobeData[] } = {};
const foundIntersections: { [key: string]: SectorlobeData[][] } = {};
for (const lobe of sectorlobesData) {
const key = lobe.frequency.toString();
if (!(key in clusters)) {
clusters[key] = [];
}
clusters[key].push(lobe);
}

// for each cluster, find if any lobes overlap
for (const key in clusters) {
const cluster = clusters[key];
for (let i = 0; i < cluster.length; i++) {
const lobe1 = cluster[i];
for (let j = i + 1; j < cluster.length; j++) {
const lobe2 = cluster[j];

// Convert the LatLngExpression[] to a Position[] for turf.js
const lobe1Positions: Position[] = lobe1.sectorVertices.map(
(vertex) => {
if (Array.isArray(vertex)) {
return [vertex[1], vertex[0]];
} else {
return [vertex.lng, vertex.lat];
}
}
);

const lobe2Positions: Position[] = lobe2.sectorVertices.map(
(vertex) => {
if (Array.isArray(vertex)) {
return [vertex[1], vertex[0]];
} else {
return [vertex.lng, vertex.lat];
}
}
);

const center1: Position = [lobe1.center[1], lobe1.center[0]];

const center2: Position = [lobe2.center[1], lobe2.center[0]];

lobe1Positions.unshift(center1);
lobe2Positions.unshift(center2);

const poly1 = polygon([lobe1Positions]);
const poly2 = polygon([lobe2Positions]);
const intersection = intersect({
type: 'FeatureCollection',
features: [poly1, poly2],
});
if (intersection) {
if (!(key in foundIntersections)) {
foundIntersections[key] = [];
}
foundIntersections[key].push([lobe1, lobe2]);
}
}
}
}
setIntersections(Object.values(foundIntersections));
}
}, [sectorlobesData]);

return (
<>
<div className="absolute right-0 top-0 z-[1001] flex flex-col justify-center bg-black">
{intersections.length > 0 ? (
<p className="text-center">
Found {intersections.length} intersection
{intersections.length > 1 ? 's' : ''}.
</p>
) : (
<p className="text-center">No intersections found.</p>
)}
{intersectionToggle && intersections.length > 0 ? (
<IntersectionInfo
intersections={intersections}
setPanToCoords={(coords: LatLngExpression) =>
setPanToCoords(coords)
}
/>
) : null}
<button onClick={() => setIntersectionToggle(!intersectionToggle)}>
{!intersectionToggle ? 'Open' : 'Close'}
</button>
</div>
{toggleInfo ? (
<AntennaInfo
currentAntenna={currentAntenna}
Expand All @@ -222,6 +391,7 @@ export default function Map() {
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<DynamicCircleRadius />
{panToCoords ? <RecenterMap panToCoords={panToCoords} /> : null}
{/* Call anything you want to add to the map here. */}
<LayersControl position="bottomleft">
<LayersControl.Overlay name="Sector Lobes" checked>
Expand Down
10 changes: 10 additions & 0 deletions app/components/SectorLobe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { SectorLobeProps } from '../types';
import { useAppSelector, useAppDispatch } from '../../lib/hooks';

import { updateCurrent } from '@/lib/features/currentAntennas/currentAntennasSlice';
import { updateSectorlobes } from '@/lib/features/sectorlobes/sectorlobesSlice';

export default function SectorLobe({
key_path,
Expand Down Expand Up @@ -152,9 +153,18 @@ export default function SectorLobe({
const newAp = { ...currentAp };
newAp.azimuth = tempHeading;
newAp.frequency = tempFreq;

const newSectorLobe = {
id: ap.id,
center: center,
sectorVertices: sectorVertices,
frequency: tempFreq,
};

setCurrentAp(newAp);
if (currentMode === 'playground') {
dispatch(updateCurrent(newAp));
dispatch(updateSectorlobes(newSectorLobe));
}
}

Expand Down
3 changes: 1 addition & 2 deletions app/components/SectorLobes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import 'leaflet/dist/leaflet.css';

import SectorLobe from './SectorLobe';

// ! Current known issues:
// ! Current known issues/map quirks:
// ! 1. The sectorlobes, when tuned to different headings, the look of the width changes.
// ! This is because the way shapes are warped due to the projection of the map.
// ! 2. The sectorLobes do not represent the actual shape of the sectorLobe.
// ! This is because the sectorLobe is not a cone. It is an ellipse.
// ! So their needs to be a specific algorithm which will aid in the creation of the sectorLobe based on the model of the antenna.
// ! 3. The sectorlobes do not appear if heading is at 360 degrees

import { useAppSelector } from '../../lib/hooks';

Expand Down
9 changes: 9 additions & 0 deletions app/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LatLngExpression, LatLngTuple } from 'leaflet';

// Interfaces

export type Antenna = {
Expand Down Expand Up @@ -38,6 +40,13 @@ export interface ReducedPoints {
[key: string]: ReducedContent;
}

export interface SectorlobeData {
id: string;
center: LatLngTuple;
sectorVertices: LatLngExpression[];
frequency: number;
}

// Props

export interface InfoProps {
Expand Down
Loading

0 comments on commit f678d97

Please sign in to comment.