Skip to content

Commit

Permalink
Merge pull request #589 from FX5F/vg-nofloat
Browse files Browse the repository at this point in the history
Add intersection points before split
  • Loading branch information
daniluk4000 authored Jan 3, 2025
2 parents fb1b5df + a434657 commit 8698951
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 12 deletions.
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

0 comments on commit 8698951

Please sign in to comment.