Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add intersection points before split #589

Merged
merged 4 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

# [1.0.0-beta.3]

- Fixed VG combined sector mode

# [1.0.0-beta.2]

- Added airline display into pilot popup
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vatsim-radar",
"version": "1.0.0-beta.2",
"version": "1.0.0-beta.3",
"private": true,
"type": "module",
"scripts": {
Expand Down Expand Up @@ -28,6 +28,8 @@
"@turf/great-circle": "^7.2.0",
"@turf/helpers": "^7.2.0",
"@turf/intersect": "^7.2.0",
"@turf/kinks": "^7.2.0",
"@turf/line-intersect": "^7.2.0",
"@turf/meta": "^7.2.0",
"@turf/truncate": "^7.2.0",
"@turf/union": "^7.2.0",
Expand Down
255 changes: 244 additions & 11 deletions src/utils/data/vatglasses-helper.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,254 @@
import { featureCollection } from '@turf/helpers';
import { featureCollection, lineString, polygon } from '@turf/helpers';
import { flattenEach } from '@turf/meta';
import union from '@turf/union';
import difference from '@turf/difference';
import intersect from '@turf/intersect';
import truncate from '@turf/truncate';
import kinks from '@turf/kinks';
import mergeRanges from 'merge-ranges';
import type { Feature as TurfFeature, Polygon as TurfPolygon, MultiPolygon as TurfMultiPolygon } from 'geojson';
import type { Feature as TurfFeature, Polygon as TurfPolygon, MultiPolygon as TurfMultiPolygon, LineString, Point } from 'geojson';
import type { VatglassesSectorProperties } from './vatglasses';

import lineIntersect from '@turf/line-intersect';

/**
* Rounds the coordinates of a Turf polygon feature to the specified number of decimal places.
* @param feature The Turf polygon feature to round.
* @param decimals The number of decimal places to round to.
* @returns The modified Turf polygon feature with rounded coordinates.
*/
export function roundPolygonCoordinates(feature: TurfFeature<TurfPolygon | TurfMultiPolygon>, decimals: number = 0): TurfFeature<TurfPolygon | TurfMultiPolygon> {
const factor = Math.pow(10, decimals);

const roundCoordinates = (coordinates: number[][]) => {
for (let i = 0; i < coordinates.length; i++) {
coordinates[i][0] = Math.round(coordinates[i][0] * factor) / factor;
coordinates[i][1] = Math.round(coordinates[i][1] * factor) / factor;
}
return coordinates;
};

if (feature.geometry.type === 'Polygon') {
for (let i = 0; i < feature.geometry.coordinates.length; i++) {
feature.geometry.coordinates[i] = roundCoordinates(feature.geometry.coordinates[i]);
}
}
else if (feature.geometry.type === 'MultiPolygon') {
for (let i = 0; i < feature.geometry.coordinates.length; i++) {
for (let j = 0; j < feature.geometry.coordinates[i].length; j++) {
feature.geometry.coordinates[i][j] = roundCoordinates(feature.geometry.coordinates[i][j]);
}
}
}

return feature;
}


function removeDuplicateCoords(feature: TurfFeature<TurfPolygon | TurfMultiPolygon>): TurfFeature<TurfPolygon | TurfMultiPolygon> {
const removeDuplicates = (coordinates: number[][]) => {
return coordinates.filter((coord, index, self) => {
if (index === 0) return true;
const prevCoord = self[index - 1];
return coord[0] !== prevCoord[0] || coord[1] !== prevCoord[1];
});
};

if (feature.geometry.type === 'Polygon') {
for (let i = 0; i < feature.geometry.coordinates.length; i++) {
feature.geometry.coordinates[i] = removeDuplicates(feature.geometry.coordinates[i]);
}
}
else if (feature.geometry.type === 'MultiPolygon') {
for (let i = 0; i < feature.geometry.coordinates.length; i++) {
for (let j = 0; j < feature.geometry.coordinates[i].length; j++) {
feature.geometry.coordinates[i][j] = removeDuplicates(feature.geometry.coordinates[i][j]);
}
}
}

return feature;
}


/**
* Finds the intersection points of two LineStrings.
* @param line1 The first LineString.
* @param line2 The second LineString.
* @returns An array of intersection points.
*/
function findIntersectionPoints(line1: TurfFeature<LineString>, line2: TurfFeature<LineString>): TurfFeature<Point>[] {
const intersections = lineIntersect(line1, line2);
return intersections.features;
}

/**
* Adds intersection points to a LineString.
* @param line The LineString to add points to.
* @param points The intersection points to add.
* @returns The modified LineString with the intersection points added.
*/
function addPointsToLine(line: TurfFeature<LineString>, points: TurfFeature<Point>[]): TurfFeature<LineString> {
const coordinates = line.geometry.coordinates;
// Remove duplicate coordinates that are next to each other
for (let i = coordinates.length - 1; i > 0; i--) {
if (coordinates[i][0] === coordinates[i - 1][0] && coordinates[i][1] === coordinates[i - 1][1]) {
coordinates.splice(i, 1);
}
}

points.forEach(point => {
const [x, y] = point.geometry.coordinates;

// Check if the point is already part of the line
if (coordinates.some(coord => coord[0] === x && coord[1] === y)) {
return;
}

for (let i = 0; i < coordinates.length - 1; i++) {
const [x1, y1] = coordinates[i];
const [x2, y2] = coordinates[i + 1];

// Check if the intersection point lies on the segment between coordinates[i] and coordinates[i + 1]
if (isPointOnSegment([x1, y1], [x2, y2], [x, y])) {
coordinates.splice(i + 1, 0, [x, y]);
break;
}
}
});

return lineString(coordinates);
}

/**
* Checks if a point lies on a segment with a given tolerance.
* @param p1 The first point of the segment.
* @param p2 The second point of the segment.
* @param p The point to check.
* @param tolerance The tolerance for the check.
* @returns True if the point lies on the segment, false otherwise.
*/
const isPointOnSegment = (p1: number[], p2: number[], p: number[], tolerance: number = 0.01) => { // 1e-6, Number.EPSILON
const [x1, y1] = p1;
const [x2, y2] = p2;
const [x, y] = p;

const crossProduct = ((y - y1) * (x2 - x1)) - ((x - x1) * (y2 - y1));
if (Math.abs(crossProduct) > tolerance) return false;

const dotProduct = ((x - x1) * (x2 - x1)) + ((y - y1) * (y2 - y1));
if (dotProduct < 0) return false;

const squaredLengthBA = ((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1));
if (dotProduct > squaredLengthBA) return false;

return true;
};

/**
* Modifies an array of Turf polygon features by finding and adding intersection points.
* @param polygons The array of Turf polygon features to modify.
* @returns The modified array of Turf polygon features.
*/
function modifyPolygonsWithIntersections(polygons: TurfFeature<TurfPolygon>[]): TurfFeature<TurfPolygon>[] {
const modifiedPolygons: TurfFeature<TurfPolygon>[] = [];
const intersectionPointsMap: { [key: string]: TurfFeature<Point>[] } = {};


for (let i = 0; i < polygons.length; i++) {
if (insertfailed) break;
const polygon1 = removeDuplicateCoords(polygons[i]);
const line1 = polygonToLineString(structuredClone(polygon1));
let intersectionPoints: TurfFeature<Point>[] = [];

for (let j = 0; j < polygons.length; j++) {
if (i === j) continue;

const polygon2 = polygons[j];
const line2 = polygonToLineString(polygon2);

// Check if intersection points are already in the map
let points: TurfFeature<Point>[];


const mapKey1 = `${ i }-${ j }`;
const mapKey2 = `${ j }-${ i }`;
if (intersectionPointsMap[mapKey1]) {
points = intersectionPointsMap[mapKey1];
}
else if (intersectionPointsMap[mapKey2]) {
points = intersectionPointsMap[mapKey2];
}
else {
// Find intersection points between the current polygon and all other polygons
points = findIntersectionPoints(line1, line2);
intersectionPointsMap[mapKey1] = points;
intersectionPointsMap[mapKey2] = points;
}

intersectionPoints = intersectionPoints.concat(points);
}

// Ensure intersection points are unique
intersectionPoints = intersectionPoints.filter((point, index, self) => index === self.findIndex(p => p.geometry.coordinates[0] === point.geometry.coordinates[0] && p.geometry.coordinates[1] === point.geometry.coordinates[1]));

// Add all collected intersection points to the current polygon
const modifiedLine1 = addPointsToLine(line1, intersectionPoints);
const modifiedPolygon1 = lineStringToPolygon(modifiedLine1);
modifiedPolygon1.properties = structuredClone(polygon1.properties);

modifiedPolygons.push(modifiedPolygon1);
}

return modifiedPolygons;
}

/**
* Converts a Turf polygon feature to a LineString feature.
* @param polygon The Turf polygon feature to convert.
* @returns The converted LineString feature.
*/
function polygonToLineString(polygon: TurfFeature<TurfPolygon | TurfMultiPolygon>): TurfFeature<LineString> {
const coordinates = polygon.geometry.type === 'Polygon'
? polygon.geometry.coordinates[0]
: polygon.geometry.coordinates[0][0];
return lineString(coordinates);
}

/**
* Converts a Turf LineString feature to a polygon feature.
* @param line The Turf LineString feature to convert.
* @returns The converted polygon feature.
*/
function lineStringToPolygon(line: TurfFeature<LineString>): TurfFeature<TurfPolygon> {
const coordinates = [line.geometry.coordinates];
return polygon(coordinates);
}


const insertfailed = false;
export function splitSectors(sectors: TurfFeature<TurfPolygon>[]) {
let resultPolygons: TurfFeature<TurfPolygon>[] = [];
for (let i = sectors.length - 1; i >= 0; i--) {
const currentPolygon = sectors[i];
removeDuplicateCoords(roundPolygonCoordinates(currentPolygon));

const kinksResult = kinks(currentPolygon);
if (kinksResult.features.length > 0) {
// console.log('kink removing id; ' + i);
sectors.splice(i, 1);
}
}

// ADD INTERSECTIONS
const sectorsWithIntersections = modifyPolygonsWithIntersections(sectors);
sectorsWithIntersections.map(polygon => removeDuplicateCoords(roundPolygonCoordinates(polygon, 0)));


let resultPolygons: TurfFeature<TurfPolygon>[] = [];

try {
for (let i = 0; i < sectors.length; i++) {
const currentPolygon = sectors[i];
truncate(currentPolygon, { mutate: true, precision: 1 });
for (let i = 0; i < sectorsWithIntersections.length; i++) {
const currentPolygon = sectorsWithIntersections[i];
// roundPolygonCoordinates(currentPolygon);
let offset = 0;
if (currentPolygon.properties?.max % 10 !== 0 && currentPolygon.properties?.max % 10 !== 5 && currentPolygon.properties?.max !== 999) {
offset = 1;
Expand All @@ -36,11 +268,12 @@ export function splitSectors(sectors: TurfFeature<TurfPolygon>[]) {
newResultPolygons.push(resultPolygon);
continue;
}
const intersection = intersect(featureCollection([remainingOfCurrentPolygon, resultPolygon]));

const intersection = (intersect(featureCollection([remainingOfCurrentPolygon, resultPolygon])));

if (intersection) {
const difference1 = difference(featureCollection([remainingOfCurrentPolygon, resultPolygon]));
const difference2 = difference(featureCollection([resultPolygon, remainingOfCurrentPolygon]));
const difference1 = (difference(featureCollection([remainingOfCurrentPolygon, resultPolygon])));
const difference2 = (difference(featureCollection([resultPolygon, remainingOfCurrentPolygon])));

if (difference1) {
difference1.properties = structuredClone(difference1.properties);
Expand All @@ -60,6 +293,7 @@ export function splitSectors(sectors: TurfFeature<TurfPolygon>[]) {
flattenEach(intersection, function(currentFeature) {
currentFeature.properties = structuredClone(resultPolygon.properties);
if (currentFeature.properties) currentFeature.properties.altrange = mergeRanges([...structuredClone(resultPolygon.properties?.altrange) ?? [], ...structuredClone(currentPolygon.properties?.altrange) ?? []]);

newResultPolygons.push(currentFeature as TurfFeature<TurfPolygon>);
});
}
Expand Down Expand Up @@ -131,4 +365,3 @@ export function combineSectors(sectors: TurfFeature<TurfPolygon>[]) {
}
return combinedGroupSectors;
}

41 changes: 41 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3455,6 +3455,29 @@ __metadata:
languageName: node
linkType: hard

"@turf/kinks@npm:^7.2.0":
version: 7.2.0
resolution: "@turf/kinks@npm:7.2.0"
dependencies:
"@turf/helpers": "npm:^7.2.0"
"@types/geojson": "npm:^7946.0.10"
tslib: "npm:^2.8.1"
checksum: 10c0/487cfba3aa015a4195742c261ecc622c5c3dd29cb0aa08b1959f051c23b24a0d1b52f2c38f4ce5c70061de21a60d19c940a472fad9b5c2484ecd0eb82fbf7654
languageName: node
linkType: hard

"@turf/line-intersect@npm:^7.2.0":
version: 7.2.0
resolution: "@turf/line-intersect@npm:7.2.0"
dependencies:
"@turf/helpers": "npm:^7.2.0"
"@types/geojson": "npm:^7946.0.10"
sweepline-intersections: "npm:^1.5.0"
tslib: "npm:^2.8.1"
checksum: 10c0/d2ed0159ce84e179f999ed461c5481f063c813bedfdfb4af45e46432503b0acd240128be5c6c2d324e05edc4981fd806a41ee0282567c5d0c80c223497e40cb4
languageName: node
linkType: hard

"@turf/meta@npm:^7.2.0":
version: 7.2.0
resolution: "@turf/meta@npm:7.2.0"
Expand Down Expand Up @@ -13072,6 +13095,15 @@ __metadata:
languageName: node
linkType: hard

"sweepline-intersections@npm:^1.5.0":
version: 1.5.0
resolution: "sweepline-intersections@npm:1.5.0"
dependencies:
tinyqueue: "npm:^2.0.0"
checksum: 10c0/587a597c75b787e61054ef88b98463af47f60855265b7829fa8acc5ebe68fb4bc3d148a80e9f72c69c16a0241bfed38d3fbbe93a735ea5a2276c00116adc5283
languageName: node
linkType: hard

"sync-child-process@npm:^1.0.2":
version: 1.0.2
resolution: "sync-child-process@npm:1.0.2"
Expand Down Expand Up @@ -13255,6 +13287,13 @@ __metadata:
languageName: node
linkType: hard

"tinyqueue@npm:^2.0.0":
version: 2.0.3
resolution: "tinyqueue@npm:2.0.3"
checksum: 10c0/d7b590088f015a94a17132fa209c2f2a80c45158259af5474901fdf5932e95ea13ff6f034bcc725a6d5f66d3e5b888b048c310229beb25ad5bebb4f0a635abf2
languageName: node
linkType: hard

"to-regex-range@npm:^5.0.1":
version: 5.0.1
resolution: "to-regex-range@npm:5.0.1"
Expand Down Expand Up @@ -13980,6 +14019,8 @@ __metadata:
"@turf/great-circle": "npm:^7.2.0"
"@turf/helpers": "npm:^7.2.0"
"@turf/intersect": "npm:^7.2.0"
"@turf/kinks": "npm:^7.2.0"
"@turf/line-intersect": "npm:^7.2.0"
"@turf/meta": "npm:^7.2.0"
"@turf/truncate": "npm:^7.2.0"
"@turf/union": "npm:^7.2.0"
Expand Down
Loading