diff --git a/.vscode/settings.json b/.vscode/settings.json index 8d41211244a..534200a9b16 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,30 +10,30 @@ }, "[json]": { "editor.defaultFormatter": "vscode.json-language-features", - "editor.formatOnSave": true + "editor.formatOnSave": false }, "[javascript]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint", - "editor.formatOnSave": true + "editor.formatOnSave": false }, "[typescript]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint", - "editor.formatOnSave": true + "editor.formatOnSave": false }, "[javascriptreact]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint", - "editor.formatOnSave": true + "editor.formatOnSave": false }, "[typescriptreact]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint", - "editor.formatOnSave": true + "editor.formatOnSave": false }, "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.formatOnSave": true }, "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit" + "source.fixAll.eslint":"never" }, "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, diff --git a/fbw-a32nx/src/systems/simbridge-client/src/components/Viewer.ts b/fbw-a32nx/src/systems/simbridge-client/src/components/Viewer.ts index b6df2280ba1..19167484db4 100644 --- a/fbw-a32nx/src/systems/simbridge-client/src/components/Viewer.ts +++ b/fbw-a32nx/src/systems/simbridge-client/src/components/Viewer.ts @@ -60,6 +60,60 @@ export class Viewer { throw new Error('File name or page number missing'); } + /** + * Used to retrieve a streamable image of specified page within a given PDF URL + * @param url required field, URL link to the pdf + * @param pageNumber required field, The page of the PDF file + * @returns a Blob + */ + public static async getPDFPageFromUrl(url: string, pageNumber: number): Promise { + if (!ClientState.getInstance().isConnected()) { + throw new Error('SimBridge is not connected.'); + } + if (url || pageNumber) { + const encodedUrl = encodeURIComponent(url); + const response = await fetchWithTimeout(`${getSimBridgeUrl()}/api/v1/utility/pdf/fromUrl?encodedUrl=${encodedUrl}&pagenumber=${pageNumber}`); + if (response.ok) { + return response.blob(); + } + throw new Error(`SimBridge Error: ${response.status}`); + } + throw new Error('File name or page number missing'); + } + + /** + * Used to retrieve a URL to the rendered image of the PDF page at the specified URL. + * It internally calls getPDFPageFromUrl and then calls createObjectURL(). + * @see https://developer.mozilla.org/en-US/docs/web/api/url/createobjecturl + * @param url required field, URL link to the pdf + * @param pageNumber required field, The page of the PDF file + * @returns url to the image (object blob) of the PDF page + */ + public static async getImageUrlFromPdfUrl(url: string, pageNumber: number): Promise { + const blob = await Viewer.getPDFPageFromUrl(url, pageNumber); + return URL.createObjectURL(blob); + } + + /** + * Retrieve the number of pages within a PDF file at the specified URL + * @param url required field, URL link to the pdf + * @returns A number + */ + public static async getPDFPageCountFromUrl(url: string): Promise { + if (!ClientState.getInstance().isConnected()) { + throw new Error('SimBridge is not connected.'); + } + if (url) { + const encodedUrl = encodeURIComponent(url); + const response = await fetchWithTimeout(`${getSimBridgeUrl()}/api/v1/utility/pdf/fromUrl/numpages?encodedUrl=${encodedUrl}`); + if (response.ok) { + return response.json(); + } + throw new Error(`SimBridge Error: ${response.status}`); + } + throw new Error('File name or page number missing'); + } + /** * Used to retrieve a list of filenames within the PDF folder * @returns an Array of strings diff --git a/fbw-common/src/systems/instruments/src/EFB/Apis/ChartFox/ChartFox.tsx b/fbw-common/src/systems/instruments/src/EFB/Apis/ChartFox/ChartFox.tsx index e3173cfb484..ac8e0e488e1 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Apis/ChartFox/ChartFox.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Apis/ChartFox/ChartFox.tsx @@ -1,99 +1,9 @@ // Copyright (c) 2023-2024 FlyByWire Simulations // SPDX-License-Identifier: GPL-3.0 -export type ChartFoxChart = { - name: string, - type: string, - runway: null | string, - url: string, -} +import React, { useContext } from 'react'; +import { ChartFoxData } from 'shared/src/chartfox/client'; -export type ChartFoxAirportCharts = { - arrival: ChartFoxChart[], - approach: ChartFoxChart[], - airport: ChartFoxChart[], - departure: ChartFoxChart[], - reference: ChartFoxChart[], -} +export const ChartFoxContext = React.createContext(undefined!); -export class ChartFoxClient { - private static token = process.env.CHARTFOX_SECRET; - - public static sufficientEnv() { - return !!ChartFoxClient.token; - } - - public async getChartList(icao: string): Promise { - if (ChartFoxClient.sufficientEnv()) { - if (icao.length === 4) { - try { - const chartJsonResp = await fetch(`https://chartfox.org/api/charts/grouped/${icao}?token=${ChartFoxClient.token}`, { method: 'POST' }); - - if (chartJsonResp.ok) { - const chartJson = await chartJsonResp.json(); - - const groundLayoutArray: ChartFoxChart[] = chartJson.charts['2'].charts.map((charts) => ({ - name: charts.name, - type: charts.type, - runway: charts.runway, - url: charts.url, - })); - - const generalArray: ChartFoxChart[] = chartJson.charts['0'].charts.map((charts) => ({ - name: charts.name, - type: charts.type, - runway: charts.runway, - url: charts.url, - })); - - const textualArray: ChartFoxChart[] = chartJson.charts['1'].charts.map((charts) => ({ - name: charts.name, - type: charts.type, - runway: charts.runway, - url: charts.url, - })); - - const sidArray: ChartFoxChart[] = chartJson.charts['6'].charts.map((charts) => ({ - name: charts.name, - type: charts.type, - runway: charts.runway, - url: charts.url, - })); - - const starArray: ChartFoxChart[] = chartJson.charts['7'].charts.map((charts) => ({ - name: charts.name, - type: charts.type, - runway: charts.runway, - url: charts.url, - })); - - const approachArray: ChartFoxChart[] = chartJson.charts['8'].charts.map((charts) => ({ - name: charts.name, - type: charts.type, - runway: charts.runway, - url: charts.url, - })); - - return { - arrival: starArray, - approach: approachArray, - airport: groundLayoutArray, - departure: sidArray, - reference: generalArray.concat(textualArray), - }; - } - } catch (_) { - console.log('Token Authentication Failed. #CF101'); - } - } - } - - return { - arrival: [], - approach: [], - airport: [], - departure: [], - reference: [], - }; - } -} +export const useChartFox = () => useContext(ChartFoxContext); diff --git a/fbw-common/src/systems/instruments/src/EFB/Apis/ChartFox/Components/Authentication.tsx b/fbw-common/src/systems/instruments/src/EFB/Apis/ChartFox/Components/Authentication.tsx new file mode 100644 index 00000000000..f4247b767fe --- /dev/null +++ b/fbw-common/src/systems/instruments/src/EFB/Apis/ChartFox/Components/Authentication.tsx @@ -0,0 +1,44 @@ +// Copyright (c) 2023-2024 FlyByWire Simulations +// SPDX-License-Identifier: GPL-3.0 + +import React from 'react'; +import { ChartFoxStatus } from '@flybywiresim/fbw-sdk'; +import { useHistory } from 'react-router-dom'; +import { t } from '../../../Localization/translation'; +import { useChartFox } from '../ChartFox'; + +export const ChartFoxAuthUIWrapper = ({ children }) => { + const chartFox = useChartFox(); + + if (chartFox.status === ChartFoxStatus.LoggedIn) { + return children; + } + + return ; +}; + +export const ChartFoxAuthRedirectUI = () => { + const history = useHistory(); + + const handleGoToThirdPartySettings = () => { + history.push('/settings/3rd-party-options'); + }; + + return ( +
+
+

{t('NavigationAndCharts.ChartFox.GoToThirdPartyOptions.Title')}

