Skip to content

Commit

Permalink
Revert "Revert "Refined Navigraph Layout gate placement (#620)""
Browse files Browse the repository at this point in the history
This reverts commit b03ccd4.
  • Loading branch information
daniluk4000 committed Jan 9, 2025
1 parent 8ca0585 commit 3d93247
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 98 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@turf/kinks": "^7.2.0",
"@turf/line-intersect": "^7.2.0",
"@turf/meta": "^7.2.0",
"@turf/nearest-point-on-line": "^7.2.0",
"@turf/truncate": "^7.2.0",
"@turf/union": "^7.2.0",
"@vite-pwa/nuxt": "^0.10.6",
Expand Down Expand Up @@ -65,6 +66,7 @@
"ws": "^8.18.0"
},
"devDependencies": {
"@navigraph/amdb": "^1.1.0",
"@nuxt/devtools": "^1.7.0",
"@nuxt/eslint": "^0.7.5",
"@nuxtjs/stylelint-module": "^5.2.0",
Expand Down
5 changes: 3 additions & 2 deletions src/components/map/airports/MapAirport.vue
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ import { fromCircle } from 'ol/geom/Polygon';
import type { VatsimShortenedController } from '~/types/data/vatsim';
import { sortControllersByPosition } from '~/composables/atc';
import MapAirportCounts from '~/components/map/airports/MapAirportCounts.vue';
import type { NavigraphAirportData, NavigraphLayoutType } from '~/types/data/navigraph';
import type { NavigraphAirportData } from '~/types/data/navigraph';
import { useMapStore } from '~/store/map';
import { getCurrentThemeRgbColor, useScrollExists } from '~/composables';
import type { Coordinate } from 'ol/coordinate';
Expand All @@ -142,6 +142,7 @@ import { fromLonLat } from 'ol/proj';
import { getSelectedColorFromSettings } from '~/composables/colors';
import { isVatGlassesActive } from '~/utils/data/vatglasses';
import { supportedNavigraphLayouts } from '~/utils/shared/vatsim';
import type { AmdbLayerName } from '@navigraph/amdb';
const props = defineProps({
airport: {
Expand Down Expand Up @@ -674,7 +675,7 @@ onMounted(async () => {
}
for (const [_key, value] of Object.entries(val)) {
const key = _key as NavigraphLayoutType;
const key = _key as AmdbLayerName;
if (!supportedLayouts.value.includes(key)) continue;
const features = geojson.readFeatures(value, {
Expand Down
8 changes: 4 additions & 4 deletions src/components/map/airports/MapAirportsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import type {
NavigraphAirportData,
NavigraphGate,
NavigraphLayout,
NavigraphLayoutType,
NavigraphRunway,
} from '~/types/data/navigraph';
import { Point } from 'ol/geom';
Expand All @@ -52,6 +51,7 @@ import type { FeatureLike } from 'ol/Feature';
import VectorImageLayer from 'ol/layer/VectorImage';
import { airportLayoutStyles } from '~/composables/airport-layout';
import { isVatGlassesActive } from '~/utils/data/vatglasses';
import type { AmdbLayerName } from '@navigraph/amdb';
let vectorLayer: VectorLayer<any>;
let airportsLayer: VectorLayer<any>;
Expand Down Expand Up @@ -260,7 +260,7 @@ watch(map, val => {
imageRatio: 2,
minZoom: 12,
style: function(feature) {
const type = feature.getProperties().type as NavigraphLayoutType;
const type = feature.getProperties().type as AmdbLayerName;
const style = styles[type];
if (typeof style === 'function') return style(feature);
Expand Down Expand Up @@ -288,7 +288,7 @@ watch(map, val => {
imageRatio: 2,
minZoom: 15,
style: function(feature) {
const type = feature.getProperties().type as NavigraphLayoutType;
const type = feature.getProperties().type as AmdbLayerName;
const style = styles[type];
if (typeof style === 'function') return style(feature);
Expand All @@ -314,7 +314,7 @@ watch(map, val => {
type: 'airport-layer',
},
style: function(feature) {
const type = feature.getProperties().type as NavigraphLayoutType;
const type = feature.getProperties().type as AmdbLayerName;
const style = styles[type];
if (typeof style === 'function') return style(feature);
Expand Down
4 changes: 2 additions & 2 deletions src/composables/airport-layout.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Fill, Stroke, Style, Text } from 'ol/style';
import type { PartialRecord } from '~/types';
import type { NavigraphLayoutType } from '~/types/data/navigraph';
import type { FeatureLike } from 'ol/Feature';
import { toRadians } from 'ol/math';
import { useStore } from '~/store';
import type { Options as StyleOptions } from 'ol/style/Style';
import type { AmdbLayerName } from '@navigraph/amdb';

const taxiwayNameRegex = new RegExp('^((Main TWY)|Main|Route) ', 'i');

export const airportLayoutStyles = (): PartialRecord<NavigraphLayoutType, Style | Style[] | ((feature: FeatureLike) => Style | Style[] | undefined)> => {
export const airportLayoutStyles = (): PartialRecord<AmdbLayerName, Style | Style[] | ((feature: FeatureLike) => Style | Style[] | undefined)> => {
const theme = useStore().getCurrentTheme;

const themeStyles = {
Expand Down
103 changes: 59 additions & 44 deletions src/server/api/data/navigraph/airport/[...icao]/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@ import type {
NavigraphAirportData,
NavigraphGate,
NavigraphLayout,
NavigraphLayoutType,
NavigraphRunway,
} from '~/types/data/navigraph';
import type { FeatureCollection, Point } from 'geojson';
import { fromServerLonLat } from '~/utils/backend/vatsim';
import type { AmdbLayerName, AmdbResponseStructure } from '@navigraph/amdb';
import type { PartialRecord } from '~/types';
import { multiLineString } from '@turf/helpers';
import type { Point } from 'geojson';
import nearestPointOnLine from '@turf/nearest-point-on-line';
import { fromServerLonLat } from '~/utils/backend/vatsim';

const allowedProperties: PartialRecord<NavigraphLayoutType, string[]> = {
const allowedProperties: PartialRecord<AmdbLayerName, string[]> = {
taxiwayintersectionmarking: ['idlin'],
taxiwayguidanceline: ['color', 'style', 'idlin'],
taxiwayholdingposition: ['idlin', 'catstop'],
runwaythreshold: ['idthr', 'brngtrue'],
finalapproachandtakeoffarea: ['idrwy'],
verticalpolygonalstructure: ['plysttyp', 'ident'],
deicingarea: ['ident'],
} satisfies {
[K in AmdbLayerName]?: (keyof AmdbResponseStructure[K]['features'][number]['properties'])[]
};

export default defineEventHandler(async (event): Promise<NavigraphAirportData | undefined> => {
Expand All @@ -40,7 +44,7 @@ export default defineEventHandler(async (event): Promise<NavigraphAirportData |
const isLayout = query.layout !== '0' && !!user?.hasFms && user.hasCharts;
const dataFromLayout = isLayout && query.originalData !== '1';

let layout: NavigraphLayout | null | undefined = null;
let layout: Partial<AmdbResponseStructure> | null | undefined = null;

let gates: NavigraphGate[] | undefined;
let runways: NavigraphRunway[] | undefined;
Expand All @@ -49,7 +53,7 @@ export default defineEventHandler(async (event): Promise<NavigraphAirportData |
layout = await getNavigraphLayout({ icao }).catch(() => undefined);
}

if (!dataFromLayout || !isLayout || !layout || !('parkingstandlocation' in layout) || !(layout.parkingstandlocation as FeatureCollection).features.length) {
if (!dataFromLayout || !isLayout || !layout || !layout.standguidanceline?.features.length || !layout.parkingstandarea?.features.length) {
gates = await getNavigraphGates({
user,
event,
Expand All @@ -64,51 +68,62 @@ export default defineEventHandler(async (event): Promise<NavigraphAirportData |

if (!gates || !runways) return;
}

else {
const _layout = layout as NavigraphLayout;
gates = layout.parkingstandarea.features.flatMap(area => {
const { centroid } = (area.properties as unknown as { centroid: Point });

gates = [
..._layout.parkingstandlocation?.features.map(feature => {
const coords = fromServerLonLat((feature.geometry as Point).coordinates);
const subGates = area.properties.idstd?.split('_');

return {
gate_identifier: `${ feature.properties!.idstd }:${ feature.properties!.termref }`,
gate_longitude: coords[0],
gate_latitude: coords[1],
name: feature.properties!.idstd || feature.properties!.termref,
airport_identifier: feature.properties!.idarpt,
};
}) ?? [],
..._layout.standguidanceline?.features.filter(x => x.properties?.midpoint && x.properties.idstd && !x.properties.idstd.includes('_')).map(feature => {
const coords = fromServerLonLat((feature.properties!.midpoint as Point).coordinates);

return {
gate_identifier: `${ feature.properties!.idstd }:${ feature.properties!.termref }`,
gate_longitude: coords[0],
gate_latitude: coords[1],
name: feature.properties!.idstd || feature.properties!.termref,
airport_identifier: feature.properties!.idarpt,
};
}) ?? [],
..._layout.parkingstandarea?.features.filter(x => x.properties?.centroid && x.properties.idstd && !x.properties.idstd.includes('_')).map(feature => {
const coords = fromServerLonLat((feature.properties!.centroid as Point).coordinates);

return {
gate_identifier: `${ feature.properties!.idstd }:${ feature.properties!.termref }`,
if (!subGates) {
// TODO: Handle parkingstandareas with a null idstd

return [];
}

// Generate stands for subgates which have associated standguidancelines
const guidanceLineGates = subGates.flatMap(ident => {
const applicableStandLines = layout.standguidanceline!.features.filter(line => line.properties.termref === area.properties.termref && line.properties.idstd?.split('_').includes(ident));

if (applicableStandLines.length === 0) {
return [];
}

const geometry = multiLineString(applicableStandLines.map(line => line.geometry.coordinates));

const nearestPoint = nearestPointOnLine(geometry, centroid);

const coords = fromServerLonLat(nearestPoint.geometry.coordinates);

return [{
gate_identifier: `${ ident }:${ area.properties.termref }`,
gate_longitude: coords[0],
gate_latitude: coords[1],
name: feature.properties!.idstd || feature.properties!.termref,
airport_identifier: feature.properties!.idarpt,
};
}) ?? [],
];
name: ident,
airport_identifier: area.properties.idarpt,
}];
});

gates = gates.filter((x, index) => x.name && !(gates as NavigraphGate[]).some((y, yIndex) => y.gate_identifier === x.gate_identifier && index > yIndex));
const remainingGates = subGates.filter(ident => !guidanceLineGates.find(item => item.name === ident));

delete layout.parkingstandlocation;
const coords = fromServerLonLat(centroid.coordinates);

// For all subGates which have no associated standguidancelines, place a gate at the centroid of the parkingstandarea
const centroidGates = remainingGates.map(ident => ({
gate_identifier: `${ ident }:${ area.properties.termref }`,
gate_longitude: coords[0],
gate_latitude: coords[1],
name: ident,
airport_identifier: area.properties.idarpt,
}));

return [...guidanceLineGates, ...centroidGates];
});

const _layout = layout as NavigraphLayout;

Object.entries(_layout).forEach(([key, value]) => {
const property = allowedProperties[key as NavigraphLayoutType];
const property = allowedProperties[key as AmdbLayerName];
if (!property?.length) value.features.forEach(feature => feature.properties = {});
else {
value.features.forEach(feature => {
Expand All @@ -128,7 +143,7 @@ export default defineEventHandler(async (event): Promise<NavigraphAirportData |
return {
airport: icao,
runways: runways ?? [],
gates: gates as NavigraphGate[],
layout: layout as NavigraphLayout,
gates,
layout: layout ?? undefined,
};
});
40 changes: 2 additions & 38 deletions src/types/data/navigraph.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Pixel } from 'ol/pixel';
import type { PartialRecord } from '~/types';
import type { FeatureCollection } from 'geojson';
import type { AmdbLayerName } from '@navigraph/amdb';

export interface NavigraphGate {
gate_identifier: string;
Expand Down Expand Up @@ -31,41 +32,4 @@ export interface NavigraphAirportData {
layout?: NavigraphLayout;
}

export type NavigraphLayoutType = | 'aerodromereferencepoint'
| 'apronelement'
| 'arrestinggearlocation'
| 'asrnedge'
| 'asrnnode'
| 'blastpad'
| 'constructionarea'
| 'deicingarea'
| 'finalapproachandtakeoffarea'
| 'frequencyarea'
| 'helipadthreshold'
| 'hotspot'
| 'landandholdshortoperationlocation'
| 'paintedcenterline'
| 'parkingstandarea'
| 'parkingstandlocation'
| 'runwaydisplacedarea'
| 'runwayelement'
| 'runwayexitline'
| 'runwayintersection'
| 'runwaymarking'
| 'runwayshoulder'
| 'runwaythreshold'
| 'serviceroad'
| 'standguidanceline'
| 'stopway'
| 'taxiwayelement'
| 'taxiwayguidanceline'
| 'taxiwayholdingposition'
| 'taxiwayintersectionmarking'
| 'taxiwayshoulder'
| 'touchdownliftoffarea'
| 'verticallinestructure'
| 'verticalpointstructure'
| 'verticalpolygonalstructure'
| 'water';

export type NavigraphLayout = PartialRecord<NavigraphLayoutType, FeatureCollection>;
export type NavigraphLayout = PartialRecord<AmdbLayerName, FeatureCollection>;
13 changes: 7 additions & 6 deletions src/utils/backend/navigraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import {
import { fromServerLonLat } from '~/utils/backend/vatsim';
import { handleH3Exception } from '~/utils/backend/h3';
import type { FullUser } from '~/utils/backend/user';
import type { NavigraphGate, NavigraphLayout, NavigraphLayoutType, NavigraphRunway } from '~/types/data/navigraph';
import type { NavigraphGate, NavigraphRunway } from '~/types/data/navigraph';
import { $fetch } from 'ofetch';
import { supportedNavigraphLayouts } from '~/utils/shared/vatsim';
import type { AmdbLayerName, AmdbResponseStructure } from '@navigraph/amdb';

function base64URLEncode(str: Buffer) {
return str
Expand Down Expand Up @@ -273,12 +274,12 @@ export async function getNavigraphGates({ user, icao, event }: {

export async function getNavigraphLayout({
icao,
exclude = ['asrnedge', 'asrnnode', 'aerodromereferencepoint', 'hotspot', 'paintedcenterline', 'verticalpointstructure', 'water'],
include = [...supportedNavigraphLayouts, 'parkingstandlocation'],
exclude = ['asrnedge', 'asrnnode', 'aerodromereferencepoint', 'parkingstandlocation', 'hotspot', 'paintedcenterline', 'verticalpointstructure', 'water'],
include = supportedNavigraphLayouts, // Note that when exclude is provided, the include property will do nothing
}: {
icao: string;
exclude?: NavigraphLayoutType[];
include?: NavigraphLayoutType[];
exclude?: AmdbLayerName[];
include?: AmdbLayerName[];
}) {
await checkNavigraphToken();
const url = new URL(`https://amdb.api.navigraph.com/v1/${ icao }`);
Expand All @@ -287,7 +288,7 @@ export async function getNavigraphLayout({
if (exclude.length) url.searchParams.set('exclude', exclude.join(','));
else if (include.length) url.searchParams.set('include', include.join(','));

return $fetch<NavigraphLayout>(url.toString(), {
return $fetch<Partial<AmdbResponseStructure>>(url.toString(), {
headers: {
Authorization: `Bearer ${ navigraphAccessKey.token }`,
},
Expand Down
5 changes: 3 additions & 2 deletions src/utils/shared/vatsim.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { VatsimPilot, VatsimShortenedAircraft } from '~/types/data/vatsim';
import type { NavigraphGate, NavigraphLayoutType } from '~/types/data/navigraph';
import type { NavigraphGate } from '~/types/data/navigraph';
import type { Coordinate } from 'ol/coordinate';
import type { GeoJSONFeature } from 'ol/format/GeoJSON';
import type { AmdbLayerName } from '@navigraph/amdb';

export function adjustPilotLonLat(pilot: VatsimShortenedAircraft | VatsimPilot): Coordinate {
let lonAdjustment = 0;
Expand Down Expand Up @@ -116,7 +117,7 @@ export function getTraconSuffix(tracon: GeoJSONFeature): string | null {
return null;
}

export const supportedNavigraphLayouts: NavigraphLayoutType[] = [
export const supportedNavigraphLayouts: AmdbLayerName[] = [
'parkingstandarea',
'apronelement',
'arrestinggearlocation',
Expand Down
Loading

0 comments on commit 3d93247

Please sign in to comment.