+ + +
+
+ ); +}; diff --git a/fbw-common/src/systems/instruments/src/EFB/Apis/ChartFox/Samples.tsx b/fbw-common/src/systems/instruments/src/EFB/Apis/ChartFox/Samples.tsx new file mode 100644 index 00000000000..d0947f0b2d7 --- /dev/null +++ b/fbw-common/src/systems/instruments/src/EFB/Apis/ChartFox/Samples.tsx @@ -0,0 +1 @@ +export const sampleData = {}; diff --git a/fbw-common/src/systems/instruments/src/EFB/Efb.tsx b/fbw-common/src/systems/instruments/src/EFB/Efb.tsx index c3c390584a1..747b52efe59 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Efb.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Efb.tsx @@ -4,8 +4,9 @@ import React, { useEffect, useState } from 'react'; import { useSimVar, useInterval, useInteractionEvent, usePersistentNumberProperty, usePersistentProperty, NavigraphClient, - SentryConsentState, SENTRY_CONSENT_KEY, + ChartFoxClient, SentryConsentState, SENTRY_CONSENT_KEY, FailureDefinition, + ChartFoxStatus, } from '@flybywiresim/fbw-sdk'; import { Redirect, Route, Switch, useHistory } from 'react-router-dom'; import { Battery } from 'react-bootstrap-icons'; @@ -14,12 +15,13 @@ import { distanceTo } from 'msfs-geo'; import { ErrorBoundary } from 'react-error-boundary'; import { MemoryRouter as Router } from 'react-router'; import { Provider } from 'react-redux'; +import { Tooltip } from './UtilComponents/TooltipWrapper'; import { Error as ErrorIcon } from './Assets/Error'; import { FailuresOrchestratorProvider } from './failures-orchestrator-provider'; import { AlertModal, ModalContainer, ModalProvider, useModals } from './UtilComponents/Modals/Modals'; import { FbwLogo } from './UtilComponents/FbwLogo'; -import { Tooltip } from './UtilComponents/TooltipWrapper'; import { NavigraphContext } from './Apis/Navigraph/Navigraph'; +import { ChartFoxContext } from './Apis/ChartFox/ChartFox'; import { StatusBar } from './StatusBar/StatusBar'; import { ToolBar } from './ToolBar/ToolBar'; import { Dashboard } from './Dashboard/Dashboard'; @@ -99,6 +101,12 @@ export const Efb = () => { const [navigraph] = useState(() => new NavigraphClient()); + const [chartFoxStatus, setChartFoxStatus] = useState(ChartFoxStatus.LoggedOut); + const handleChartFoxUpdate = ({ status }) => { + setChartFoxStatus(status); + }; + const [chartFoxClient] = useState(() => new ChartFoxClient({ onUpdate: handleChartFoxUpdate })); + const dispatch = useAppDispatch(); const [dc2BusIsPowered] = useSimVar('L:A32NX_ELEC_DC_2_BUS_IS_POWERED', 'bool'); @@ -315,43 +323,45 @@ export const Efb = () => { case PowerStates.LOADED: return ( - - -
- - - - -
- -
- - - - - - - - - - - - - - - - + + + +
+ + + + +
+ +
+ + + + + + + + + + + + + + + + +
-
- + + ); default: diff --git a/fbw-common/src/systems/instruments/src/EFB/Localization/data/en.json b/fbw-common/src/systems/instruments/src/EFB/Localization/data/en.json index 36100374aba..eab4fea471e 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Localization/data/en.json +++ b/fbw-common/src/systems/instruments/src/EFB/Localization/data/en.json @@ -240,6 +240,23 @@ "ScanTheQrCodeOrOpen": "Scan the QR Code or Open", "Title": "Navigraph" }, + "ChartFox": { + "AirportDoesNotExist": "Airport Does Not Exist", + "GoToThirdPartyOptions": { + "Title": "Go to 3rd Party Options to link ChartFox account", + "Button": "3rd Party Options" + }, + "AuthenticateWithChartFox": "Authenticate with ChartFox", + "LoadingMsg": "Loading", + "NoAirportSelected": "No Airport Selected", + "ResetChartFoxAuthentication": "Reset ChartFox Authentication", + "Title": "ChartFox", + "EstablishingConnection": "Establishing Connection", + "FailedToEstablishConnection": "Failed to Establish Connection.", + "ConnectionRequiredForPdf": "Connection is required for PDF charts.", + "Retry": "Retry", + "ContinueInBrowser": "Continue login in the browser window" + }, "PinnedCharts": { "Delete": "Delete", "Edit": "Edit", @@ -607,6 +624,13 @@ "Title": "Navigraph Account Login" } }, + "ChartFoxLogin": { + "SettingTitle": "ChartFox Login", + "Connected": "Connected", + "LogIn": "Log In", + "LogOut": "Log Out", + "ContinueInBrowser": "Continue login in the browser window" + }, "OverrideSimBriefUserID": "Override SimBrief User ID", "YourSimBriefPilotIdHasBeenValidatedAndUpdatedTo": "The override SimBrief PilotID has been validated and updated to", "PleaseCheckThatYouHaveCorrectlyEnteredYourSimBriefUsernameOrPilotId": "Please check that you have correctly entered your SimBrief username or pilot ID.", diff --git a/fbw-common/src/systems/instruments/src/EFB/Navigation/Navigation.tsx b/fbw-common/src/systems/instruments/src/EFB/Navigation/Navigation.tsx index 3d7b0a3858d..7642a130124 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Navigation/Navigation.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Navigation/Navigation.tsx @@ -17,6 +17,7 @@ import { } from 'react-bootstrap-icons'; import { useSimVar } from '@flybywiresim/fbw-sdk'; import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch'; +import { ChartFoxPage } from './Pages/ChartFoxPage/ChartFoxPage'; import { t } from '../Localization/translation'; import { TooltipWrapper } from '../UtilComponents/TooltipWrapper'; // import { DrawableCanvas } from '../UtilComponents/DrawableCanvas'; @@ -36,11 +37,12 @@ import { import { PageLink, PageRedirect, TabRoutes } from '../Utils/routing'; import { Navbar } from '../UtilComponents/Navbar'; import { NavigraphPage } from './Pages/NavigraphPage/NavigraphPage'; -import { getPdfUrl, LocalFilesPage } from './Pages/LocalFilesPage/LocalFilesPage'; +import { LocalFilesPage } from './Pages/LocalFilesPage/LocalFilesPage'; import { PinnedChartUI } from './Pages/PinnedChartsPage'; export const navigationTabs: (PageLink & {associatedTab: NavigationTab})[] = [ { name: 'Navigraph', alias: '', component: , associatedTab: NavigationTab.NAVIGRAPH }, + { name: 'ChartFox', alias: '', component: , associatedTab: NavigationTab.CHARTFOX }, { name: 'Local Files', alias: '', component: , associatedTab: NavigationTab.LOCAL_FILES }, { name: 'Pinned Charts', alias: '', component: , associatedTab: NavigationTab.PINNED_CHARTS }, ]; @@ -50,8 +52,9 @@ export const Navigation = () => { if (navigationTabs) { navigationTabs[0].alias = t('NavigationAndCharts.Navigraph.Title'); - navigationTabs[1].alias = t('NavigationAndCharts.LocalFiles.Title'); - navigationTabs[2].alias = t('NavigationAndCharts.PinnedCharts.Title'); + navigationTabs[1].alias = t('NavigationAndCharts.ChartFox.Title'); + navigationTabs[2].alias = t('NavigationAndCharts.LocalFiles.Title'); + navigationTabs[3].alias = t('NavigationAndCharts.PinnedCharts.Title'); } return ( @@ -94,7 +97,6 @@ export const ChartViewer = () => { isFullScreen, chartDimensions, chartLinks, - chartId, pagesViewable, currentPage, chartPosition, @@ -190,22 +192,10 @@ export const ChartViewer = () => { } }, [chartRef, chartDimensions]); - useEffect(() => { - if (pagesViewable > 1) { - getPdfUrl(chartId, currentPage) - .then((url) => { - dispatch(editTabProperty({ tab: currentTab, chartName: { light: url, dark: url } })); - }) - .catch((error) => { - console.error(`Error: ${error}`); - }); - } - }, [currentPage]); - const transformRef = useRef(null); const planeRef = useRef(null); - if (!chartLinks.light || !chartLinks.dark) { + if (provider !== ChartProvider.CHARTFOX && (!chartLinks.light || !chartLinks.dark)) { return (
{ + const NO_RUNWAY_NAME = 'NONE'; + const [runwaySet, setRunwaySet] = useState>(new Set()); + const [organizedCharts, setOrganizedCharts] = useState([]); + + const dispatch = useAppDispatch(); + + const { chartId, searchQuery, selectedTabIndex } = useAppSelector((state) => state.navigationTab[NavigationTab.CHARTFOX]); + const { pinnedCharts } = useAppSelector((state) => state.navigationTab); + + useEffect(() => { + if (selectedTab.bundleRunways) { + const runwayNumbers: string[] = []; + + selectedTab.charts.forEach((chart) => { + if (chart.runways.length !== 0) { + chart.runways.forEach((runway) => { + runwayNumbers.push(runway); + }); + } else { + runwayNumbers.push(NO_RUNWAY_NAME); + } + }); + + setRunwaySet(new Set(runwayNumbers)); + } else { + setRunwaySet(new Set()); + } + }, [selectedTab.charts]); + + useEffect(() => { + if (selectedTab.bundleRunways) { + const organizedRunwayCharts: RunwayOrganizedChart[] = []; + + runwaySet.forEach((runway) => { + organizedRunwayCharts.push({ + name: runway, + charts: selectedTab.charts.filter( + (chart) => chart.runways.includes(runway) + || (chart.runways.length === 0 && runway === NO_RUNWAY_NAME), + ), + }); + }); + + setOrganizedCharts(organizedRunwayCharts); + } else { + setOrganizedCharts([]); + } + }, [runwaySet]); + + const handleChartClick = async (groupedChart: ChartFoxGroupedChart) => { + dispatch(editTabProperty({ tab: NavigationTab.CHARTFOX, chartId: groupedChart.id })); + dispatch(editTabProperty({ tab: NavigationTab.CHARTFOX, currentPage: 1 })); + dispatch(editTabProperty({ tab: NavigationTab.CHARTFOX, chartDimensions: { width: undefined, height: undefined } })); + + // TODO: convert translation matrix into bounding box + // dispatch(setBoundingBox(chart.boundingBox)); + + dispatch(setProvider(ChartProvider.CHARTFOX)); + }; + + if (loading) { + return ( +
+ +
+ ); + } + + if (!selectedTab.charts.length) { + return ( +
+

{t('NavigationAndCharts.ThereAreNoChartsToDisplay')}

+
+ ); + } + + return ( +
+ {selectedTab.bundleRunways + ? ( + <> + {organizedCharts.map((item) => ( +
+ {item.name} + {item.charts.map((chart) => ( +
handleChartClick(chart)} + key={chart.id} + > +
+
+
{ + event.stopPropagation(); + + if (isChartPinned(chart.id)) { + dispatch(removedPinnedChart({ chartId: chart.id })); + } else { + dispatch(addPinnedChart({ + chartId: chart.id, + chartName: { light: chart.name, dark: chart.name }, + title: searchQuery, + subTitle: chart.name, + tabIndex: selectedTabIndex, + timeAccessed: 0, + tag: selectedTab.name, + provider: ChartProvider.CHARTFOX, + pagesViewable: 1, + // TODO: convert translation matrix into bounding box + // boundingBox: chart.boundingBox, + pageIndex: navigationTabs.findIndex((tab) => tab.associatedTab === NavigationTab.CHARTFOX), + })); + } + }} + > + { + pinnedCharts.some((pinnedChart) => pinnedChart.chartId === chart.id) + ? + : + } +
+
+
+ {chart.name} + + {/* TODO: Figure out what to use instead of indexNumber */} + {/* {chart.indexNumber} */} + +
+
+ ))} +
+ ))} + + ) + : ( + <> + {selectedTab.charts.map((chart) => ( +
handleChartClick(chart)} + key={chart.id} + > +
+
+
{ + event.stopPropagation(); + + if (isChartPinned(chart.id)) { + dispatch(removedPinnedChart({ chartId: chart.id })); + } else { + dispatch(addPinnedChart({ + chartId: chart.id, + chartName: { light: chart.name, dark: chart.name }, + title: searchQuery, + subTitle: chart.name, + tabIndex: selectedTabIndex, + timeAccessed: 0, + tag: selectedTab.name, + provider: ChartProvider.CHARTFOX, + pagesViewable: 1, + // TODO: convert translation matrix into bounding box + // boundingBox: chart.boundingBox, + pageIndex: navigationTabs.findIndex((tab) => tab.associatedTab === NavigationTab.CHARTFOX), + })); + } + }} + > + { + pinnedCharts.some((pinnedChart) => pinnedChart.chartId === chart.id) + ? + : + } +
+
+
+ {chart.name} + + {/* TODO: Figure out what to use instead of indexNumber */} + {/* {chart.indexNumber} */} + +
+
+ ))} + + )} +
+ ); +}; diff --git a/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/ChartFoxPage/ChartFoxChartUI.tsx b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/ChartFoxPage/ChartFoxChartUI.tsx new file mode 100644 index 00000000000..4174d98ab46 --- /dev/null +++ b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/ChartFoxPage/ChartFoxChartUI.tsx @@ -0,0 +1,222 @@ +// Copyright (c) 2021-2023 FlyByWire Simulations +// +// SPDX-License-Identifier: GPL-3.0 + +import React, { useEffect, useState } from 'react'; +import { ArrowReturnRight } from 'react-bootstrap-icons'; +import { toast } from 'react-toastify'; +import { ClientState, Viewer, ChartFoxAirportCharts, emptyChartFoxCharts } from '@flybywiresim/fbw-sdk'; +import { useChartFox } from '../../../Apis/ChartFox/ChartFox'; + +import { t } from '../../../Localization/translation'; +import { ChartFoxChartSelector, OrganizedChart } from './ChartFoxChartSelector'; +import { ChartFileType, NavigationTab, editTabProperty } from '../../../Store/features/navigationPage'; +import { isSimbriefDataLoaded } from '../../../Store/features/simBrief'; +import { useAppDispatch, useAppSelector } from '../../../Store/store'; +import { SelectGroup, SelectItem } from '../../../UtilComponents/Form/Select'; +import { SimpleInput } from '../../../UtilComponents/Form/SimpleInput/SimpleInput'; +import { ScrollableContainer } from '../../../UtilComponents/ScrollableContainer'; +import { ChartFoxChartViewer } from './ChartFoxChartViewer'; + +export const getPdfInfo = async (url: string, pageNumber: number): Promise<{imageUrl: string, numPages: number}> => { + const id = 'loading-file'; + try { + toast.loading(t('NavigationAndCharts.LoadingPdf'), { toastId: id, pauseOnFocusLoss: false }); + const imageUrl = await Viewer.getImageUrlFromPdfUrl(url, pageNumber); + const numPages = await Viewer.getPDFPageCountFromUrl(url); + toast.update(id, { toastId: id, render: '', type: 'success', isLoading: false, pauseOnFocusLoss: false }); + toast.dismiss(id); + return { imageUrl, numPages }; + } catch (err) { + toast.dismiss(id); + toast.error(t('NavigationAndCharts.LoadingPdfFailed'), { autoClose: 1000 }); + return Promise.reject(); + } +}; + +export const ChartFoxChartUI = () => { + const dispatch = useAppDispatch(); + + const { client } = useChartFox(); + + const [statusBarInfo, setStatusBarInfo] = useState(''); + + const [icaoAndNameDisagree, setIcaoAndNameDisagree] = useState(false); + const [chartListDisagrees, setChartListDisagrees] = useState(false); + + const [charts, setCharts] = useState({ + arrival: [], + approach: [], + airport: [], + departure: [], + reference: [], + }); + + const [organizedCharts, setOrganizedCharts] = useState([ + { name: 'GEN', charts: charts.reference }, + { name: 'GND', charts: charts.airport }, + { name: 'SID', charts: charts.departure }, + { name: 'STAR', charts: charts.arrival }, + { name: 'APP', charts: charts.approach }, + ]); + + const { isFullScreen, searchQuery, chartId, selectedTabIndex, currentPage } = useAppSelector((state) => state.navigationTab[NavigationTab.CHARTFOX]); + + useEffect(() => { + setOrganizedCharts([ + { name: 'GEN', charts: charts.reference }, + { name: 'GND', charts: charts.airport, bundleRunways: charts.airport.some((chart) => chart.runways.length > 0) }, + { name: 'SID', charts: charts.departure, bundleRunways: charts.departure.some((chart) => chart.runways.length > 0) }, + { name: 'STAR', charts: charts.arrival, bundleRunways: charts.arrival.some((chart) => chart.runways.length > 0) }, + { name: 'APP', charts: charts.approach, bundleRunways: charts.approach.some((chart) => chart.runways.length > 0) }, + ]); + }, [charts]); + + const fetchCharts = async () => { + const { sourceUrl, sourceUrlType } = await client.getChart(chartId); + let imageUrl = sourceUrl; + let numPages = 1; + // For PDFs, chartName will be the original PDF link. This will be needed for pagination later. + // We also want to make sure chartName.fileType is set so we know if it's a PDF even if there + // is no client connection. + dispatch(editTabProperty({ tab: NavigationTab.CHARTFOX, chartName: { light: sourceUrl, dark: sourceUrl, fileType: sourceUrlType } })); + setChartListDisagrees(true); + if (sourceUrlType === ChartFileType.Pdf) { + if (!ClientState.getInstance().isConnected()) { + return; + } + try { + ({ imageUrl, numPages } = await getPdfInfo(sourceUrl, currentPage)); + } catch (_) { + return; + } + } + setChartListDisagrees(false); + dispatch(editTabProperty({ tab: NavigationTab.CHARTFOX, pagesViewable: numPages })); + // For PDFs, chartLinks will be URIs to the converted image data for the current page. + dispatch(editTabProperty({ tab: NavigationTab.CHARTFOX, chartLinks: { light: imageUrl, dark: imageUrl, fileType: sourceUrlType } })); + }; + + useEffect(() => { + if (chartId) { + fetchCharts(); + } + }, [chartId, currentPage]); + + const handleIcaoChange = async (value: string) => { + if (value.length !== 4) { + setStatusBarInfo(''); + setCharts(emptyChartFoxCharts); + return; + } + const newValue = value.toUpperCase(); + dispatch(editTabProperty({ tab: NavigationTab.CHARTFOX, searchQuery: newValue })); + + setIcaoAndNameDisagree(true); + setChartListDisagrees(true); + const chartIndex = await client.getChartIndex(newValue); + setStatusBarInfo(chartIndex?.name || t('NavigationAndCharts.ChartFox.AirportDoesNotExist')); + if (chartIndex?.groupedCharts) { + setCharts(chartIndex.groupedCharts); + } + setIcaoAndNameDisagree(false); + setChartListDisagrees(false); + }; + + useEffect(() => { + handleIcaoChange(searchQuery); + }, []); + + const loading = (!statusBarInfo.length || icaoAndNameDisagree || chartListDisagrees) && searchQuery.length === 4; + + const getStatusBarText = () => { + if (searchQuery.length !== 4) { + return t('NavigationAndCharts.ChartFox.NoAirportSelected'); + } + if (loading) { + return t('NavigationAndCharts.PleaseWait'); + } + return statusBarInfo; + }; + + const { altIcao, departingAirport, arrivingAirport } = useAppSelector((state) => state.simbrief.data); + const simbriefDataLoaded = isSimbriefDataLoaded(); + + return ( +
+ <> + {!isFullScreen && ( +
+
+ + + {isSimbriefDataLoaded() && ( + + handleIcaoChange(departingAirport)} + > + {t('NavigationAndCharts.From')} + + handleIcaoChange(arrivingAirport)} + > + {t('NavigationAndCharts.To')} + + {!!altIcao && ( + handleIcaoChange(altIcao)} + > + {t('NavigationAndCharts.Altn')} + + )} + + )} +
+ +
+ +
+ {getStatusBarText()} +
+
+ +
+ + {organizedCharts.map((organizedChart, index) => ( + dispatch(editTabProperty({ tab: NavigationTab.CHARTFOX, selectedTabIndex: index }))} + key={organizedChart.name} + className="flex w-full justify-center" + > + {organizedChart.name} + + ))} + + + + +
+
+ )} + + fetchCharts()} /> + +
+ ); +}; diff --git a/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/ChartFoxPage/ChartFoxChartViewer.tsx b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/ChartFoxPage/ChartFoxChartViewer.tsx new file mode 100644 index 00000000000..637b5c001b9 --- /dev/null +++ b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/ChartFoxPage/ChartFoxChartViewer.tsx @@ -0,0 +1,93 @@ +import React, { useEffect, useState } from 'react'; +import { CloudArrowDown } from 'react-bootstrap-icons'; +import { ClientState } from '@flybywiresim/fbw-sdk'; +import { useAppSelector } from '../../../Store/store'; +import { ChartFileType, NavigationTab } from '../../../Store/features/navigationPage'; +import { t } from '../../../Localization/translation'; +import { ChartViewer } from '../../Navigation'; + +enum ConnectionState { + ATTEMPTING, + FAILED, + ESTABLISHED, +} + +export const ChartFoxChartViewer = ({ onConnectionRetry }) => { + const [connectionState, setConnectionState] = useState(ConnectionState.ATTEMPTING); + + const { chartName } = useAppSelector((state) => state.navigationTab[NavigationTab.CHARTFOX]); + + const tryConnection = async () => { + if (!ClientState.getInstance().isConnected()) { + window.setTimeout(() => { + setConnectionState(ConnectionState.FAILED); + }, 500); + return; + } + setConnectionState(ConnectionState.ESTABLISHED); + }; + + const handleConnectionRetry = () => { + setConnectionState(ConnectionState.ATTEMPTING); + onConnectionRetry(); + tryConnection(); + }; + + useEffect(() => { + tryConnection(); + }, [chartName]); + + // using chartName instead of chartLinks because PDF failures won't set chartLinks + if (!chartName.light || !chartName.dark) { + return ( +
+

{t('NavigationAndCharts.ThereIsNoChartToDisplay')}

+
+ ); + } + + // Don't require a client connection for image-based charts + if (chartName.fileType !== ChartFileType.Pdf) { + return ; + } + + // ---- PDF territory ---- + switch (connectionState) { + case ConnectionState.ATTEMPTING: + return ( +
+

{t('NavigationAndCharts.ChartFox.EstablishingConnection')}

+ +
+ ); + case ConnectionState.ESTABLISHED: + return ; + case ConnectionState.FAILED: + return ( +
+
+

{t('NavigationAndCharts.ChartFox.FailedToEstablishConnection')}

+ +
+
+ ); + default: return <>; + } +}; diff --git a/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/ChartFoxPage/ChartFoxPage.tsx b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/ChartFoxPage/ChartFoxPage.tsx new file mode 100644 index 00000000000..b3380b82e81 --- /dev/null +++ b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/ChartFoxPage/ChartFoxPage.tsx @@ -0,0 +1,13 @@ +// Copyright (c) 2021-2023 FlyByWire Simulations +// +// SPDX-License-Identifier: GPL-3.0 + +import React from 'react'; +import { ChartFoxAuthUIWrapper } from '../../../Apis/ChartFox/Components/Authentication'; +import { ChartFoxChartUI } from './ChartFoxChartUI'; + +export const ChartFoxPage = () => ( + + + +); diff --git a/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFileChartSelector.tsx b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFileChartSelector.tsx index 6b8d02f87f5..af5deb67086 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFileChartSelector.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFileChartSelector.tsx @@ -19,7 +19,7 @@ import { } from '../../../Store/features/navigationPage'; import { useAppDispatch, useAppSelector } from '../../../Store/store'; import { navigationTabs } from '../../Navigation'; -import { getImageUrl, getPdfUrl } from './LocalFilesPage'; +import { getImageUrl, getPdfImageUrl } from './LocalFilesPage'; export type LocalFileChart = { fileName: string; @@ -67,7 +67,7 @@ export const LocalFileChartSelector = ({ selectedTab, loading }: LocalFileChartS const getChartResourceUrl = async (chart: LocalFileChart): Promise => { try { if (chart.type === 'PDF') { - return await getPdfUrl(chart.fileName, 1); + return await getPdfImageUrl(chart.fileName, 1); } return await getImageUrl(chart.fileName); } catch (err) { @@ -92,7 +92,8 @@ export const LocalFileChartSelector = ({ selectedTab, loading }: LocalFileChartS try { const url = await getChartResourceUrl(chart); dispatch(editTabProperty({ tab: NavigationTab.LOCAL_FILES, chartDimensions: { width: undefined, height: undefined } })); - dispatch(editTabProperty({ tab: NavigationTab.LOCAL_FILES, chartName: { light: url, dark: url } })); + dispatch(editTabProperty({ tab: NavigationTab.LOCAL_FILES, chartName: { light: chart.fileName, dark: chart.fileName } })); + dispatch(editTabProperty({ tab: NavigationTab.LOCAL_FILES, chartLinks: { light: url, dark: url } })); dispatch(setBoundingBox(undefined)); const pagesViewable = await getPagesViewable(chart); dispatch(editTabProperty({ tab: NavigationTab.LOCAL_FILES, pagesViewable })); diff --git a/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFileChartUI.tsx b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFileChartUI.tsx index 7cdca7742a3..354c25f4db2 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFileChartUI.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFileChartUI.tsx @@ -5,6 +5,7 @@ import React, { useEffect, useState } from 'react'; import { ArrowReturnRight } from 'react-bootstrap-icons'; import { toast } from 'react-toastify'; import { Viewer } from '@flybywiresim/fbw-sdk'; +import { getPdfImageUrl } from './LocalFilesPage'; import { t } from '../../../Localization/translation'; import { LocalFileChart, LocalFileChartSelector, LocalFileOrganizedCharts } from './LocalFileChartSelector'; import { ScrollableContainer } from '../../../UtilComponents/ScrollableContainer'; @@ -33,7 +34,7 @@ export const LocalFileChartUI = () => { { name: 'PDF', alias: t('NavigationAndCharts.LocalFiles.Pdf'), charts: charts.pdfs }, { name: 'BOTH', alias: t('NavigationAndCharts.LocalFiles.Both'), charts: [...charts.images, ...charts.pdfs] }, ]); - const { searchQuery, isFullScreen, chartName, selectedTabIndex } = useAppSelector((state) => state.navigationTab[NavigationTab.LOCAL_FILES]); + const { searchQuery, isFullScreen, selectedTabIndex, currentPage, chartName, pagesViewable } = useAppSelector((state) => state.navigationTab[NavigationTab.LOCAL_FILES]); const updateSearchStatus = async () => { setIcaoAndNameDisagree(true); @@ -80,8 +81,16 @@ export const LocalFileChartUI = () => { }, [charts]); useEffect(() => { - dispatch(editTabProperty({ tab: NavigationTab.LOCAL_FILES, chartLinks: { light: chartName.light, dark: chartName.dark } })); - }, [chartName]); + if (pagesViewable > 1) { + getPdfImageUrl(chartName.light, currentPage) + .then((url) => { + dispatch(editTabProperty({ tab: NavigationTab.LOCAL_FILES, chartLinks: { light: url, dark: url } })); + }) + .catch((error) => { + console.error(`Error: ${error}`); + }); + } + }, [currentPage]); const getLocalFileChartList = async (searchQuery: string): Promise => { const pdfs: LocalFileChart[] = []; diff --git a/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFilesPage.tsx b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFilesPage.tsx index 95e6027db82..19fc9adfe1a 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFilesPage.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/LocalFilesPage/LocalFilesPage.tsx @@ -15,7 +15,7 @@ enum ConnectionState { ESTABLISHED, } -export const getPdfUrl = async (fileName: string, pageNumber: number): Promise => { +export const getPdfImageUrl = async (fileName: string, pageNumber: number): Promise => { const id = 'loading-file'; try { toast.loading(t('NavigationAndCharts.LoadingPdf'), { toastId: id, pauseOnFocusLoss: false }); diff --git a/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/PinnedChartsPage.tsx b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/PinnedChartsPage.tsx index fa9370a5cf8..6f5f1931677 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/PinnedChartsPage.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Navigation/Pages/PinnedChartsPage.tsx @@ -142,6 +142,7 @@ export const PinnedChartUI = () => { ALL: { alias: t('NavigationAndCharts.All'), provider: 'ALL' }, [ChartProvider.LOCAL_FILES]: { alias: t('NavigationAndCharts.LocalFiles.Title'), provider: ChartProvider.LOCAL_FILES }, [ChartProvider.NAVIGRAPH]: { alias: t('NavigationAndCharts.Navigraph.Title'), provider: ChartProvider.NAVIGRAPH }, + [ChartProvider.CHARTFOX]: { alias: t('NavigationAndCharts.ChartFox.Title'), provider: ChartProvider.CHARTFOX }, }; const filterTabs: { tag: string, alias: string }[] = { @@ -158,6 +159,14 @@ export const PinnedChartUI = () => { { tag: 'REF', alias: 'REF' }, { tag: 'ALL', alias: 'ALL' }, ], + [ChartProvider.CHARTFOX]: [ + { tag: 'GEN', alias: 'GEN' }, + { tag: 'GND', alias: 'GND' }, + { tag: 'SID', alias: 'SID' }, + { tag: 'STAR', alias: 'STAR' }, + { tag: 'APP', alias: 'APP' }, + { tag: 'ALL', alias: 'ALL' }, + ], }[selectedProvider] ?? []; const providerCharts = pinnedCharts.filter((pinnedChart) => { @@ -186,7 +195,7 @@ export const PinnedChartUI = () => { return pinnedChart.tag === filterItem.tag; } - if (provider === ChartProvider.NAVIGRAPH) { + if (provider === ChartProvider.NAVIGRAPH || provider === ChartProvider.CHARTFOX) { if (filterItem.tag === 'ALL') { return true; } diff --git a/fbw-common/src/systems/instruments/src/EFB/Settings/Pages/ThirdPartyOptionsPage.tsx b/fbw-common/src/systems/instruments/src/EFB/Settings/Pages/ThirdPartyOptionsPage.tsx index e75dc3b878c..76bad72132e 100644 --- a/fbw-common/src/systems/instruments/src/EFB/Settings/Pages/ThirdPartyOptionsPage.tsx +++ b/fbw-common/src/systems/instruments/src/EFB/Settings/Pages/ThirdPartyOptionsPage.tsx @@ -2,10 +2,11 @@ // SPDX-License-Identifier: GPL-3.0 import React, { useState } from 'react'; -import { NavigraphSubscriptionStatus, usePersistentNumberProperty, usePersistentProperty } from '@flybywiresim/fbw-sdk'; +import { ChartFoxStatus, NavigraphSubscriptionStatus, usePersistentNumberProperty, usePersistentProperty } from '@flybywiresim/fbw-sdk'; import { Route, Switch, useHistory } from 'react-router-dom'; import { toast } from 'react-toastify'; import { IconTrash } from '@tabler/icons'; +import { useChartFox } from 'instruments/src/EFB/Apis/ChartFox/ChartFox'; import { Toggle } from '../../UtilComponents/Form/Toggle'; import { FullscreenSettingsPage, SettingItem, SettingsPage } from '../Settings'; import { t } from '../../Localization/translation'; @@ -16,6 +17,61 @@ import { SimpleInput } from '../../UtilComponents/Form/SimpleInput/SimpleInput'; // @ts-ignore import NavigraphIcon from '../../Assets/navigraph-logo-alone.svg'; +const ChartFoxSettingContent = () => { + const { client, status } = useChartFox(); + + const handleLogout = () => { + client.deAuthenticate(); + }; + + const handleLogin = () => { + client.authenticate(); + }; + + if (status === ChartFoxStatus.Pending) { + return ( + <> + + {t('Settings.ThirdPartyOptions.ChartFoxLogin.ContinueInBrowser')} + + + + + ); + } + + if (status === ChartFoxStatus.LoggedOut) { + return ( + + ); + } + + return ( + + ); +}; + export const ThirdPartyOptionsPage = () => { const history = useHistory(); const navigraph = useNavigraph(); @@ -153,6 +209,10 @@ export const ThirdPartyOptionsPage = () => { + + + + void; + +export type ConstructorParams = { + onUpdate: OnUpdateFunc +} + +export type ChartFoxData = { + client: ChartFoxClient, + status: ChartFoxStatus, +} + +export class ChartFoxClient { + private accessToken: string | null = null; + + private sessionId: string | null = null; + + private pollIntervalId: NodeJS.Timeout | null = null; + + private tokenExpiryDate: Date | null = null; + + private onUpdate: (params: OnUpdateParams) => void; + + constructor(params: ConstructorParams) { + this.onUpdate = params.onUpdate; + const token = NXDataStore.get('CHARTFOX_ACCESS_TOKEN'); + + if (token) { + this.accessToken = token; + this.onUpdate({ status: ChartFoxStatus.LoggedIn }); + } + } + + public async authenticate(): Promise { + this.accessToken = null; + this.onUpdate({ status: ChartFoxStatus.Pending }); + this.sessionId = null; + + try { + const sessResp = await fetch(`${baseUrl}/chartfox/session`, { method: 'POST' }); + + if (sessResp.ok) { + const { id } = await sessResp.json(); + this.sessionId = id; + } + } catch (e) { + console.log('Unable to Authorize Device. #CF101'); + throw e; + } + + if (!this.sessionId) { + throw new Error('got back empty session id'); + } + + // TODO: wait a second or so first? + Coherent.trigger('OPEN_WEB_BROWSER', `${baseUrl}/chartfox/auth?session_id=${this.sessionId}`); + + this.pollIntervalId = setInterval(() => { + this.checkSession(); + }, 1000); + } + + private async loginFailed() { + this.sessionId = null; + this.accessToken = null; + this.onUpdate({ status: ChartFoxStatus.LoggedOut }); + if (this.pollIntervalId) { + clearInterval(this.pollIntervalId); + } + // TODO: toast? + } + + private async checkSession() { + try { + const resp = await fetch(`${baseUrl}/chartfox/session/${this.sessionId}`); + if (resp.ok) { + const { status, accessToken, tokenExpiryDate } = await resp.json(); + if (status === 'created' || status === 'processing') { + return; + } + if (status === 'failed') { + console.log('status: failed'); + this.loginFailed(); + return; + } + if (status !== 'completed') { + console.log(`unknown status ${status}`); + this.loginFailed(); + return; + } + if (!accessToken) { + console.log('received no access token'); + this.loginFailed(); + return; + } + + this.accessToken = accessToken; + this.onUpdate({ status: ChartFoxStatus.LoggedIn }); + this.tokenExpiryDate = new Date(tokenExpiryDate); + if (this.pollIntervalId) { + clearInterval(this.pollIntervalId); + } + } + } catch (e) { + console.log(e); + this.loginFailed(); + } + } + + public deAuthenticate() { + this.accessToken = null; + this.onUpdate({ status: ChartFoxStatus.LoggedOut }); + this.sessionId = null; + this.tokenExpiryDate = null; + NXDataStore.set('CHARTFOX_ACCESS_TOKEN', ''); + if (this.pollIntervalId) { + clearInterval(this.pollIntervalId); + } + } + + public async getChart(id: string): Promise { + if (id === '') { + console.log('no chart id'); + return null; + } + + try { + let jsonValue; + if (UseChartFoxSamples) { + jsonValue = sampleData[id]; + } else { + if (!this.accessToken) { + throw new Error('log in first'); + } + + const jsonResp = await fetch(`https://api.chartfox.org/v2/charts/${id}`, { + headers: { + accept: 'application/json', + authorization: `Bearer ${this.accessToken}`, + }, + }); + + if (!jsonResp.ok) { + console.log('network error'); + return null; + } + + jsonValue = await jsonResp.json(); + } + return { + id: jsonValue.id, + parentId: jsonValue.parent_id, + name: jsonValue.name, + type: jsonValue.type, + typeKey: jsonValue.type_key, + url: jsonValue.url, + sourceUrl: jsonValue.source_url, + sourceUrlType: jsonValue.source_url_type, + georefs: jsonValue.georefs.map((georef) => ({ + tx: georef.tx, + ty: georef.ty, + k: georef.k, + transformAngle: georef.transform_angle, + pdfPageRotation: georef.pdf_page_rotation, + page: georef.page, + })), + hasGeoreferences: jsonValue.has_georeferences, + updatedAt: jsonValue.updated_at, + runways: jsonValue.meta.find((meta) => meta.type_key === 'Runways')?.value ?? [], + }; + } catch (e) { + console.log(`Error getting ChartFox chart: ${e}`); + } + + return null; + } + + public async getChartIndex(icao: string): Promise { + if (icao.length !== 4) { + return emptyAirportIndex; + } + + try { + let groupedCharts; + if (UseChartFoxSamples) { + groupedCharts = sampleData[icao].props.groupedCharts; + } else { + if (!this.accessToken) { + throw new Error('log in first'); + } + const chartJsonResp = await fetch(`https://chartfox.org/v2/airports/${icao}/grouped`, { + method: 'GET', + headers: { + accept: 'application/json', + authorization: `Bearer ${this.accessToken}`, + }, + }); + + if (!chartJsonResp.ok) { + return emptyAirportIndex; + } + + const { data } = await chartJsonResp.json(); + groupedCharts = data; + } + const groundLayoutArray: ChartFoxGroupedChart[] = groupedCharts['3'].map((chart) => ({ + id: chart.id, + name: chart.name, + type: chart.type, + typeKey: chart.type_key, + runways: chart.meta.find((meta) => meta.type_key === 'Runways')?.value ?? [], + })); + + const unknownArray: ChartFoxGroupedChart[] = groupedCharts['0'].map((chart) => ({ + id: chart.id, + name: chart.name, + type: chart.type, + typeKey: chart.type_key, + runways: chart.meta.find((meta) => meta.type_key === 'Runways')?.value ?? [], + })); + + const sidArray: ChartFoxGroupedChart[] = groupedCharts['4'].map((chart) => ({ + id: chart.id, + name: chart.name, + type: chart.type, + typeKey: chart.type_key, + runways: chart.meta.find((meta) => meta.type_key === 'Runways')?.value ?? [], + })); + + const starArray: ChartFoxGroupedChart[] = groupedCharts['5'].map((chart) => ({ + id: chart.id, + name: chart.name, + type: chart.type, + typeKey: chart.type_key, + runways: chart.meta.find((meta) => meta.type_key === 'Runways')?.value ?? [], + })); + + const approachArray: ChartFoxGroupedChart[] = groupedCharts['6'].map((chart) => ({ + id: chart.id, + name: chart.name, + type: chart.type, + typeKey: chart.type_key, + runways: chart.meta.find((meta) => meta.type_key === 'Runways')?.value ?? [], + })); + + // TODO: change this if we're able to get airport info + return merge(omit(emptyAirportIndex, ['groupedCharts']), { + groupedCharts: { + arrival: starArray, + approach: approachArray, + airport: groundLayoutArray, + departure: sidArray, + reference: unknownArray, + }, + }); + // return { + // id: chartJson.props.airport.id, + // ident: chartJson.props.airport.ident, + // icaoCode: chartJson.props.airport.icao_code, + // iataCode: chartJson.props.airport.iata_code, + // name: chartJson.props.airport.name, + // type: chartJson.props.airport.type, + // latitude: chartJson.props.airport.latitude, + // longitude: chartJson.props.airport.longitude, + // elevationFt: chartJson.props.airport.elevation_ft, + // isoA2Country: chartJson.props.airport.iso_a2_country, + // hasCharts: chartJson.props.airport.has_charts, + // hasSources: chartJson.props.airport.has_sources, + // groupedCharts: { + // arrival: starArray, + // approach: approachArray, + // airport: groundLayoutArray, + // departure: sidArray, + // reference: unknownArray, + // }, + // }; + } catch (e) { + // console.log('Token Authentication Failed. #CF101'); + console.log(e); + } + + return emptyAirportIndex; + } + + public get hasToken(): boolean { + return !!this.accessToken; + } +} diff --git a/fbw-common/src/systems/shared/src/chartfox/index.ts b/fbw-common/src/systems/shared/src/chartfox/index.ts new file mode 100644 index 00000000000..d860d0670db --- /dev/null +++ b/fbw-common/src/systems/shared/src/chartfox/index.ts @@ -0,0 +1,2 @@ +export * from './client'; +export * from './types'; diff --git a/fbw-common/src/systems/shared/src/chartfox/samples.tsx b/fbw-common/src/systems/shared/src/chartfox/samples.tsx new file mode 100644 index 00000000000..6661801a7a0 --- /dev/null +++ b/fbw-common/src/systems/shared/src/chartfox/samples.tsx @@ -0,0 +1,4076 @@ +export const sampleData = { + '95ea7c53-cd65-4a96-838b-8eb92d2f334c': { + id: '95ea7c53-cd65-4a96-838b-8eb92d2f334c', + parent_id: null, + airport_icao: 'KBOS', + name: 'ILS RWY 04R (CAT II - III)', + code: null, + type: 6, + type_key: 'Approach', + url: 'https://cdn.chartfox.org/95ea7c53-cd65-4a96-838b-8eb92d2f334c-9ca31751-1df0-480f-a1e5-c05e16f297aa.pdf', + // source_url: 'https://aeronav.faa.gov/d-tpp/2402/00058I4RC2_3.PDF', + source_url: 'http://localhost:1180/outback-2018.pdf', + source_url_type: 0, + source_uuid: '00058I4RC2_3.PDF', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '04R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + source: { + name: 'FAA', + display_name: 'US FAA', + iso_a2_countries: [ + 'US', + ], + copyright_statement_short: null, + copyright_statement_long: null, + last_full_update: '2024-02-22T03:43:59+00:00', + }, + files: [ + { + type: 0, + url: 'https://cdn.chartfox.org/95ea7c53-cd65-4a96-838b-8eb92d2f334c-9ca31751-1df0-480f-a1e5-c05e16f297aa.pdf', + }, + ], + georefs: [], + requires_preauth: false, + allows_iframe: true, + has_georeferences: false, + updated_at: '2024-02-22T03:36:38+00:00', + }, + '2cb4f027-856e-47a2-9dc3-0777258a8844': { + id: '2cb4f027-856e-47a2-9dc3-0777258a8844', + parent_id: null, + airport_icao: 'KBOS', + name: 'AIRPORT DIAGRAM', + code: null, + type: 3, + type_key: 'GroundLayout', + url: 'https://cdn.chartfox.org/2cb4f027-856e-47a2-9dc3-0777258a8844-ea1fda15-5b00-4fe9-b75f-acd04cdb7814.pdf', + source_url: 'https://aeronav.faa.gov/d-tpp/2401/00058AD.PDF', + source_url_type: 0, + source_uuid: '00058AD.PDF', + meta: [], + source: { + name: 'FAA', + display_name: 'US FAA', + iso_a2_countries: [ + 'US', + ], + copyright_statement_short: null, + copyright_statement_long: null, + last_full_update: '2024-01-25T03:49:16+00:00', + }, + files: [ + { + type: 0, + url: 'https://cdn.chartfox.org/2cb4f027-856e-47a2-9dc3-0777258a8844-ea1fda15-5b00-4fe9-b75f-acd04cdb7814.pdf', + }, + ], + georefs: [ + { + tx: -7907286.8272118, + ty: 5220669.2746691, + k: 8501.5478901426, + transform_angle: -0.0009076649999999464, + pdf_page_rotation: 0, + page: 1, + }, + ], + requires_preauth: false, + allows_iframe: true, + has_georeferences: true, + updated_at: '2024-01-25T03:39:23+00:00', + }, + 'a358346d-ae01-4300-a59d-67816f27a72f': { + id: 'a358346d-ae01-4300-a59d-67816f27a72f', + parent_id: null, + airport_icao: 'CYYZ', + name: 'AERODROME CHART', + code: null, + type: 3, + type_key: 'GroundLayout', + url: 'http://imageserver.fltplan.com/merge/Canada/merge240125/PngCharts/CYYZ_AERODROME_CHART.png', + source_url: 'http://imageserver.fltplan.com/merge/Canada/merge240125/PngCharts/CYYZ_AERODROME_CHART.png', + source_url_type: 1, + source_uuid: null, + meta: [], + source: { + name: 'FltPlan', + display_name: 'Flt Plan', + iso_a2_countries: [ + 'CA', + ], + copyright_statement_short: '© NAV CANADA', + copyright_statement_long: null, + last_full_update: '2024-02-17T19:27:32+00:00', + }, + files: [], + georefs: [], + requires_preauth: false, + allows_iframe: true, + has_georeferences: false, + updated_at: '2024-02-20T00:00:14+00:00', + }, + 'CYYZ': { + component: 'charts/index', + props: { + errors: {}, + analytics_event: null, + flash: { + error_message: null, + success_message: null, + popup_message: null, + page_data: null, + }, + settings: { + feature: { + enable_contribution_centre: true, + enable_donations: true, + enable_api_applications: true, + }, + consented_cookies: [ + 'chartfox_cookie_consent', + 'chartfoxv2_session', + 'XSRF-TOKEN', + 'chartfox_user_pat', + 'remember_web_*', + ], + }, + config: { + browser_extensions: { + chromium: 'https://chrome.google.com/webstore/detail/chartfox-browser-extensio/gbpidgkkacbjclomlpoajjmpkflgneep', + firefox: 'https://addons.mozilla.org/en-GB/firefox/addon/chartfox-browser-extension/', + }, + base_uri: { api: 'https://api.chartfox.org:443' }, + }, + airport: { + id: 14138, + ident: 'CYYZ', + icao_code: 'CYYZ', + iata_code: 'YYZ', + name: 'Toronto Lester B. Pearson International Airport', + type: 'large_airport', + latitude: 43.6772, + longitude: -79.6306, + elevation_ft: 569, + iso_a2_country: 'CA', + service_notifications: [], + has_charts: true, + has_sources: true, + }, + groupedCharts: { + 6: [ + { + id: '0afa2e43-1e1b-489f-a190-ebaf98622207', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ADVISORY CHART', + code: null, + type: 6, + type_key: 'Approach', + meta: [], + has_georeferences: false, + }, + { + id: 'dbedcc21-a7a0-4df1-8477-7f6b2a99a329', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ESTABLISHED ON RNP AR EOR USER INSTRUCTIONS ATTENTION ALL USERS OF EOR', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'dbb46825-d2a8-4871-913c-041d52f8c0f4', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS CAT II OR III RWY 05', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '05', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '1e8645ef-8f56-4a32-8c83-f9b2d543b4bd', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS CAT II OR III RWY 06L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '06L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '934255ea-dbd7-4d36-9a72-a5513d5b1b9d', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS RWY 05', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '05', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '390a4199-14d1-4c9f-9c6e-a70af4883fcd', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS RWY 06L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '06L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '471c2d70-819c-409c-8f5a-768286b626c4', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS RWY 06R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '06R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '89f7ec0f-eb44-4f3a-880d-d428545e045c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS RWY 15L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '15L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '9a0fcb25-1b2e-4b9d-a898-06d3aba095e7', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS RWY 15R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '15R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'bfc6941d-9d77-44de-9336-56188f7634ff', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS RWY 23', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '23', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'a393da79-21a7-4d34-ac4e-e99cb6223c70', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS RWY 24L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '24L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '7f9206e9-0042-40d4-8acd-b5a5b74b72f3', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS RWY 24R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '24R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '62f90b05-840b-4ecc-80e8-dc2f7f9c16bd', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS RWY 33L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '33L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'dbb7764d-9b7b-44bf-ac1e-83a5544be1e1', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ILS RWY 33R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '33R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'bbe2338a-06dd-450f-8a13-69b576ce1d32', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LOW VISIBILITY PROCEDURES', + code: null, + type: 6, + type_key: 'Approach', + meta: [], + has_georeferences: false, + }, + { + id: '778df9d6-1919-47e4-a03a-c0bd4a7fd2e1', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS X RWY 05', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '05', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '521c611d-4b79-473b-a7e0-25015965e9c8', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS X RWY 06L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '06L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '81a54682-8dc4-4cce-9a53-d92ad6a5a853', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS X RWY 06R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '06R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'b3dc695a-02da-4a47-b9ea-a34d46e645f3', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS X RWY 23', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '23', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '6c6ba02d-515d-476b-a3db-21cf95ffd5ff', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS X RWY 24L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '24L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '1adef8f0-65ad-4e56-9bf6-8f8cd7e8dc4a', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS X RWY 24R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '24R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '9f119549-54c0-47da-9ea4-56a4680e00a5', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS Z RWY 05', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '05', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '5e61d4a9-e5ea-4a43-923d-9904eafe83af', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS Z RWY 06L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '06L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'c399bbb3-c4d2-4d8e-b0d2-ae4adc208bc0', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS Z RWY 06R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '06R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '1ea95879-5aa7-4f67-9d3a-f82bbaeebd8a', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS Z RWY 15L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '15L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'ade2847b-3093-4979-a858-e511f45e1acd', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS Z RWY 15R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '15R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '34550090-b843-4435-8cf7-7f713fdb472e', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS Z RWY 23', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '23', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '698c7165-8f11-4ddc-ae08-c2c1ab1fae75', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS Z RWY 24L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '24L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'bea80420-3b41-437f-b2e0-a7d394055575', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS Z RWY 24R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '24R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '779cc556-ed6d-4424-a55a-5c455081ce34', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS Z RWY 33L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '33L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '2440b7cd-4721-42ca-b7be-2fef9663ae01', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV GNSS Z RWY 33R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '33R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '5e2c10e8-1073-483e-a897-0f64b5dabfae', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV RNP Y RWY 05', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '05', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '743435a2-8675-472d-9b1f-2d6a63daf566', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RNAV RNP Y RWY 23', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '23', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + ], + 3: [ + { + id: 'a358346d-ae01-4300-a59d-67816f27a72f', + parent_id: null, + airport_icao: 'CYYZ', + name: 'AERODROME CHART', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: '18641730-9716-4ada-bd4a-cbb397589d8c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'AERODROME CHART HIGH INTENSITY RUNWAY OPERATIONS HIRO', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: '8b1ed143-f262-4e22-96ff-3291bd7ea149', + parent_id: null, + airport_icao: 'CYYZ', + name: 'AERODROME CHART REDUCED TAKE-OFF RUNS', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: 'b0185201-ffb0-465f-86c4-f6a98a904ae1', + parent_id: null, + airport_icao: 'CYYZ', + name: 'COMMUTER PARKING AREAS', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: '40ab4c0d-bd8e-4b0e-935b-e99331d5d06a', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LOW VISIBILITY TAXI CHART GM-3B', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: 'b638fd49-6051-4d4f-839c-25a8112f6c2a', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LOW VISIBILITY TAXI CHART GM-3C', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: '91aabdfe-cb23-40d7-b9b3-150372574023', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LOW VISIBILITY TAXI CHART GM-3D', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: 'd6831658-09f4-4315-b51d-0b738ca6cff8', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PARKING AREAS FEDEX', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: 'e89a2355-aa09-4838-9732-f93056c6e39f', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PARKING AREAS INFIELD', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: '2215bdab-eb85-437a-b91d-930442a9405b', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PARKING AREAS TERMINAL 1', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: 'f627b4b0-2c86-42d4-9292-9659394efc36', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PARKING AREAS TERMINAL 1 EAST HOLD', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: '71ed30c9-9ff2-4dfe-a969-6c6b41746868', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PARKING AREAS TERMINAL 3', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: '40e14047-a1e0-49d0-be19-52332fb6e9eb', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TAXI CHART GM-1B', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: 'f18f30a1-bc66-48fb-8891-1334919690b4', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TAXI CHART GM-1C', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: '204661b4-a4aa-42e2-a607-399d264c0a41', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TAXI CHART GM-1D', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: 'e4b99ee3-a434-4396-ac49-41e93f27bac0', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TAXILANE LIMITATIONS APD-4', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + { + id: 'e760873c-9321-4bea-b99c-748d1bbc78e1', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TAXILANE LIMITATIONS APD-5', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: false, + }, + ], + 4: [ + { + id: '8c88c2a6-7a45-436d-91a2-972373c4fcec', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ANCOL FIVE DEP ANCOL5 SID-1A', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'de895dd9-97d5-40f3-8903-d8208e48c438', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ANCOL FIVE DEP ANCOL5 SID-1B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'bdfbb8c6-d2c3-42e7-ac67-b6adf9c40006', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ANCOL FIVE DEP ANCOL5 SID-1C', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'bd074e93-5a21-4719-a4ef-e004a85e81b8', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ARROW FOUR DEP ARROW4 SID-2A', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'bbaa3045-ec0f-4a00-a9b3-36dd59eacb60', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ARROW FOUR DEP ARROW4 SID-2B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '4bde66de-8752-4a58-83ef-416bf6ae759d', + parent_id: null, + airport_icao: 'CYYZ', + name: 'AVSEP SEVEN DEP AVSEP7', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'be38445b-1243-41b4-9810-02a8fd2206f7', + parent_id: null, + airport_icao: 'CYYZ', + name: 'AVSEP SEVEN DEP AVSEP7 DEPARTURE ROUTES SID-3B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '3d7e08e5-825a-4434-bd66-33f73949fa17', + parent_id: null, + airport_icao: 'CYYZ', + name: 'AVSEP SEVEN DEP AVSEP7 DEPARTURE ROUTES SID-3C', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'bf943ed4-9a9e-44e8-84eb-2c3c4051a662', + parent_id: null, + airport_icao: 'CYYZ', + name: 'AVSEP SEVEN DEP AVSEP7 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '3c5458f9-55eb-4f5c-b8ec-821881ce5697', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BETES THREE DEP BETES3', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '5a591993-f9e7-4682-941d-bb4bdbb3eb34', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BETES THREE DEP BETES3 DEPARTURE ROUTES SID-4B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'f43f277b-13a0-4601-a2cd-80031f1a413f', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BETES THREE DEP BETES3 DEPARTURE ROUTES SID-4C', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '8eb53cd9-9453-4468-8040-9ad64792c917', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BETES THREE DEP BETES3 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '68edb396-3484-4f43-b78f-9ffd1ce14b87', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BOMET EIGHT DEP BOMET8', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'e97aa52b-1c3c-4490-9ffe-0b4a8eb38df8', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BOMET EIGHT DEP BOMET8 DEPARTURE ROUTES SID-5B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '2d961636-af84-4ad9-975f-cc9af9d73b56', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BOMET EIGHT DEP BOMET8 DEPARTURE ROUTES SID-5C', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '42e30dd1-8ae1-417d-948b-9ae85b00541a', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BOMET EIGHT DEP BOMET8 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '2e7455b0-f9a7-4ba0-bb07-231fe784682c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DEDKI FIVE DEP DEDKI5', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'e251da49-a873-4e03-bc8c-f987d6c47d03', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DEDKI FIVE DEP DEDKI5 DEPARTURE ROUTES SID-6B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '0595237c-542a-43a0-a990-c8d54b31aa30', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DEDKI FIVE DEP DEDKI5 DEPARTURE ROUTES SID-6C', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'd821d8cd-5906-437d-8222-544fd78f662e', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DEDKI FIVE DEP DEDKI5 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '7b6ecc46-4503-4fc2-891a-7e10e6b1902a', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DEPARTURE PROCEDURE', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'd92cb971-f568-4716-aee6-0c802f12b7c5', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DUSOM THREE DEP DUSOM3', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'f11906d6-94a9-42e0-8340-bf68cb560c03', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DUSOM THREE DEP DUSOM3 DEPARTURE ROUTES SID-7B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '06afc740-ded6-4236-80c6-194694f27c8c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DUSOM THREE DEP DUSOM3 DEPARTURE ROUTES SID-7C', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '343b4b47-dce9-4e02-bc83-d9a01e861181', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DUSOM THREE DEP DUSOM3 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'e174b662-7c8e-4c28-9aad-45e318b6ac16', + parent_id: null, + airport_icao: 'CYYZ', + name: 'EBKIN FOUR DEP EBKIN4', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'f41a2484-811c-4865-9e5c-26b0733dc996', + parent_id: null, + airport_icao: 'CYYZ', + name: 'EBKIN FOUR DEP EBKIN4 DEPARTURE ROUTES SID-8B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '0d402f0d-6d74-4125-bd0f-6a909437aa64', + parent_id: null, + airport_icao: 'CYYZ', + name: 'EBKIN FOUR DEP EBKIN4 DEPARTURE ROUTES SID-8C', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '3210ba95-8cdc-4e4d-8e05-e77d4a16e706', + parent_id: null, + airport_icao: 'CYYZ', + name: 'EBKIN FOUR DEP EBKIN4 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '9c844006-089a-4228-9bd7-3fa52899635e', + parent_id: null, + airport_icao: 'CYYZ', + name: 'GOPUP FOUR DEP GOPUP4', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '741dab21-dcb9-4785-a5ed-7bb98b5742b1', + parent_id: null, + airport_icao: 'CYYZ', + name: 'GOPUP FOUR DEP GOPUP4 DEPARTURE ROUTES SID-9B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'b297b1de-edb3-4d3e-aebb-e2b6b2a44ba8', + parent_id: null, + airport_icao: 'CYYZ', + name: 'GOPUP FOUR DEP GOPUP4 DEPARTURE ROUTES SID-9C', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '358337f8-5b67-40ac-801f-f002d6192387', + parent_id: null, + airport_icao: 'CYYZ', + name: 'GOPUP FOUR DEP GOPUP4 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '558e355a-a36c-4c7c-b3d4-eaf08c2593c9', + parent_id: null, + airport_icao: 'CYYZ', + name: 'IKLEN THREE DEP IKLEN3 SID-10A', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'b0ade805-8fbd-4864-aa30-4f2f51798634', + parent_id: null, + airport_icao: 'CYYZ', + name: 'IKLEN THREE DEP IKLEN3 SID-10B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'f07562f2-293b-4628-8665-8bf79d550c69', + parent_id: null, + airport_icao: 'CYYZ', + name: 'IKLEN THREE DEP IKLEN3 SID-10C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '10C', + ], + }, + ], + has_georeferences: false, + }, + { + id: 'ac8130ab-5f08-465a-940c-3bc8d2973307', + parent_id: null, + airport_icao: 'CYYZ', + name: 'KEPTA THREE DEP KEPTA3', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '9cc666f5-542a-49dc-b342-4d22f6e76df9', + parent_id: null, + airport_icao: 'CYYZ', + name: 'KEPTA THREE DEP KEPTA3 DEPARTURE ROUTES SID-11B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '72bc0e62-b0fb-499c-ac4c-7715f4c733af', + parent_id: null, + airport_icao: 'CYYZ', + name: 'KEPTA THREE DEP KEPTA3 DEPARTURE ROUTES SID-11C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '11C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '1504f2d1-6e89-48e7-ad4c-860b402d29ba', + parent_id: null, + airport_icao: 'CYYZ', + name: 'KEPTA THREE DEP KEPTA3 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'a5549637-8ce2-4d06-8ea0-908971719ce8', + parent_id: null, + airport_icao: 'CYYZ', + name: 'KISEP FOUR DEP KISEP4', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '03c48bcd-f17a-4f68-932b-98ed465833cf', + parent_id: null, + airport_icao: 'CYYZ', + name: 'KISEP FOUR DEP KISEP4 DEPARTURE ROUTES SID-12B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'bed01037-a458-4bf1-b5ba-468cf6ba5b9b', + parent_id: null, + airport_icao: 'CYYZ', + name: 'KISEP FOUR DEP KISEP4 DEPARTURE ROUTES SID-12C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '12C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '3da3ad81-b9c8-420e-80cd-2d24cbfc617d', + parent_id: null, + airport_icao: 'CYYZ', + name: 'KISEP FOUR DEP KISEP4 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '779091ff-45d9-42fe-90ac-cb0973e16d78', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LAKES FOUR DEP LAKES4 SID-13A', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'fe648b91-3d33-4023-997e-407a6c995b93', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LAKES FOUR DEP LAKES4 SID-13B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '16f7b5c2-b761-4b1f-affe-1ea5914e550a', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LAKES FOUR DEP LAKES4 SID-13C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '13C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '869d0487-2cf5-4491-a40b-6bd21c6c909c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'MATES SIX DEP MATES6 SID-14A', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '90bd529b-4ab4-4b90-9c44-10c6fee411b1', + parent_id: null, + airport_icao: 'CYYZ', + name: 'MATES SIX DEP MATES6 SID-14B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'c1978724-1780-4cc6-ba86-753e2f4c8ede', + parent_id: null, + airport_icao: 'CYYZ', + name: 'MATES SIX DEP MATES6 SID-14C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '14C', + ], + }, + ], + has_georeferences: false, + }, + { + id: 'f9c6c2fe-2d01-416c-94bd-8a19f6a4540c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'MAVAN THREE DEP MAVAN3', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '86f3bac6-b7cc-4153-9417-e94d0711bb91', + parent_id: null, + airport_icao: 'CYYZ', + name: 'MAVAN THREE DEP MAVAN3 DEPARTURE ROUTES SID-15B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '32d686c6-05f5-4ae4-8bd6-23f28e02c969', + parent_id: null, + airport_icao: 'CYYZ', + name: 'MAVAN THREE DEP MAVAN3 DEPARTURE ROUTES SID-15C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '15C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '15d5582a-95b2-4a69-bc8e-39dba55a609b', + parent_id: null, + airport_icao: 'CYYZ', + name: 'MAVAN THREE DEP MAVAN3 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '7f9e5ea6-105e-4bb6-a10e-60d7148914ec', + parent_id: null, + airport_icao: 'CYYZ', + name: 'MIXUT SEVEN DEP MIXUT7', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'c10dad01-e6de-445e-8146-8ee0944fc51c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'MIXUT SEVEN DEP MIXUT7 DEPARTURE ROUTES SID-16B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'b46b7606-f6bf-4564-aa81-e56f8b69b3ff', + parent_id: null, + airport_icao: 'CYYZ', + name: 'MIXUT SEVEN DEP MIXUT7 DEPARTURE ROUTES SID-16C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '16C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '3ac77271-a0d7-4958-b9b2-ac54d1d3692e', + parent_id: null, + airport_icao: 'CYYZ', + name: 'MIXUT SEVEN DEP MIXUT7 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'acb62e03-da12-4791-9215-86f48bb5865d', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NOSIK FOUR DEP NOSIK4', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'e2dc3f57-077b-40dd-89c8-2a55121bf9a1', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NOSIK FOUR DEP NOSIK4 DEPARTURE ROUTES SID-17B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '03c2d873-aa11-4ab3-bb37-e27402342cae', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NOSIK FOUR DEP NOSIK4 DEPARTURE ROUTES SID-17C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '17C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '6cbd61a1-e469-4601-bc1f-eafd65e5354b', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NOSIK FOUR DEP NOSIK4 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '742f80e9-12b6-450c-9dcf-9d66233447d7', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NUGOP SIX DEP NUGOP6', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '0302beeb-9f05-42ab-8114-e9262fcaa7ab', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NUGOP SIX DEP NUGOP6 DEPARTURE ROUTES SID-18B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '51cf08c9-6f4c-4941-a50a-5b24ec42005b', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NUGOP SIX DEP NUGOP6 DEPARTURE ROUTES SID-18C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '18C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '124e988a-a3ef-4303-b0bd-56c94ad173ba', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NUGOP SIX DEP NUGOP6 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '1ebfff97-db9d-4580-8ac1-fb058bbf41f8', + parent_id: null, + airport_icao: 'CYYZ', + name: 'OAKVL THREE DEP OAKVL3', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '610adf9a-734f-4be5-8694-0e8589e465d3', + parent_id: null, + airport_icao: 'CYYZ', + name: 'OAKVL THREE DEP OAKVL3 DEPARTURE ROUTES SID-19B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'afd2c545-a589-419d-9b14-65b92d8737f1', + parent_id: null, + airport_icao: 'CYYZ', + name: 'OAKVL THREE DEP OAKVL3 DEPARTURE ROUTES SID-19C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '19C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '0ecbea6a-fa6a-4241-a703-59c77b7badf4', + parent_id: null, + airport_icao: 'CYYZ', + name: 'OAKVL THREE DEP OAKVL3 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '54b2cb47-fa33-4285-8ce8-0409f684c093', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PEMBA SIX DEP PEMBA6', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'b8fd0d58-d8dd-44cf-97f0-12b60fb88a66', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PEMBA SIX DEP PEMBA6 DEPARTURE ROUTES SID-20B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '04414ec3-408b-47ec-86a2-053362654f9d', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PEMBA SIX DEP PEMBA6 DEPARTURE ROUTES SID-20C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '20C', + ], + }, + ], + has_georeferences: false, + }, + { + id: 'a1000e77-0257-4c00-a3a1-f58eaf7fa39d', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PEMBA SIX DEP PEMBA6 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '6468b0d4-4283-4d9a-a646-fa8b27bb331c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PERLO FIVE DEP PERLO5 SID-21A', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'fa1f6bdc-5da2-43cd-ad13-249c4069c00b', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PERLO FIVE DEP PERLO5 SID-21B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '6d247be6-bbe5-406b-96c9-7863bcd33d9a', + parent_id: null, + airport_icao: 'CYYZ', + name: 'PERLO FIVE DEP PERLO5 SID-21C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '21C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '99346fc8-c7d1-4b16-afc6-b1508ba4ca72', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RIGUS FIVE DEP RIGUS5', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '9e481c05-c252-460a-a5c1-456e9aa04427', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RIGUS FIVE DEP RIGUS5 DEPARTURE ROUTES SID-22B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '7b4160ef-b350-402c-8bca-0583501aa01f', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RIGUS FIVE DEP RIGUS5 DEPARTURE ROUTES SID-22C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '22C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '6b4a0dae-b69c-4128-84be-f5b55652319e', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RIGUS FIVE DEP RIGUS5 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '7d422c2e-ad8b-4117-9aeb-de8d8d3004c1', + parent_id: null, + airport_icao: 'CYYZ', + name: 'SEDOG SIX DEP SEDOG6', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '010aaa41-6deb-4c4a-97fa-451e96c09a5d', + parent_id: null, + airport_icao: 'CYYZ', + name: 'SEDOG SIX DEP SEDOG6 DEPARTURE ROUTES SID-23B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '9c97ae3b-32c6-4001-9f99-dbe05dbf01cc', + parent_id: null, + airport_icao: 'CYYZ', + name: 'SEDOG SIX DEP SEDOG6 DEPARTURE ROUTES SID-23C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '23C', + ], + }, + ], + has_georeferences: false, + }, + { + id: 'fbd1c5d7-e02c-4ed6-a80c-50f4d8603b88', + parent_id: null, + airport_icao: 'CYYZ', + name: 'SEDOG SIX DEP SEDOG6 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '7d8d1d5a-8f69-42c8-bfb8-ddd8299ad1c9', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TEVAD THREE DEP TEVAD3 SID-24A', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '8023b1e0-aa32-4615-9248-daa79f10f91d', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TEVAD THREE DEP TEVAD3 SID-24B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '15377c72-cec4-4b0b-8cc0-5eef99511f6d', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TEVAD THREE DEP TEVAD3 SID-24C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '24C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '25d9add0-a467-453d-b137-7b1e35c9f86f', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TORONTO FOUR DEP TRNTO4 SID-25A', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '1b0c0f02-ec6a-46ac-8969-b9142ef9a1f4', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TORONTO FOUR DEP TRNTO4 SID-25B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'f43f3f90-e3a9-4d54-b645-1367962494fd', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TULEK FOUR DEP TULEK4', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '87f80239-a650-4c60-b2cb-15b6cea79cc6', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TULEK FOUR DEP TULEK4 DEPARTURE ROUTES SID-26B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '2dbf7af4-8b41-4ebd-bcb0-fff3062991d9', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TULEK FOUR DEP TULEK4 DEPARTURE ROUTES SID-26C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '26C', + ], + }, + ], + has_georeferences: false, + }, + { + id: 'f556684f-c971-4174-9695-551f212f101c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'TULEK FOUR DEP TULEK4 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '4b2a8420-604f-45a3-a88d-2c6a8ce39be9', + parent_id: null, + airport_icao: 'CYYZ', + name: 'URSAL FOUR DEP URSAL4', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'e0a20581-1447-4683-bdda-60dcfa17097e', + parent_id: null, + airport_icao: 'CYYZ', + name: 'URSAL FOUR DEP URSAL4 DEPARTURE ROUTES SID-27B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'd8b22c66-cd5d-4946-b071-d5ab41fcb72b', + parent_id: null, + airport_icao: 'CYYZ', + name: 'URSAL FOUR DEP URSAL4 DEPARTURE ROUTES SID-27C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '27C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '71246867-4845-4124-b9f0-20792d4ed0f5', + parent_id: null, + airport_icao: 'CYYZ', + name: 'URSAL FOUR DEP URSAL4 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'd5e66f19-ce85-444c-acd1-21ab0889279a', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VERDO SEVEN DEP VERDO7', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'a659fc28-0af8-4966-9d89-cc6b675bc684', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VERDO SEVEN DEP VERDO7 DEPARTURE ROUTES SID-28B', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'd999a0c5-ed54-4f93-9b70-a5a4417595d3', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VERDO SEVEN DEP VERDO7 DEPARTURE ROUTES SID-28C', + code: null, + type: 4, + type_key: 'SID', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '28C', + ], + }, + ], + has_georeferences: false, + }, + { + id: 'd23f0c6e-b842-4834-900c-6d8f3a7aa969', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VERDO SEVEN DEP VERDO7 TRANSITION ROUTES', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + ], + 5: [ + { + id: '4416a4ff-fdb7-4ce5-b3b5-8735826412c8', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BOXUM SEVEN ARR BOXUM BOXUM7 ARRIVAL ROUTES STAR-1B', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '1c23ba34-eaa8-4b73-b71d-3e2e9f6ff19c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BOXUM SEVEN ARR BOXUM BOXUM7 ARRIVAL ROUTES STAR-1C', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'c15f5b6f-b5e2-4942-b5ab-12414b82572b', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BOXUM SEVEN ARR BOXUM BOXUM7 ARRIVAL ROUTES STAR-1D', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'baaff896-65bd-4ec9-8b06-8cac2a201f56', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BOXUM SEVEN ARR BOXUM BOXUM7 ARRIVAL ROUTES STAR-1E', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '50bd6d85-7ac1-4168-81c7-807e80314f80', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DUVOS FOUR ARR BOXUM DUVOS4 ARRIVAL ROUTES STAR-2B', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'e4ef1ac0-0533-4833-ba3e-124402483bea', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DUVOS FOUR ARR BOXUM DUVOS4 ARRIVAL ROUTES STAR-2C', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'f1372e93-a738-4786-86b6-d9e15b582394', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DUVOS FOUR ARR BOXUM DUVOS4 ARRIVAL ROUTES STAR-2D', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '3b412d56-b38b-483a-a0b7-a6301015a425', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DUVOS FOUR ARR BOXUM DUVOS4 ARRIVAL ROUTES STAR-2E', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '5cf895cb-a94f-4366-a588-23808d312df3', + parent_id: null, + airport_icao: 'CYYZ', + name: 'IMEBA NINE ARR IMEBA IMEBA9 ARRIVAL ROUTES STAR-3B', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '75db688f-f8cc-446f-a8cc-e0d3327a4a1c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'IMEBA NINE ARR IMEBA IMEBA9 ARRIVAL ROUTES STAR-3C', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '55aa321c-357c-4f70-bcda-4ada7f0ec2fe', + parent_id: null, + airport_icao: 'CYYZ', + name: 'IMEBA NINE ARR IMEBA IMEBA9 ARRIVAL ROUTES STAR-3D', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '9d012692-4681-4bc0-99ee-5dd63fb150af', + parent_id: null, + airport_icao: 'CYYZ', + name: 'IMEBA NINE ARR IMEBA IMEBA9 ARRIVAL ROUTES STAR-3E', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'cc850d56-27f3-4988-a97c-35a5380d18b2', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LINNG THREE ARR LINNG LINNG3 ARRIVAL ROUTES STAR-4A', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '7cd90722-c0a2-4a7a-929f-8a9a5582e0b2', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LINNG THREE ARR LINNG LINNG3 ARRIVAL ROUTES STAR-4B', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '280e7f4d-57bb-4151-9ef7-d81cb3f270f8', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LINNG THREE ARR LINNG LINNG3 ARRIVAL ROUTES STAR-4C', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '5cd3663f-0eb7-4d15-86d7-7b7c82180457', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LINNG THREE ARR LINNG LINNG3 ARRIVAL ROUTES STAR-4D', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'e5bb42d2-06b8-471e-8a92-03b887795b69', + parent_id: null, + airport_icao: 'CYYZ', + name: 'LINNG THREE ARR LINNG LINNG3 ARRIVAL ROUTES STAR-4E', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'eba21643-e4de-4cea-a739-c48a7eb27748', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NAKBO SIX ARR NUBER NAKBO6 ARRIVAL ROUTES STAR-5B', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'b8354d41-d742-452b-8b23-2f64dcac08f0', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NAKBO SIX ARR NUBER NAKBO6 ARRIVAL ROUTES STAR-5C', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'b24b7ab6-6a0b-40ee-822a-9f56a6c55f7e', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NAKBO SIX ARR NUBER NAKBO6 ARRIVAL ROUTES STAR-5D', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '59c4be77-a266-4ab9-a5e8-9f5b25e3fd7b', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NUBER SIX ARR NUBER NUBER6 ARRIVAL ROUTES STAR-6B', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'd7d80c36-b7d7-4213-8198-5dda1157b12f', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NUBER SIX ARR NUBER NUBER6 ARRIVAL ROUTES STAR-6C', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'd4da5e56-f05e-4594-ac40-05aceee27508', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NUBER SIX ARR NUBER NUBER6 ARRIVAL ROUTES STAR-6D', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'fd8629e7-3a1c-4c0f-8aaa-b0b11d06f363', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RAGID FIVE ARR RAGID RAGID5 ARRIVAL ROUTES STAR-7B', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '73e90b96-75eb-48bd-b05e-ab7d359fe74e', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RAGID FIVE ARR RAGID RAGID5 ARRIVAL ROUTES STAR-7C', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '986ed9f1-b4a3-4caf-b42d-1d723b0ec01c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RAGID FIVE ARR RAGID RAGID5 ARRIVAL ROUTES STAR-7D', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '2239498e-0a12-4941-b6a5-1ad76bbeedc5', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RAGID FIVE ARR RAGID RAGID5 ARRIVAL ROUTES STAR-7E', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '12bd95a1-b553-407f-970a-4b96dddc7db0', + parent_id: null, + airport_icao: 'CYYZ', + name: 'UDNOX FIVE ARR RAGID UDNOX5 ARRIVAL ROUTES STAR-8B', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '952d691d-80b2-4b8e-9df4-7b264eab3ca8', + parent_id: null, + airport_icao: 'CYYZ', + name: 'UDNOX FIVE ARR RAGID UDNOX5 ARRIVAL ROUTES STAR-8C', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '2238dcf2-0b52-486d-9ec5-efc975725046', + parent_id: null, + airport_icao: 'CYYZ', + name: 'UDNOX FIVE ARR RAGID UDNOX5 ARRIVAL ROUTES STAR-8D', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'e609a397-74b9-4be9-84e9-c787ac9ae5b4', + parent_id: null, + airport_icao: 'CYYZ', + name: 'UDNOX FIVE ARR RAGID UDNOX5 ARRIVAL ROUTES STAR-8E', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '6a8fb993-5ed5-47e5-95bb-f3ce78afe511', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VERKO ONE ARR LINNG VERKO1 ARRIVAL ROUTES STAR-9B', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '5ff24ea1-a1af-4e4e-8ac8-a8befd212259', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VERKO ONE ARR LINNG VERKO1 ARRIVAL ROUTES STAR-9C', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'eb9ebf1f-d203-4a8f-b004-8207951fd7f0', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VERKO ONE ARR LINNG VERKO1 ARRIVAL ROUTES STAR-9D', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'e2b1d48e-8472-4fb0-9bb6-4f150b2a37ad', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VERKO ONE ARR LINNG VERKO1 ARRIVAL ROUTES STAR-9E', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '0e1d87c4-9a6b-4d1c-9e50-9cc5b4f4cd9d', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VIBLI SIX ARR IMEBA VIBLI6 ARRIVAL ROUTES STAR-10B', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '9ab92a30-1c71-48b3-bffd-67992706cdfa', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VIBLI SIX ARR IMEBA VIBLI6 ARRIVAL ROUTES STAR-10C', + code: null, + type: 5, + type_key: 'STAR', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '10C', + ], + }, + ], + has_georeferences: false, + }, + { + id: '6a8f8299-7808-4125-b340-1ed92a6f8f91', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VIBLI SIX ARR IMEBA VIBLI6 ARRIVAL ROUTES STAR-10D', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '20fdb317-e0c6-4bc4-9070-1008da0ebc62', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VIBLI SIX ARR IMEBA VIBLI6 ARRIVAL ROUTES STAR-10E', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + ], + 7: [ + { + id: '6078fd43-4ef0-4698-ae01-a3346cba8031', + parent_id: null, + airport_icao: 'CYYZ', + name: 'BOXUM SEVEN ARR BOXUM BOXUM7 TRANSITION ROUTES', + code: null, + type: 7, + type_key: 'Transition', + meta: [], + has_georeferences: false, + }, + { + id: '499f9886-bcfd-446a-86cd-b3fe949d3614', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DUVOS FOUR ARR BOXUM DUVOS4 TRANSITION ROUTES', + code: null, + type: 7, + type_key: 'Transition', + meta: [], + has_georeferences: false, + }, + { + id: '619ee37d-f27c-46b9-b0c9-d7bc4373e693', + parent_id: null, + airport_icao: 'CYYZ', + name: 'IMEBA NINE ARR IMEBA IMEBA9 TRANSITION ROUTES', + code: null, + type: 7, + type_key: 'Transition', + meta: [], + has_georeferences: false, + }, + { + id: 'f88709d3-e215-4268-9821-33a0962cccef', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NAKBO SIX ARR NUBER NAKBO6 TRANSITION ROUTES', + code: null, + type: 7, + type_key: 'Transition', + meta: [], + has_georeferences: false, + }, + { + id: '5362973d-5939-43ef-9cd8-98e35dc51226', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NUBER SIX ARR NUBER NUBER6 TRANSITION ROUTES', + code: null, + type: 7, + type_key: 'Transition', + meta: [], + has_georeferences: false, + }, + { + id: 'a073bbce-6a4d-410c-9ec1-d26ff1af338d', + parent_id: null, + airport_icao: 'CYYZ', + name: 'RAGID FIVE ARR RAGID RAGID5 TRANSITION ROUTES', + code: null, + type: 7, + type_key: 'Transition', + meta: [], + has_georeferences: false, + }, + { + id: 'be26c77e-6fb8-4c9c-86f2-a209e0a1f81e', + parent_id: null, + airport_icao: 'CYYZ', + name: 'UDNOX FIVE ARR RAGID UDNOX5 TRANSITION ROUTES', + code: null, + type: 7, + type_key: 'Transition', + meta: [], + has_georeferences: false, + }, + { + id: '83c1ed42-667c-4049-9133-ab566610e733', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VERKO ONE ARR LINNG VERKO1 TRANSITION ROUTES', + code: null, + type: 7, + type_key: 'Transition', + meta: [], + has_georeferences: false, + }, + { + id: '1ce33143-870b-480d-bf30-075dec61dd0e', + parent_id: null, + airport_icao: 'CYYZ', + name: 'VIBLI SIX ARR IMEBA VIBLI6 TRANSITION ROUTES', + code: null, + type: 7, + type_key: 'Transition', + meta: [], + has_georeferences: false, + }, + ], + 0: [ + { + id: 'f3ad2a4a-933b-4561-aaa7-b33471ec5a1c', + parent_id: null, + airport_icao: 'CYYZ', + name: 'CENTRAL DE-ICING FACILITY', + code: null, + type: 0, + type_key: 'Unknown', + meta: [], + has_georeferences: false, + }, + { + id: '61435794-26f0-4f15-9969-f8398885bb41', + parent_id: null, + airport_icao: 'CYYZ', + name: 'DE-ICING OPERATIONS', + code: null, + type: 0, + type_key: 'Unknown', + meta: [], + has_georeferences: false, + }, + { + id: 'ccc05761-8a20-4e8d-bf7b-90cb9650fbbc', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ENGINE FAN BLADE ICE SHEDDING PROCEDURES GM-4C', + code: null, + type: 0, + type_key: 'Unknown', + meta: [], + has_georeferences: false, + }, + { + id: '30229d81-35b5-482d-9514-02d491c588db', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ENGINE FAN BLADE ICE SHEDDING PROCEDURES GM-4D', + code: null, + type: 0, + type_key: 'Unknown', + meta: [], + has_georeferences: false, + }, + { + id: '68dab719-a954-44dc-a304-de9ff0485170', + parent_id: null, + airport_icao: 'CYYZ', + name: 'ENGINE FAN BLADE ICE SHEDDING PROCEDURES GM-4E', + code: null, + type: 0, + type_key: 'Unknown', + meta: [], + has_georeferences: false, + }, + { + id: 'daad95a1-9112-4529-b5e8-cbd546959ca6', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NOISE ABATEMENT PROCEDURES', + code: null, + type: 0, + type_key: 'Unknown', + meta: [], + has_georeferences: false, + }, + { + id: '3b0e3c18-4af4-47d9-a7ab-4e2410779771', + parent_id: null, + airport_icao: 'CYYZ', + name: 'NOISE OPERATING RESTRICTIONS', + code: null, + type: 0, + type_key: 'Unknown', + meta: [], + has_georeferences: false, + }, + ], + }, + onboarding: { has_discord: '211253507968204816' }, + contributeUrl: 'https://chartfox.org/contribute/chart/:chartId:/georeference/edit', + auth: { + user: { + id: 15933, + first_name: 'Benjamin', + last_name: 'Schubert', + full_name: 'Benjamin Schubert', + abilities: { + developer: false, + admin: false, + }, + organisations: [], + notifications: { number_unread: 0 }, + }, + }, + }, + url: '/CYYZ', + version: '5769e79e17d5b4249fff32521e8f7881', + }, + 'KBOS': { + component: 'charts/index', + props: { + errors: {}, + analytics_event: null, + flash: { + error_message: null, + success_message: null, + popup_message: null, + page_data: null, + }, + settings: { + feature: { + enable_contribution_centre: true, + enable_donations: true, + enable_api_applications: true, + }, + consented_cookies: [ + 'chartfox_cookie_consent', + 'chartfoxv2_session', + 'XSRF-TOKEN', + 'chartfox_user_pat', + 'remember_web_*', + ], + }, + config: { + browser_extensions: { + chromium: 'https://chrome.google.com/webstore/detail/chartfox-browser-extensio/gbpidgkkacbjclomlpoajjmpkflgneep', + firefox: 'https://addons.mozilla.org/en-GB/firefox/addon/chartfox-browser-extension/', + }, + base_uri: { api: 'https://api.chartfox.org:443' }, + }, + airport: { + id: 19919, + ident: 'KBOS', + icao_code: 'KBOS', + iata_code: 'BOS', + name: 'Logan International Airport', + type: 'large_airport', + latitude: 42.3643, + longitude: -71.005203, + elevation_ft: 20, + iso_a2_country: 'US', + service_notifications: [], + has_charts: true, + has_sources: true, + }, + groupedCharts: { + 3: [ + { + id: '2cb4f027-856e-47a2-9dc3-0777258a8844', + parent_id: null, + airport_icao: 'KBOS', + name: 'AIRPORT DIAGRAM', + code: null, + type: 3, + type_key: 'GroundLayout', + meta: [], + has_georeferences: true, + }, + ], + 0: [ + { + id: '737c5fbe-873a-4940-b29e-8fdfd102d42c', + parent_id: null, + airport_icao: 'KBOS', + name: 'ALTERNATE MINIMUMS', + code: null, + type: 0, + type_key: 'Unknown', + meta: [], + has_georeferences: false, + }, + { + id: '108808bb-3e31-4c89-a715-672c23cc146c', + parent_id: null, + airport_icao: 'KBOS', + name: 'DIVERSE VECTOR AREA', + code: null, + type: 0, + type_key: 'Unknown', + meta: [], + has_georeferences: false, + }, + { + id: 'a123e005-cff0-4fb3-a4c4-22dd03120297', + parent_id: null, + airport_icao: 'KBOS', + name: 'HOT SPOT', + code: null, + type: 0, + type_key: 'Unknown', + meta: [], + has_georeferences: false, + }, + { + id: 'b8544992-eff1-41b6-8a83-31d5325c8dc7', + parent_id: null, + airport_icao: 'KBOS', + name: 'LAHSO', + code: null, + type: 0, + type_key: 'Unknown', + meta: [], + has_georeferences: false, + }, + ], + 4: [ + { + id: 'ccb2bb27-7935-4d1b-9787-32d8e7592b01', + parent_id: null, + airport_icao: 'KBOS', + name: 'BLZZR SIX (RNAV)', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'a78496f9-e3fa-4748-b24c-355b39f6f977', + parent_id: 'ccb2bb27-7935-4d1b-9787-32d8e7592b01', + airport_icao: 'KBOS', + name: 'BLZZR SIX (RNAV), CONT.1', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '99c8508b-f71a-4ab6-a45b-0c02da57bdf1', + parent_id: null, + airport_icao: 'KBOS', + name: 'BRUWN SEVEN (RNAV)', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '1820492c-a67e-4328-a278-9f42d1b45c81', + parent_id: '99c8508b-f71a-4ab6-a45b-0c02da57bdf1', + airport_icao: 'KBOS', + name: 'BRUWN SEVEN (RNAV), CONT.1', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'be5b83cf-a73c-4af5-9789-0500946422dc', + parent_id: null, + airport_icao: 'KBOS', + name: 'CELTK SEVEN (RNAV)', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '366c88a8-ba80-4d95-8b34-76b04ac580c6', + parent_id: 'be5b83cf-a73c-4af5-9789-0500946422dc', + airport_icao: 'KBOS', + name: 'CELTK SEVEN (RNAV), CONT.1', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '65c1841f-05ca-42e4-bf3f-12e642591a24', + parent_id: null, + airport_icao: 'KBOS', + name: 'HYLND SEVEN (RNAV)', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '210222bc-429e-4e6c-b2c1-92a5b912d816', + parent_id: '65c1841f-05ca-42e4-bf3f-12e642591a24', + airport_icao: 'KBOS', + name: 'HYLND SEVEN (RNAV), CONT.1', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '7dba61fc-a82d-4da0-a800-b09ddfd2aa1a', + parent_id: null, + airport_icao: 'KBOS', + name: 'LBSTA EIGHT (RNAV)', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '0c5bf64e-2d8c-4bc0-94b1-a92e352f30db', + parent_id: '7dba61fc-a82d-4da0-a800-b09ddfd2aa1a', + airport_icao: 'KBOS', + name: 'LBSTA EIGHT (RNAV), CONT.1', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '1ee13155-9e48-4f43-b3d1-576799fec173', + parent_id: null, + airport_icao: 'KBOS', + name: 'LOGAN FOUR', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '89f82400-7e28-4741-8d3b-43c7d4be77d0', + parent_id: '1ee13155-9e48-4f43-b3d1-576799fec173', + airport_icao: 'KBOS', + name: 'LOGAN FOUR, CONT.1', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '8877620b-77d6-4732-ab35-51d345ed6485', + parent_id: null, + airport_icao: 'KBOS', + name: 'PATSS SEVEN (RNAV)', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '1d176df8-d9d7-4735-ae03-50dfd23c5d2b', + parent_id: '8877620b-77d6-4732-ab35-51d345ed6485', + airport_icao: 'KBOS', + name: 'PATSS SEVEN (RNAV), CONT.1', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: '15f227f3-72d3-42e4-b4d4-b2e810c7be46', + parent_id: null, + airport_icao: 'KBOS', + name: 'REVSS SIX (RNAV)', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'e4b2073a-41fd-446e-86b3-e67a2bc47875', + parent_id: '15f227f3-72d3-42e4-b4d4-b2e810c7be46', + airport_icao: 'KBOS', + name: 'REVSS SIX (RNAV), CONT.1', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'ea5dddd1-1aeb-442c-abfb-0f0a8750c5c7', + parent_id: null, + airport_icao: 'KBOS', + name: 'SSOXS SEVEN (RNAV)', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'dc510b9f-c870-47e5-88ee-a35d1506409d', + parent_id: 'ea5dddd1-1aeb-442c-abfb-0f0a8750c5c7', + airport_icao: 'KBOS', + name: 'SSOXS SEVEN (RNAV), CONT.1', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + { + id: 'e8a84b40-a69d-4295-9745-3b16204efd38', + parent_id: null, + airport_icao: 'KBOS', + name: 'WYLYY FIVE (RNAV)', + code: null, + type: 4, + type_key: 'SID', + meta: [], + has_georeferences: false, + }, + ], + 5: [ + { + id: '85a33cee-b744-4b9d-8f14-f9f656e501a9', + parent_id: null, + airport_icao: 'KBOS', + name: 'GARDNER FOUR', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '4bec12ea-b125-4935-8c39-f57a187d44fe', + parent_id: null, + airport_icao: 'KBOS', + name: 'JFUND TWO (RNAV)', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '21889e27-7b68-4191-b26c-33c7ddcf8d77', + parent_id: '4bec12ea-b125-4935-8c39-f57a187d44fe', + airport_icao: 'KBOS', + name: 'JFUND TWO (RNAV), CONT.1', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '01403f48-bb3b-4368-9344-785fa2ec879b', + parent_id: '4bec12ea-b125-4935-8c39-f57a187d44fe', + airport_icao: 'KBOS', + name: 'JFUND TWO (RNAV), CONT.2', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '640c892e-7c67-442e-8d1e-0a22f46a4775', + parent_id: null, + airport_icao: 'KBOS', + name: 'NORWICH SEVEN', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '02756c1e-a8e1-4bad-aeb4-62250ab2d650', + parent_id: null, + airport_icao: 'KBOS', + name: 'OOSHN FIVE (RNAV)', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'c4ebab7d-4fe9-4ed2-8e82-e97b362842d0', + parent_id: '02756c1e-a8e1-4bad-aeb4-62250ab2d650', + airport_icao: 'KBOS', + name: 'OOSHN FIVE (RNAV), CONT.1', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '64e513cb-631b-4412-944b-984547bf0632', + parent_id: null, + airport_icao: 'KBOS', + name: 'ROBUC THREE (RNAV)', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: '0801f7e5-34f5-4980-b889-954067074d3c', + parent_id: '64e513cb-631b-4412-944b-984547bf0632', + airport_icao: 'KBOS', + name: 'ROBUC THREE (RNAV), CONT.1', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'bfe5ca9e-63b2-4ecd-8925-f52b7185413c', + parent_id: '64e513cb-631b-4412-944b-984547bf0632', + airport_icao: 'KBOS', + name: 'ROBUC THREE (RNAV), CONT.2', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + { + id: 'b7ae3380-1f01-46e9-834b-e25d760bd435', + parent_id: null, + airport_icao: 'KBOS', + name: 'WOONS TWO', + code: null, + type: 5, + type_key: 'STAR', + meta: [], + has_georeferences: false, + }, + ], + 6: [ + { + id: '904ec1c3-c825-4e24-832f-3122ea707189', + parent_id: null, + airport_icao: 'KBOS', + name: 'ILS OR LOC RWY 04R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '04R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'a4ca460e-0954-49b4-a283-896213609bf5', + parent_id: null, + airport_icao: 'KBOS', + name: 'ILS OR LOC RWY 15R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '15R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '30ade611-f224-4d98-bfd5-d8be282b3e6b', + parent_id: null, + airport_icao: 'KBOS', + name: 'ILS OR LOC RWY 22L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '22L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'a6a2cf08-8a9c-4fe7-83c0-02f56cfc8a33', + parent_id: null, + airport_icao: 'KBOS', + name: 'ILS OR LOC RWY 27', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '27', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '9bc05652-1223-4afc-b858-c1aee2315da4', + parent_id: null, + airport_icao: 'KBOS', + name: 'ILS OR LOC RWY 33L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '33L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '95ea7c53-cd65-4a96-838b-8eb92d2f334c', + parent_id: null, + airport_icao: 'KBOS', + name: 'ILS RWY 04R (CAT II - III)', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '04R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'e7dac924-07f6-4573-82be-3dec671ed74e', + parent_id: null, + airport_icao: 'KBOS', + name: 'ILS RWY 04R (SA CAT I)', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '04R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '3d275e14-46b7-474e-a10f-f98829701478', + parent_id: null, + airport_icao: 'KBOS', + name: 'ILS RWY 33L (CAT II - III)', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '33L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'a628d783-8c5a-4604-962b-f8bb7cd8e5e9', + parent_id: null, + airport_icao: 'KBOS', + name: 'ILS RWY 33L (SA CAT I)', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '33L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 0, + ], + }, + ], + has_georeferences: false, + }, + { + id: '488e0df2-a5ae-4f90-b9aa-2e556373ab86', + parent_id: null, + airport_icao: 'KBOS', + name: 'LIGHT VISUAL RWY 33L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '33L', + ], + }, + ], + has_georeferences: false, + }, + { + id: '3fb8a404-d71d-469f-aa89-a5dcc9934871', + parent_id: null, + airport_icao: 'KBOS', + name: 'RNAV (GPS) RWY 04L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '04L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '6299828a-41c0-48df-9376-23cb42bb0b61', + parent_id: null, + airport_icao: 'KBOS', + name: 'RNAV (GPS) RWY 04R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '04R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '18f63df0-28d9-427b-973b-7215bf3d8e16', + parent_id: null, + airport_icao: 'KBOS', + name: 'RNAV (GPS) RWY 15R', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '15R', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '9d9a927e-4b20-4f8c-ae21-b5c8b8add01a', + parent_id: null, + airport_icao: 'KBOS', + name: 'RNAV (GPS) RWY 27', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '27', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'd40a24aa-e347-45ad-8bcc-a877475d61fe', + parent_id: null, + airport_icao: 'KBOS', + name: 'RNAV (GPS) RWY 32', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '32', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'b18cfbef-dd64-41bb-aed4-08f4fe107ec9', + parent_id: null, + airport_icao: 'KBOS', + name: 'RNAV (GPS) X RWY 22L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '22L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '13292429-055b-4cd4-b44c-090927c6fd2b', + parent_id: null, + airport_icao: 'KBOS', + name: 'RNAV (GPS) Y RWY 22L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '22L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '6a826e03-903e-41a6-8ed6-a602f80145f5', + parent_id: null, + airport_icao: 'KBOS', + name: 'RNAV (GPS) Z RWY 33L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '33L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: '125fcadd-b8c0-4ce6-a548-966c78d995f6', + parent_id: null, + airport_icao: 'KBOS', + name: 'RNAV (RNP) X RWY 33L', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 0, + type_key: 'Runways', + value: [ + '33L', + ], + }, + { + type: 2, + type_key: 'ProcedureType', + value: [ + 50, + ], + }, + ], + has_georeferences: false, + }, + { + id: 'ab1e43b5-8cb0-4cdd-a747-b0ede48039a2', + parent_id: null, + airport_icao: 'KBOS', + name: 'VOR-A', + code: null, + type: 6, + type_key: 'Approach', + meta: [ + { + type: 2, + type_key: 'ProcedureType', + value: [ + 52, + ], + }, + ], + has_georeferences: false, + }, + ], + }, + onboarding: { has_discord: '211253507968204816' }, + contributeUrl: 'https://chartfox.org/contribute/chart/:chartId:/georeference/edit', + auth: { + user: { + id: 15933, + first_name: 'Benjamin', + last_name: 'Schubert', + full_name: 'Benjamin Schubert', + abilities: { + developer: false, + admin: false, + }, + organisations: [], + notifications: { number_unread: 0 }, + }, + }, + }, + url: '/KBOS', + version: '5769e79e17d5b4249fff32521e8f7881', + }, +}; diff --git a/fbw-common/src/systems/shared/src/chartfox/types.ts b/fbw-common/src/systems/shared/src/chartfox/types.ts new file mode 100644 index 00000000000..cb10cfbcbcf --- /dev/null +++ b/fbw-common/src/systems/shared/src/chartfox/types.ts @@ -0,0 +1,65 @@ +import { ChartFileType } from 'instruments/src/EFB/Store/features/navigationPage'; + +export type ChartFoxChart = { + id: string, + parentId: string | null, + name: string, + type: number, + typeKey: string, + url: string, + sourceUrl: string, + sourceUrlType: ChartFileType, + georefs: ChartFoxGeoRef[], + hasGeoreferences: boolean, + updatedAt: string, + runways: string[], +} + +export type ChartFoxGeoRef = { + tx: number, + ty: number, + k: number, + transformAngle: number, + pdfPageRotation: number, + page: number, +} + +export type ChartFoxGroupedChart = { + id: string, + name: string, + type: number, + typeKey: string, + runways: string[], +} + +export type ChartFoxAirportCharts = { + arrival: ChartFoxGroupedChart[], + approach: ChartFoxGroupedChart[], + airport: ChartFoxGroupedChart[], + departure: ChartFoxGroupedChart[], + reference: ChartFoxGroupedChart[], +} + +export type ChartFoxAirportIndex = { + id: number, + ident: string, + icaoCode: string, + iataCode: string, + name: string, + type: string, + latitude: number, + longitude: number, + elevationFt: number, + isoA2Country: string, + hasCharts: boolean, + hasSources: boolean, + groupedCharts: ChartFoxAirportCharts, +} + +export const emptyChartFoxCharts: ChartFoxAirportCharts = { + arrival: [] as ChartFoxGroupedChart[], + approach: [] as ChartFoxGroupedChart[], + airport: [] as ChartFoxGroupedChart[], + departure: [] as ChartFoxGroupedChart[], + reference: [] as ChartFoxGroupedChart[], +}; diff --git a/fbw-common/src/systems/shared/src/index.ts b/fbw-common/src/systems/shared/src/index.ts index 196cc0e4992..e30e3450968 100644 --- a/fbw-common/src/systems/shared/src/index.ts +++ b/fbw-common/src/systems/shared/src/index.ts @@ -19,13 +19,13 @@ export * from './arinc429'; export * from './array'; export * from './ata'; export * from './bitFlags'; +export * from './chartfox'; export * from './config'; export * from './failures'; export * from './localization'; export * from './logic'; export * from './navdata'; export * from './navigraph'; -export * from './navigraph'; export * from './notification'; export * from './parseMetar'; export * from './persistence'; diff --git a/fbw-common/src/systems/shared/src/simbridge/components/Viewer.ts b/fbw-common/src/systems/shared/src/simbridge/components/Viewer.ts index b6df2280ba1..19167484db4 100644 --- a/fbw-common/src/systems/shared/src/simbridge/components/Viewer.ts +++ b/fbw-common/src/systems/shared/src/simbridge/components/Viewer.ts @@ -60,6 +60,60 @@ export class Viewer { throw new Error('File name or page number missing'); } + /** + * Used to retrieve a streamable image of specified page within a given PDF URL + * @param url required field, URL link to the pdf + * @param pageNumber required field, The page of the PDF file + * @returns a Blob + */ + public static async getPDFPageFromUrl(url: string, pageNumber: number): Promise { + if (!ClientState.getInstance().isConnected()) { + throw new Error('SimBridge is not connected.'); + } + if (url || pageNumber) { + const encodedUrl = encodeURIComponent(url); + const response = await fetchWithTimeout(`${getSimBridgeUrl()}/api/v1/utility/pdf/fromUrl?encodedUrl=${encodedUrl}&pagenumber=${pageNumber}`); + if (response.ok) { + return response.blob(); + } + throw new Error(`SimBridge Error: ${response.status}`); + } + throw new Error('File name or page number missing'); + } + + /** + * Used to retrieve a URL to the rendered image of the PDF page at the specified URL. + * It internally calls getPDFPageFromUrl and then calls createObjectURL(). + * @see https://developer.mozilla.org/en-US/docs/web/api/url/createobjecturl + * @param url required field, URL link to the pdf + * @param pageNumber required field, The page of the PDF file + * @returns url to the image (object blob) of the PDF page + */ + public static async getImageUrlFromPdfUrl(url: string, pageNumber: number): Promise { + const blob = await Viewer.getPDFPageFromUrl(url, pageNumber); + return URL.createObjectURL(blob); + } + + /** + * Retrieve the number of pages within a PDF file at the specified URL + * @param url required field, URL link to the pdf + * @returns A number + */ + public static async getPDFPageCountFromUrl(url: string): Promise { + if (!ClientState.getInstance().isConnected()) { + throw new Error('SimBridge is not connected.'); + } + if (url) { + const encodedUrl = encodeURIComponent(url); + const response = await fetchWithTimeout(`${getSimBridgeUrl()}/api/v1/utility/pdf/fromUrl/numpages?encodedUrl=${encodedUrl}`); + if (response.ok) { + return response.json(); + } + throw new Error(`SimBridge Error: ${response.status}`); + } + throw new Error('File name or page number missing'); + } + /** * Used to retrieve a list of filenames within the PDF folder * @returns an Array of strings diff --git a/package.json b/package.json index 763e61378d0..5443107aae0 100644 --- a/package.json +++ b/package.json @@ -203,5 +203,8 @@ "use-long-press": "^1.1.2", "uuid": "^9.0.0", "ws": "^7.4.5" + }, + "overrides": { + "@types/react": "17.0.11" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3bfeee2c909..69c4fefa98a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -219,7 +219,7 @@ devDependencies: version: 27.5.2 '@types/react': specifier: ~17.0.11 - version: 17.0.71 + version: 17.0.11 '@types/react-dom': specifier: ~17.0.8 version: 17.0.25 @@ -2928,7 +2928,7 @@ packages: /@types/hoist-non-react-statics@3.3.5: resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==} dependencies: - '@types/react': 17.0.71 + '@types/react': 17.0.11 hoist-non-react-statics: 3.3.2 dev: false @@ -2986,20 +2986,20 @@ packages: /@types/react-canvas-draw@1.2.3: resolution: {integrity: sha512-0Y89n63EwSFEN1cITPtq6F99SGDwhXWbbxcdw2WghoyIl4TwLCmv2Igm6B0+c8ArN6I0LFpuHdjpg7pPOsydtw==} dependencies: - '@types/react': 17.0.71 + '@types/react': 17.0.11 dev: false /@types/react-dom@17.0.25: resolution: {integrity: sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==} dependencies: - '@types/react': 17.0.71 + '@types/react': 17.0.11 dev: true /@types/react-redux@7.1.32: resolution: {integrity: sha512-YJYV0M27cyHHJIacaRsZRx5OETzK8KWjEGnix7UH3ngItYo4It0MUBzU6WNwqnwhbrPw5wx9KXluuoTZ85Gg7A==} dependencies: '@types/hoist-non-react-statics': 3.3.5 - '@types/react': 17.0.71 + '@types/react': 17.0.11 hoist-non-react-statics: 3.3.2 redux: 4.2.1 dev: false @@ -3008,7 +3008,7 @@ packages: resolution: {integrity: sha512-Go0vxZSigXTyXx8xPkGiBrrc3YbBs82KE14WENMLS6TSUKcRFSmYVbL19zFOnNFqJhqrPqEs2h5eUpJhSRrwZw==} dependencies: '@types/history': 5.0.0 - '@types/react': 17.0.71 + '@types/react': 17.0.11 '@types/react-router': 5.1.20 dev: true @@ -3016,11 +3016,11 @@ packages: resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} dependencies: '@types/history': 4.7.11 - '@types/react': 17.0.71 + '@types/react': 17.0.11 dev: true - /@types/react@17.0.71: - resolution: {integrity: sha512-lfqOu9mp16nmaGRrS8deS2Taqhd5Ih0o92Te5Ws6I1py4ytHBcXLqh0YIqVsViqwVI5f+haiFM6hju814BzcmA==} + /@types/react@17.0.11: + resolution: {integrity: sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==} dependencies: '@types/prop-types': 15.7.11 '@types/scheduler': 0.16.8