From 656cd6476b0ee303c930dc6b986bab94663512b0 Mon Sep 17 00:00:00 2001 From: mayurrawte Date: Fri, 3 Jul 2026 14:41:26 +0530 Subject: [PATCH] feat: accept UN/LOCODE port codes + loadPorts(url) for CDN loading seaRoute/seaRouteMulti/seaRouteAlternatives accept a UN/LOCODE string anywhere a point is accepted (mixable with coordinates). The ~1600-port dataset ships behind the searoute-ts/ports subpath. Adds loadPorts(url) to fetch and register the dataset at runtime instead of bundling it, and emits a raw dist/ports.json so jsDelivr/unpkg serve it versioned. Closes #7 --- CHANGELOG.md | 20 +++++++ DOCS.md | 6 +- README.md | 79 +++++++++++++++++++++++++- package.json | 11 +++- scripts/build-ports.cjs | 89 +++++++++++++++++++++++++++++ scripts/emit-ports-json.cjs | 13 +++++ src/index.spec.ts | 10 ++++ src/index.ts | 19 ++++++- src/lib/ports.ts | 110 ++++++++++++++++++++++++++++++++++++ src/load-ports.spec.ts | 53 +++++++++++++++++ src/ports/data.ts | 14 +++++ src/ports/index.spec.ts | 63 +++++++++++++++++++++ src/ports/index.ts | 43 ++++++++++++++ 13 files changed, 524 insertions(+), 6 deletions(-) create mode 100644 scripts/build-ports.cjs create mode 100644 scripts/emit-ports-json.cjs create mode 100644 src/lib/ports.ts create mode 100644 src/load-ports.spec.ts create mode 100644 src/ports/data.ts create mode 100644 src/ports/index.spec.ts create mode 100644 src/ports/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d7f7d7..3bf2b00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## Unreleased + +### Added +- Port-code (UN/LOCODE) input: `seaRoute`, `seaRouteMulti` and + `seaRouteAlternatives` accept a code string (e.g. `seaRoute('CNSHA', 'NLRTM')`) + anywhere a point is accepted, mixable with coordinates. The ~1 600-port dataset + ships behind the new `searoute-ts/ports` subpath export (source: + [marchah/sea-ports](https://github.com/marchah/sea-ports), MIT, based on + UN/LOCODE), so the core stays lean — importing the subpath registers the + resolver. New `searoute-ts/ports` exports `lookupPort`, `resolvePort`, `PORTS`, + `PORT_COUNT`, `Port`, `PortRecord`; the core adds `UnknownPortError`, + `registerPortResolver` and a `PortResolver` type. Unknown codes throw + `UnknownPortError`. (#7) +- `loadPorts(url, options?)` — optionally fetch the UN/LOCODE port dataset from a + URL/CDN at runtime and register it, enabling `seaRoute('CNSHA', 'NLRTM')` + without bundling the `searoute-ts/ports` dataset. The dataset also ships as a + raw `dist/ports.json`, so it is served versioned by jsDelivr/unpkg + (`https://cdn.jsdelivr.net/npm/searoute-ts@latest/dist/ports.json`). Uses the + global `fetch`; pass `{ fetch }` to override. (#7) + ## 2.1.0 — 2026-07-03 ### Added diff --git a/DOCS.md b/DOCS.md index 5852856..4008eb9 100644 --- a/DOCS.md +++ b/DOCS.md @@ -48,6 +48,10 @@ Two side-effects: - `properties.originSnapKm` and `properties.destinationSnapKm` report how far the inputs moved when snapped. Useful for debugging routes that look "off" — if `originSnapKm` is 600 km, your input was nowhere near the sea. - The `maxSnapDistanceKm` option throws `SnapFailedError` if the input is further than the threshold. Without it, a typo'd coordinate in the middle of a continent silently snaps to the nearest coast. +### Port-code input (UN/LOCODE) + +An origin or destination can be a UN/LOCODE string (e.g. `'CNSHA'`). Because the port dataset would bloat the core, it ships behind the `searoute-ts/ports` subpath export; importing that module registers a resolver into the core (or supply your own via `registerPortResolver`). A string input is resolved to `[lon, lat]` first, then follows the same snapping path as a coordinate. Codes are matched case-insensitively with whitespace stripped; unknown codes throw `UnknownPortError`. The dataset (~1 600 seaports) comes from [marchah/sea-ports](https://github.com/marchah/sea-ports) (MIT), derived from UN/LOCODE — see `scripts/build-ports.cjs`. + --- ## 3. Shortest path @@ -175,7 +179,7 @@ The finder cache is keyed by network identity, so swapping networks at runtime w - **Navigation.** Routes are graph paths, not great-circle or rhumb-line tracks. Don't sail them. - **Weather-aware.** No wave height, currents, ice forecasts, or seasonal variation. The Northwest/Northeast Passages are gated by `allowArctic`, not by an ice model. For weather routing see [VISIR-2](https://gmd.copernicus.org/articles/17/4355/2024/). -- **Port-aware.** No port database, ETAs, or berth selection. Routes terminate at the nearest network vertex to the input coordinates. +- **Port-aware.** UN/LOCODE strings are accepted as input (via `searoute-ts/ports`) and resolved to coordinates, but there are no ETAs, berth selection, or terminal-level detail. Routes terminate at the nearest network vertex to the (resolved) input coordinates. - **Emissions-grade.** Duration estimates assume constant speed in calm water. For CO₂ accounting see [searoutes.com](https://searoutes.com/co2-api/) or implement your own model on top of the duration. --- diff --git a/README.md b/README.md index 75ee889..f830798 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,56 @@ seaRoute(shanghai, newYork, { // → Panama auto-blocked, route goes via Suez ``` +### Port codes (UN/LOCODE) + +```ts +import 'searoute-ts/ports'; // enables UN/LOCODE strings on the core API +import { seaRoute } from 'searoute-ts'; + +seaRoute('CNSHA', 'NLRTM'); // Shanghai → Rotterdam +seaRoute('CNSHA', [4.4, 51.9]); // mixing a code and coordinates is fine too +``` + +The ~1 600-port dataset lives behind the `searoute-ts/ports` subpath so the core +stays lean — importing it registers the resolver. You can also resolve codes +yourself: + +```ts +import { lookupPort, resolvePort } from 'searoute-ts/ports'; + +lookupPort('SGSIN'); // → { code, name: 'Singapore', country, coordinates: [lon, lat] } +resolvePort('SGSIN'); // → [103.85, 1.28] +``` + +Unknown codes throw `UnknownPortError`. See [Port codes](#port-codes-unlocode-1) below for provenance. + +#### Load the port dataset from a CDN instead of bundling it + +Don't want to bundle the ~135 KB dataset? Fetch it at runtime with `loadPorts` — +the analog of [`loadNetwork`](#fetch-the-network-from-a-url-instead-of-bundling-it-optional). +The dataset also ships as a raw `dist/ports.json`, so **jsDelivr/unpkg serve it +versioned for free**: + +```ts +import { seaRoute, loadPorts } from 'searoute-ts'; + +// Pin a version for reproducibility, or use @latest to always get the newest. +await loadPorts('https://cdn.jsdelivr.net/npm/searoute-ts@latest/dist/ports.json'); + +seaRoute('CNSHA', 'NLRTM'); // works — the fetched dataset is now registered +``` + +``` +https://cdn.jsdelivr.net/npm/searoute-ts@latest/dist/ports.json # newest +https://cdn.jsdelivr.net/npm/searoute-ts@/dist/ports.json # frozen/immutable +``` + +(`dist/ports.json` ships from the release that adds port codes onward — pin any +version at or after it for reproducibility.) + +`loadPorts` registers the fetched dataset (so code strings resolve) and returns +it. It uses the global `fetch` (Node ≥18 / browsers); pass `{ fetch }` to override. + ### Multi-leg / port rotation ```ts @@ -198,7 +248,8 @@ seaRoute(origin, destination, { }); ``` -Inputs can be `[lon, lat]` arrays, GeoJSON `Feature`, or bare `Point` objects. +Inputs can be `[lon, lat]` arrays, GeoJSON `Feature`, bare `Point` objects, +or a UN/LOCODE string (e.g. `'CNSHA'`) once `searoute-ts/ports` is imported. ### Antimeridian (dateline) handling @@ -284,6 +335,8 @@ import { clearFinderCache, // drop the PathFinder cache (tests / hot reload) SnapFailedError, NoRouteError, + UnknownPortError, // thrown for unresolved UN/LOCODE strings + registerPortResolver, // plug in a custom port dataset // types type Passage, type Antimeridian, @@ -295,8 +348,32 @@ import { type MarnetNetwork, type MarnetProperties, } from 'searoute-ts'; + +import { + lookupPort, // UN/LOCODE → { code, name, country, coordinates } + resolvePort, // UN/LOCODE → [lon, lat] + PORTS, // the raw dataset (Record) + PORT_COUNT, + type Port, + type PortRecord, +} from 'searoute-ts/ports'; ``` +## Port codes (UN/LOCODE) + +Origins and destinations may be given as UN/LOCODE strings (e.g. `'CNSHA'`) +instead of coordinates. The port dataset ships behind the `searoute-ts/ports` +subpath export, so consumers only pay for it if they use it — importing the +subpath (for any of its exports, or purely for its side effect) registers a +resolver into the core so `seaRoute('CNSHA', 'NLRTM')` works. + +- **~1 600 seaports**, keyed by UN/LOCODE (primary codes and aliases). +- **Source:** [marchah/sea-ports](https://github.com/marchah/sea-ports) (MIT), + itself derived from **UN/LOCODE**. Regenerate with `scripts/build-ports.cjs`. +- **Coordinates are approximate** (port-city granularity) — the routing engine + snaps them onto the network anyway, so this is fine for distance/visualisation. +- Unknown or malformed codes throw `UnknownPortError`. + ## How it works A two-page deep-dive (graph data, snapping, Dijkstra, restrictions, diff --git a/package.json b/package.json index 105172e..55b6f1d 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,16 @@ "import": "./dist/esm/index.js", "require": "./dist/cjs/index.js" }, + "./ports": { + "types": "./dist/types/ports/index.d.ts", + "import": "./dist/esm/ports/index.js", + "require": "./dist/cjs/ports/index.js" + }, "./package.json": "./package.json" }, - "sideEffects": false, + "sideEffects": [ + "**/ports/index.js" + ], "repository": { "type": "git", "url": "git+https://github.com/mayurrawte/searoute-ts.git" @@ -53,7 +60,7 @@ ], "scripts": { "clean": "rm -rf dist build coverage", - "build": "npm run clean && npm run build:cjs && npm run build:esm && npm run build:types && node scripts/fixup-cjs.cjs && node scripts/copy-marnet.cjs dist", + "build": "npm run clean && npm run build:cjs && npm run build:esm && npm run build:types && node scripts/fixup-cjs.cjs && node scripts/copy-marnet.cjs dist && node scripts/emit-ports-json.cjs", "build:cjs": "tsc -p tsconfig.cjs.json", "build:esm": "tsc -p tsconfig.esm.json", "build:types": "tsc -p tsconfig.types.json", diff --git a/scripts/build-ports.cjs b/scripts/build-ports.cjs new file mode 100644 index 0000000..0dd2ccf --- /dev/null +++ b/scripts/build-ports.cjs @@ -0,0 +1,89 @@ +/* Generate src/ports/data.ts from the marchah/sea-ports dataset. + * + * Source: https://github.com/marchah/sea-ports (MIT), a curated set of seaports + * keyed by UN/LOCODE with coordinates, itself derived from UN/LOCODE. We keep + * only { name, country, coordinates } and index every UN/LOCODE alias so both a + * port's primary code and its aliases resolve. + * + * The data is embedded as a JSON string + JSON.parse() so the TypeScript + * compiler doesn't pretty-print a large object literal. Ships behind the + * `searoute-ts/ports` subpath export so the core stays lean. + * + * Refresh with: + * curl -sSL https://raw.githubusercontent.com/marchah/sea-ports/master/lib/ports.json -o /tmp/seaports.json + * node scripts/build-ports.cjs /tmp/seaports.json + */ +const fs = require('fs'); +const path = require('path'); + +const input = process.argv[2] || '/tmp/seaports.json'; +const outPath = path.resolve(__dirname, '..', 'src/ports/data.ts'); + +const source = JSON.parse(fs.readFileSync(input, 'utf8')); + +const isValidCode = (code) => /^[A-Z]{2}[A-Z0-9]{3}$/.test(code); +const isValidCoord = (c) => + Array.isArray(c) && + c.length === 2 && + Number.isFinite(c[0]) && + Number.isFinite(c[1]) && + Math.abs(c[0]) <= 180 && + Math.abs(c[1]) <= 90; + +const round = (n) => Math.round(n * 1e6) / 1e6; + +const ports = {}; +let skipped = 0; +for (const [key, entry] of Object.entries(source)) { + const coords = entry.coordinates; + if (!isValidCoord(coords)) { + skipped++; + continue; + } + const record = { + name: entry.name, + country: entry.country || '', + coordinates: [round(coords[0]), round(coords[1])], + }; + const codes = new Set([key, ...(Array.isArray(entry.unlocs) ? entry.unlocs : [])]); + for (const raw of codes) { + const code = String(raw).toUpperCase(); + if (!isValidCode(code)) continue; + if (!ports[code]) ports[code] = record; // first entry wins on collisions + } +} + +// Deterministic key order keeps the generated file stable across runs. +const sorted = {}; +for (const code of Object.keys(ports).sort()) sorted[code] = ports[code]; + +const json = JSON.stringify(sorted); +const literal = JSON.stringify(json); // safe double-quoted JS string literal + +const header = `/* Auto-generated by scripts/build-ports.cjs. Do not edit by hand. + * + * Curated seaport dataset keyed by UN/LOCODE, derived from + * https://github.com/marchah/sea-ports (MIT), itself based on UN/LOCODE. + * Only { name, country, coordinates: [lon, lat] } is kept, indexed by every + * UN/LOCODE alias. Embedded as a JSON string parsed once at module load. + */ +export type PortRecord = { name: string; country: string; coordinates: [number, number] }; + +const PORTS: Record = JSON.parse(${literal}) as Record; + +export default PORTS; +`; + +fs.mkdirSync(path.dirname(outPath), { recursive: true }); +fs.writeFileSync(outPath, header); +console.log( + 'wrote', + path.relative(path.resolve(__dirname, '..'), outPath), + '—', + Object.keys(sorted).length, + 'codes,', + skipped, + 'entries skipped (no coords),', + (fs.statSync(outPath).size / 1024).toFixed(0), + 'KB', +); diff --git a/scripts/emit-ports-json.cjs b/scripts/emit-ports-json.cjs new file mode 100644 index 0000000..e022f27 --- /dev/null +++ b/scripts/emit-ports-json.cjs @@ -0,0 +1,13 @@ +// Emit the UN/LOCODE port dataset as a raw dist/ports.json so it is served +// versioned by the npm CDNs (jsDelivr/unpkg) for use with loadPorts(url). +// Runs after the CJS build, reading the compiled dataset module. +const fs = require('node:fs'); +const path = require('node:path'); + +const dataModule = path.join(__dirname, '..', 'dist', 'cjs', 'ports', 'data.js'); +const mod = require(dataModule); +const records = mod.default ?? mod; + +const out = path.join(__dirname, '..', 'dist', 'ports.json'); +fs.writeFileSync(out, JSON.stringify(records)); +console.log(`wrote ${out} (${Object.keys(records).length} port codes)`); diff --git a/src/index.spec.ts b/src/index.spec.ts index 92f479a..adff06f 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -11,6 +11,7 @@ import { seaRouteAlternatives, seaRouteMulti, SnapFailedError, + UnknownPortError, } from './index'; function pt(lon: number, lat: number): Feature { @@ -553,6 +554,15 @@ test('seaRouteAlternatives ignores antimeridian and returns LineStrings', (t) => }); for (const a of alts) t.is(a.geometry.type, 'LineString'); }); +// ── Port codes (core, without the dataset) ────────────────────────────────── + +test('a UN/LOCODE string throws UnknownPortError when no dataset is registered', (t) => { + // The core does not bundle the port dataset; without importing + // 'searoute-ts/ports', a string origin/destination cannot be resolved. + const err = t.throws(() => seaRoute('CNSHA', 'NLRTM'), { instanceOf: UnknownPortError }); + t.is(err?.code, 'CNSHA'); + t.regex(err!.message, /searoute-ts\/ports/); +}); // ── Custom network ────────────────────────────────────────────────────────── diff --git a/src/index.ts b/src/index.ts index 3fd0e84..2297471 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import { type Antimeridian, splitAtAntimeridian, unwrapCoords } from './lib/anti import { passagesBlockedByDraft } from './lib/drafts.js'; import { buildFinder, DEFAULT_MARNET, type MarnetNetwork } from './lib/finder.js'; import { bboxOf, greatCircleKm } from './lib/metrics.js'; +import { resolvePortCode } from './lib/ports.js'; import { type Passage, passagesAlong } from './lib/restrictions.js'; import { snapToNetwork } from './lib/snap.js'; @@ -23,9 +24,22 @@ export type { MarnetNetwork } from './lib/finder.js'; export { DEFAULT_MARNET, clearFinderCache } from './lib/finder.js'; export { SnapFailedError } from './lib/snap.js'; export { CANAL_MAX_DRAFT_M } from './lib/drafts.js'; +export { + UnknownPortError, + registerPortResolver, + loadPorts, + type PortResolver, + type PortDataset, + type LoadPortsOptions, +} from './lib/ports.js'; -/** Input accepted as origin/destination. */ -export type PointInput = Position | Feature | Point; +/** + * Input accepted as origin/destination. A `string` is treated as a UN/LOCODE + * port code (e.g. `'CNSHA'`); resolving codes requires importing the + * `searoute-ts/ports` subpath (or registering a resolver via + * `registerPortResolver`). + */ +export type PointInput = Position | Feature | Point | string; export type SeaRouteOptions = { /** Output unit for `properties.length`. Defaults to nautical miles. */ @@ -138,6 +152,7 @@ export class NoRouteError extends Error { } function toFeaturePoint(input: PointInput): Feature { + if (typeof input === 'string') return turfPoint(resolvePortCode(input)); if (Array.isArray(input)) return turfPoint(input); if ('geometry' in input) return input as Feature; return turfFeature(input as Point) as Feature; diff --git a/src/lib/ports.ts b/src/lib/ports.ts new file mode 100644 index 0000000..84b4c3e --- /dev/null +++ b/src/lib/ports.ts @@ -0,0 +1,110 @@ +import type { Position } from 'geojson'; + +/** + * Thrown when an origin/destination given as a UN/LOCODE string cannot be + * resolved to coordinates — either the code is unknown, or no port dataset has + * been registered (the `searoute-ts/ports` subpath was never imported). + */ +export class UnknownPortError extends Error { + readonly code: string; + constructor(code: string, datasetLoaded: boolean) { + super( + datasetLoaded + ? `Unknown port code '${code}'` + : `Cannot resolve port code '${code}': no port dataset is registered. ` + + `Import 'searoute-ts/ports' to enable UN/LOCODE lookups, or pass ` + + `coordinates instead.`, + ); + this.name = 'UnknownPortError'; + this.code = code; + } +} + +/** Resolves a normalized UN/LOCODE to `[lon, lat]`, or `undefined` if unknown. */ +export type PortResolver = (code: string) => Position | undefined; + +let activeResolver: PortResolver | undefined; + +/** + * Register the resolver used to turn UN/LOCODE strings into coordinates. + * Called as a side effect of importing `searoute-ts/ports`; can also be used to + * plug in a custom port dataset. + */ +export function registerPortResolver(resolver: PortResolver | undefined): void { + activeResolver = resolver; +} + +/** True when a port resolver has been registered. */ +export function hasPortResolver(): boolean { + return activeResolver !== undefined; +} + +/** Normalize a UN/LOCODE: trim, upper-case, drop internal whitespace. */ +export function normalizePortCode(code: string): string { + return code.trim().toUpperCase().replace(/\s+/g, ''); +} + +/** + * Resolve a UN/LOCODE string to `[lon, lat]`. + * + * @throws {UnknownPortError} when no resolver is registered or the code is + * not in the dataset. + */ +export function resolvePortCode(code: string): Position { + const normalized = normalizePortCode(code); + const coord = activeResolver ? activeResolver(normalized) : undefined; + if (!coord) throw new UnknownPortError(code, activeResolver !== undefined); + return coord; +} + +/** A UN/LOCODE → port record map, as fetched by {@link loadPorts}. */ +export type PortDataset = Record< + string, + { name?: string; country?: string; coordinates: [number, number] } +>; + +/** Options for {@link loadPorts}. */ +export type LoadPortsOptions = { + /** Custom fetch (defaults to the global `fetch`; Node ≥18 and browsers). */ + fetch?: typeof fetch; +}; + +/** + * Fetch the UN/LOCODE port dataset from a URL and register it, enabling + * `seaRoute('CNSHA', 'NLRTM')` without bundling the `searoute-ts/ports` subpath. + * + * The dataset is also published as a raw `dist/ports.json`, so it is served + * versioned by the npm CDNs — e.g. + * `https://cdn.jsdelivr.net/npm/searoute-ts@latest/dist/ports.json` (or pin a + * version like `@2.1.0` for reproducibility). You can also point at your own host. + * + * ```ts + * await loadPorts('https://cdn.jsdelivr.net/npm/searoute-ts@latest/dist/ports.json'); + * const route = seaRoute('CNSHA', 'NLRTM'); + * ``` + * + * Calling it registers the resolver as a side effect (like importing + * `searoute-ts/ports`) and returns the dataset. + * + * @throws {Error} on a non-OK HTTP response, or when the payload is not a + * UN/LOCODE → port record object. + */ +export async function loadPorts(url: string, options: LoadPortsOptions = {}): Promise { + const doFetch = options.fetch ?? globalThis.fetch; + if (typeof doFetch !== 'function') { + throw new Error( + 'loadPorts: no fetch implementation available — pass options.fetch on runtimes without a global fetch', + ); + } + const res = await doFetch(url); + if (!res.ok) { + throw new Error(`loadPorts: failed to fetch ${url} (HTTP ${res.status})`); + } + const data = (await res.json()) as unknown; + if (!data || typeof data !== 'object' || Array.isArray(data)) { + throw new Error(`loadPorts: ${url} is not a UN/LOCODE → port record object`); + } + const dataset = data as PortDataset; + registerPortResolver((code) => dataset[code]?.coordinates); + return dataset; +} diff --git a/src/load-ports.spec.ts b/src/load-ports.spec.ts new file mode 100644 index 0000000..c01c834 --- /dev/null +++ b/src/load-ports.spec.ts @@ -0,0 +1,53 @@ +import test from 'ava'; + +import { loadPorts, seaRoute, UnknownPortError } from './index'; + +// A tiny UN/LOCODE dataset whose coordinates sit near the bundled network, so a +// resolved route actually computes. Kept in its own spec file because loadPorts +// registers a process-global resolver. +const PORTS = { + XXNYC: { name: 'New York', country: 'XX', coordinates: [-74.04, 40.69] as [number, number] }, + XXLDN: { name: 'London', country: 'XX', coordinates: [-0.13, 51.5] as [number, number] }, +}; + +function fakeResponse(body: unknown, init: { ok?: boolean; status?: number } = {}): Response { + return { + ok: init.ok ?? true, + status: init.status ?? 200, + json: async () => body, + } as unknown as Response; +} + +test.serial('loadPorts fetches, registers, and enables seaRoute by UN/LOCODE', async (t) => { + let requested = ''; + const stub = (async (url: string) => { + requested = String(url); + return fakeResponse(PORTS); + }) as unknown as typeof fetch; + + const ports = await loadPorts('https://cdn.example.com/ports.json', { fetch: stub }); + + t.is(requested, 'https://cdn.example.com/ports.json'); + t.is(Object.keys(ports).length, 2); + // The resolver is now registered: a code string routes end-to-end. + const route = seaRoute('XXNYC', 'xxldn', { units: 'kilometers' }); // case-insensitive + t.is(route.geometry.type, 'LineString'); + t.true(route.properties.length > 0); +}); + +test.serial('loadPorts throws a clear error on a non-OK response', async (t) => { + const stub = (async () => + fakeResponse({}, { ok: false, status: 404 })) as unknown as typeof fetch; + await t.throwsAsync(loadPorts('https://cdn.example.com/missing.json', { fetch: stub }), { + message: /404/, + }); +}); + +test.serial('loadPorts rejects a payload that is not a UN/LOCODE object', async (t) => { + const stub = (async () => fakeResponse(['not', 'an', 'object'])) as unknown as typeof fetch; + await t.throwsAsync(loadPorts('https://cdn.example.com/bad.json', { fetch: stub }), { + message: /UN\/LOCODE|object/, + }); + // Sanity: UnknownPortError is still the error type for genuinely missing codes. + t.is(new UnknownPortError('ZZZZZ', true).name, 'UnknownPortError'); +}); diff --git a/src/ports/data.ts b/src/ports/data.ts new file mode 100644 index 0000000..6af5787 --- /dev/null +++ b/src/ports/data.ts @@ -0,0 +1,14 @@ +/* Auto-generated by scripts/build-ports.cjs. Do not edit by hand. + * + * Curated seaport dataset keyed by UN/LOCODE, derived from + * https://github.com/marchah/sea-ports (MIT), itself based on UN/LOCODE. + * Only { name, country, coordinates: [lon, lat] } is kept, indexed by every + * UN/LOCODE alias. Embedded as a JSON string parsed once at module load. + */ +export type PortRecord = { name: string; country: string; coordinates: [number, number] }; + +const PORTS: Record = JSON.parse( + '{"AEAJM":{"name":"Ajman","country":"United Arab Emirates","coordinates":[55.513643,25.405217]},"AEAUH":{"name":"Abu Dhabi","country":"United Arab Emirates","coordinates":[54.37,24.47]},"AEDXB":{"name":"Dubai","country":"United Arab Emirates","coordinates":[55.27,25.25]},"AEFJR":{"name":"Al Fujayrah","country":"United Arab Emirates","coordinates":[56.33,25.12]},"AEJEA":{"name":"Jebel Ali","country":"United Arab Emirates","coordinates":[55.02729,24.985715]},"AEJED":{"name":"Jebel Dhanna","country":"United Arab Emirates","coordinates":[52.612603,24.191514]},"AEKLF":{"name":"Khor al Fakkan","country":"United Arab Emirates","coordinates":[56.35,25.33]},"AEPRA":{"name":"Port Rashid","country":"United Arab Emirates","coordinates":[55.275651,25.284755]},"AEQIW":{"name":"Umm al Qaiwain","country":"United Arab Emirates","coordinates":[55.55,25.57]},"AERKT":{"name":"Ras al Khaimah","country":"United Arab Emirates","coordinates":[55.95,25.78]},"AERUW":{"name":"Ar Ruways","country":"United Arab Emirates","coordinates":[52.73,24.12]},"AESHJ":{"name":"Sharjah","country":"United Arab Emirates","coordinates":[55.38,25.35]},"AGANU":{"name":"Antigua","country":"Antigua and Barbuda","coordinates":[-61.817521,17.074656]},"AGSJO":{"name":"Saint John\'s","country":"Antigua and Barbuda","coordinates":[-61.85,17.12]},"AIBLP":{"name":"Blowing Point","country":"Anguilla","coordinates":[-63.093518,18.177343]},"AIRBY":{"name":"Road Bay","country":"Anguilla","coordinates":[-63.065326,18.220749]},"ALDRZ":{"name":"Durres","country":"Albania","coordinates":[19.461607,41.332807]},"ANBON":{"name":"Bonaire","country":"Netherlands Antilles","coordinates":[-68.262382,12.20189]},"ANCUR":{"name":"Curacao","country":"Netherlands Antilles","coordinates":[-68.882423,12.122422]},"ANEUX":{"name":"Sint Eustatius (Antilles)","country":"Netherlands","coordinates":[-63.084167,18.034826]},"ANGSB":{"name":"Galisby","country":"Netherlands Antilles","coordinates":[12.168676,-68.448614]},"ANPHI":{"name":"Philipsburg","country":"Netherlands Antilles","coordinates":[-63.047137,18.029584]},"ANSXM":{"name":"St Maarten","country":"Netherlands Antilles","coordinates":[-68.810885,12.224877]},"ANWIL":{"name":"Willemstad","country":"Netherlands Antilles","coordinates":[-68.882423,12.122422]},"AOCAB":{"name":"Cabinda","country":"Angola","coordinates":[12.197583,-5.570574]},"AOLAD":{"name":"Luanda","country":"Angola","coordinates":[13.289437,-8.839988]},"AOLOB":{"name":"Lobito","country":"Angola","coordinates":[13.561045,-12.375729]},"AOMSZ":{"name":"Namibe","country":"Angola","coordinates":[12.157554,-15.197832]},"AOSZA":{"name":"Soyo","country":"Angola","coordinates":[12.352115,-6.141026]},"ARBHI":{"name":"Bahia Blanca","country":"Argentina","coordinates":[-62.28,-38.72]},"ARBUE":{"name":"Buenos Aires","country":"Argentina","coordinates":[-58.67,-34.58]},"ARCMP":{"name":"Campana","country":"Argentina","coordinates":[-58.95,-34.15]},"ARCNQ":{"name":"Corrientes","country":"Argentina","coordinates":[-58.830635,-27.469213]},"ARCRD":{"name":"Comodoro Rivadavia","country":"Argentina","coordinates":[-67.482243,-45.865615]},"ARCVI":{"name":"Caleta Olivia","country":"Argentina","coordinates":[-67.517153,-46.442565]},"ARDDC":{"name":"Delta Dock","country":"Argentina","coordinates":[-34.044757,-59.199761]},"ARLPG":{"name":"La Plata","country":"Argentina","coordinates":[-57.88,-34.85]},"ARMDQ":{"name":"Mar del Plata","country":"Argentina","coordinates":[-57.53,-38.05]},"ARNEC":{"name":"Necochea","country":"Argentina","coordinates":[-58.739609,-38.554497]},"AROYA":{"name":"Goya","country":"Argentina","coordinates":[-59.264324,-29.144224]},"ARPMY":{"name":"Puerto Madryn","country":"Argentina","coordinates":[-65.03,-42.75]},"ARPUD":{"name":"Puerto Deseado","country":"Argentina","coordinates":[-65.9,-47.75]},"ARRGA":{"name":"Rio Grande","country":"Argentina","coordinates":[-67.7,-53.78]},"ARRGL":{"name":"Rio Gallegos","country":"Argentina","coordinates":[-69.22,-51.63]},"ARRIC":{"name":"Rio Cullen","country":"Argentina","coordinates":[-68.352302,-52.895561]},"ARROS":{"name":"Rosario","country":"Argentina","coordinates":[-60.65,-32.95]},"ARSAE":{"name":"San Antonio Este","country":"Argentina","coordinates":[-64.73,-40.8]},"ARSFN":{"name":"Santa Fe","country":"Argentina","coordinates":[-60.68,-31.6]},"ARSLO":{"name":"San Lorenzo","country":"Argentina","coordinates":[-60.723872,-32.807289]},"ARSNS":{"name":"San Nicolas de los Arroyos","country":"Argentina","coordinates":[-60.23,-33.33]},"ARSPD":{"name":"San Pedro","country":"Argentina","coordinates":[-59.17,-33.68]},"ARUSH":{"name":"Ushuaia","country":"Argentina","coordinates":[-68.3,-54.8]},"ARZAE":{"name":"Zarate","country":"Argentina","coordinates":[-59.03,-34.1]},"ASPPG":{"name":"Pago Pago","country":"American Samoa","coordinates":[-170.702036,-14.275632]},"ATLTX":{"name":"Hainfeld","country":"Austria","coordinates":[15.77332,48.033727]},"AUABP":{"name":"Abbot Point","country":"Australia","coordinates":[148.084673,-19.907886]},"AUADL":{"name":"Adelaide","country":"Australia","coordinates":[138.58,-34.92]},"AUARD":{"name":"Ardrossan","country":"Australia","coordinates":[137.917428,-34.42307]},"AUBBY":{"name":"Bremer Bay","country":"Australia","coordinates":[119.379376,-34.392621]},"AUBEL":{"name":"Bell Bay","country":"Australia","coordinates":[146.863556,-41.115415]},"AUBNE":{"name":"Brisbane","country":"Australia","coordinates":[153.02,-27.47]},"AUBOO":{"name":"Booby Island","country":"Australia","coordinates":[141.92,-10.6]},"AUBWT":{"name":"Burnie","country":"Australia","coordinates":[145.906851,-41.052465]},"AUCHT":{"name":"Chittering","country":"Australia","coordinates":[116.101011,-31.470055]},"AUCNS":{"name":"Cairns","country":"Australia","coordinates":[145.77086,-16.920334]},"AUDPO":{"name":"Devonport","country":"Australia","coordinates":[146.360953,-41.178353]},"AUDRW":{"name":"Darwin","country":"Australia","coordinates":[130.83,-12.45]},"AUFRE":{"name":"Fremantle","country":"Australia","coordinates":[115.74718,-32.05604]},"AUGLT":{"name":"Gladstone","country":"Australia","coordinates":[151.2598,-23.848708]},"AUGOV":{"name":"Gove","country":"Australia","coordinates":[130.871788,-12.378072]},"AUHBA":{"name":"Hobart","country":"Australia","coordinates":[147.3,-42.87]},"AUHPT":{"name":"Hay Point","country":"Australia","coordinates":[149.292163,-21.305578]},"AULST":{"name":"Launceston","country":"Australia","coordinates":[147.112468,-41.426181]},"AUMEL":{"name":"Melbourne","country":"Australia","coordinates":[144.97,-37.82]},"AUNLK":{"name":"New Norfolk","country":"Australia","coordinates":[147.06365,-42.780683]},"AUNTL":{"name":"Newcastle","country":"Australia","coordinates":[151.778921,-32.926689]},"AUPAE":{"name":"Port Adelaide","country":"Australia","coordinates":[138.507362,-34.847745]},"AUPHE":{"name":"Port Hedland","country":"Australia","coordinates":[118.575258,-20.311627]},"AUPKL":{"name":"Port Kembla","country":"Australia","coordinates":[150.900495,-34.480073]},"AUSYD":{"name":"Sydney","country":"Australia","coordinates":[151.2,-33.85]},"AUTSV":{"name":"Townsville","country":"Australia","coordinates":[146.817871,-19.257627]},"AUWEI":{"name":"Weipa","country":"Australia","coordinates":[141.838406,-12.659147]},"AWAUA":{"name":"Aruba","country":"Aruba","coordinates":[-69.97,12.5]},"AWORJ":{"name":"Oranjestad","country":"Aruba","coordinates":[-70.008631,12.509204]},"AZBAK":{"name":"Baku","country":"Azerbaijan","coordinates":[49.867092,40.409262]},"BBBGI":{"name":"Bridgetown","country":"Barbados","coordinates":[-59.598809,13.113222]},"BDCGP":{"name":"Chittagong","country":"Bangladesh","coordinates":[91.812332,22.347537]},"BDCHL":{"name":"Chalna","country":"Bangladesh","coordinates":[89.513969,22.603697]},"BDDAC":{"name":"Dhaka","country":"Bangladesh","coordinates":[90.412518,23.810332]},"BDMGL":{"name":"Mongla","country":"Bangladesh","coordinates":[89.601617,22.49422]},"BEANR":{"name":"Antwerpen","country":"Belgium","coordinates":[4.42,51.22]},"BEBSU":{"name":"Bossuit","country":"Belgium","coordinates":[3.407288,50.748117]},"BEDEL":{"name":"Doel","country":"Belgium","coordinates":[4.264975,51.310598]},"BEGNE":{"name":"Gent (Ghent)","country":"Belgium","coordinates":[3.72,51.05]},"BEOST":{"name":"Oostende (Ostend)","country":"Belgium","coordinates":[2.92,51.2]},"BEZEE":{"name":"Zeebrugge","country":"Belgium","coordinates":[3.2,51.33]},"BFOUA":{"name":"Ouagadougou","country":"Burkina Faso","coordinates":[-1.51966,12.371428]},"BGBOJ":{"name":"Burgas","country":"Bulgaria","coordinates":[27.462636,42.504793]},"BGVAR":{"name":"Varna","country":"Bulgaria","coordinates":[27.914733,43.21405]},"BHBAH":{"name":"Bahrain","country":"Bahrain","coordinates":[50.63,26.27]},"BHKBS":{"name":"Khalifa Bin Salman Port","country":"Bahrain","coordinates":[50.62,26.2]},"BHMAN":{"name":"Manama","country":"Bahrain","coordinates":[50.58605,26.228516]},"BHSIT":{"name":"Sitrah","country":"Bahrain","coordinates":[50.62,26.13]},"BJCOO":{"name":"Cotonou","country":"Benin","coordinates":[2.391236,6.370293]},"BMBDA":{"name":"Hamilton","country":"Bermuda","coordinates":[-64.781375,32.294816]},"BMSOU":{"name":"Southampton","country":"Bermuda","coordinates":[-1.404351,50.9097]},"BNBWN":{"name":"Bandar Seri Begawan","country":"Brunei Darussalam","coordinates":[114.93,4.88]},"BNKUB":{"name":"Kuala Belait","country":"Brunei Darussalam","coordinates":[114.221217,4.583199]},"BNMUA":{"name":"Muara","country":"Brunei Darussalam","coordinates":[115.073288,5.033321]},"BOPBU":{"name":"Puerto Busch","country":"Bolivia, Plurinational State of","coordinates":[-57.966667,-20.033333]},"BOSRZ":{"name":"Santa Cruz","country":"Bolivia, Plurinational State of","coordinates":[-63.18,-17.78]},"BRARB":{"name":"Aratu","country":"Brazil","coordinates":[-38.456075,-12.785585]},"BRARE":{"name":"Areia Branca","country":"Brazil","coordinates":[-37.17,-4.98]},"BRBEL":{"name":"Belem","country":"Brazil","coordinates":[-48.5,-1.45]},"BRCDO":{"name":"Cabedelo","country":"Brazil","coordinates":[-34.828722,-6.987721]},"BRFNO":{"name":"Forno","country":"Brazil","coordinates":[-41.88077,-22.758365]},"BRFOR":{"name":"Fortaleza","country":"Brazil","coordinates":[-38.52667,-3.731862]},"BRIBB":{"name":"Imbituba","country":"Brazil","coordinates":[-48.669129,-28.227627]},"BRIGI":{"name":"Itaguai","country":"Brazil","coordinates":[-43.77,-22.87]},"BRIOA":{"name":"Itapoa","country":"Brazil","coordinates":[-48.6,-26.12]},"BRIOS":{"name":"Ilheus","country":"Brazil","coordinates":[-39.04638,-14.793505]},"BRIRO":{"name":"Itabirito","country":"Brazil","coordinates":[-43.803057,-20.253062]},"BRITJ":{"name":"Itajai","country":"Brazil","coordinates":[-48.670951,-26.910596]},"BRITQ":{"name":"Itaqui","country":"Brazil","coordinates":[-56.548112,-29.129401]},"BRITZ":{"name":"Itapemirim","country":"Brazil","coordinates":[-40.831058,-21.010006]},"BRMAO":{"name":"Manaus","country":"Brazil","coordinates":[-60.021731,-3.119027]},"BRMCZ":{"name":"Maceio","country":"Brazil","coordinates":[-35.72,-9.67]},"BRNAT":{"name":"Natal","country":"Brazil","coordinates":[-35.200916,-5.779257]},"BRNVT":{"name":"Navegantes","country":"Brazil","coordinates":[-48.65,-26.9]},"BRPCL":{"name":"Portocel","country":"Brazil","coordinates":[-40.308863,-19.183423]},"BRPEC":{"name":"Pecem","country":"Brazil","coordinates":[-38.87,-3.52]},"BRPNG":{"name":"Paranagua","country":"Brazil","coordinates":[-48.5,-25.52]},"BRPNN":{"name":"Paranã","country":"Brazil","coordinates":[-47.875933,-12.616281]},"BRPOA":{"name":"Porto Alegre","country":"Brazil","coordinates":[-51.217658,-30.034647]},"BRREC":{"name":"Recife","country":"Brazil","coordinates":[-34.882897,-8.057838]},"BRRIG":{"name":"Rio Grande","country":"Brazil","coordinates":[-52.10758,-32.035378]},"BRRIO":{"name":"Rio de Janeiro","country":"Brazil","coordinates":[-43.172896,-22.906847]},"BRSAL":{"name":"Salto Grande","country":"Brazil","coordinates":[-49.983606,-22.889868]},"BRSFS":{"name":"Sao Francisco do Sul","country":"Brazil","coordinates":[-48.634845,-26.258414]},"BRSPB":{"name":"Porto de Itaguai","country":"Brazil","coordinates":[-43.82,-22.92]},"BRSSA":{"name":"Salvador","country":"Brazil","coordinates":[-38.502304,-12.97304]},"BRSSO":{"name":"Sao Sebastiao","country":"Brazil","coordinates":[-45.401653,-23.806347]},"BRSSZ":{"name":"Santos","country":"Brazil","coordinates":[-46.33,-23.95]},"BRSTM":{"name":"Santarem","country":"Brazil","coordinates":[-54.700923,-2.450629]},"BRSUA":{"name":"Suape","country":"Brazil","coordinates":[-34.957553,-8.353662]},"BRVDC":{"name":"Vitoria da Conquista","country":"Brazil","coordinates":[-40.85,-14.85]},"BRVIC":{"name":"Vila do Conde","country":"Brazil","coordinates":[-34.9,-7.27]},"BRVIX":{"name":"Vitoria","country":"Brazil","coordinates":[-40.295777,-20.297618]},"BRVLC":{"name":"Vila do Conde","country":"Brazil","coordinates":[-48.75,-1.53]},"BSFPO":{"name":"Freeport, Grand Bahama","country":"Bahamas","coordinates":[-78.645547,26.541609]},"BSNAS":{"name":"Nassau","country":"Bahamas","coordinates":[-77.35,25.08]},"BYMSQ":{"name":"Minsk","country":"Belarus","coordinates":[27.561524,53.90454]},"BZBZE":{"name":"Belize City","country":"Belize","coordinates":[-88.196213,17.504566]},"CABAT":{"name":"Bathurst","country":"Canada","coordinates":[-65.651336,47.618351]},"CABCK":{"name":"Brockville","country":"Canada","coordinates":[-75.684286,44.589524]},"CABDU":{"name":"Baie d\'Urfe","country":"Canada","coordinates":[-73.92,45.4]},"CABEC":{"name":"Becancour","country":"Canada","coordinates":[-72.43,46.33]},"CABJO":{"name":"Baie-Johan-Beetz","country":"Canada","coordinates":[-62.83,50.3]},"CABRL":{"name":"Burlington","country":"Canada","coordinates":[-79.799032,43.32552]},"CACAL":{"name":"Calgary","country":"Canada","coordinates":[-114.07,51.03]},"CACBK":{"name":"Corner Brook","country":"Canada","coordinates":[-57.93,48.95]},"CACCB":{"name":"Cowichan Bay","country":"Canada","coordinates":[-123.62155,48.737309]},"CACOL":{"name":"Collingwood","country":"Canada","coordinates":[-80.216905,44.500769]},"CACRO":{"name":"Crofton","country":"Canada","coordinates":[-123.645798,48.863844]},"CACWL":{"name":"Cornwall","country":"Canada","coordinates":[-74.730345,45.021276]},"CADOR":{"name":"Dorval","country":"Canada","coordinates":[-73.750048,45.450321]},"CADRU":{"name":"Drummondville","country":"Canada","coordinates":[-72.484282,45.880291]},"CAEDM":{"name":"Edmonton","country":"Canada","coordinates":[-113.490927,53.544389]},"CAHAL":{"name":"Halifax","country":"Canada","coordinates":[-63.57532,44.648863]},"CAHAM":{"name":"Hamilton","country":"Canada","coordinates":[-79.866091,43.250021]},"CAHMC":{"name":"Harmac","country":"Canada","coordinates":[-123.85,49.13]},"CAKVL":{"name":"Kingsville","country":"Canada","coordinates":[-82.738981,42.036284]},"CAMAG":{"name":"Magog","country":"Canada","coordinates":[-72.147989,45.266486]},"CAMNT":{"name":"Moncton","country":"Canada","coordinates":[-64.778231,46.087817]},"CAMTR":{"name":"Montreal","country":"Canada","coordinates":[-73.567256,45.501689]},"CANIA":{"name":"Niagara Falls","country":"Canada","coordinates":[-79.084944,43.089558]},"CANNO":{"name":"Nanaimo","country":"Canada","coordinates":[-123.940065,49.165884]},"CANOR":{"name":"North Bay","country":"Canada","coordinates":[-79.45,46.3]},"CANWE":{"name":"New Westminster","country":"Canada","coordinates":[-122.910956,49.205718]},"CAOAK":{"name":"Oakville","country":"Canada","coordinates":[-79.687666,43.467517]},"CAODE":{"name":"Odessa","country":"Canada","coordinates":[-76.718292,44.277038]},"CAOSH":{"name":"Oshawa","country":"Canada","coordinates":[-78.865791,43.897093]},"CAPCR":{"name":"Port Credit","country":"Canada","coordinates":[-79.585628,43.551615]},"CAPDV":{"name":"Port Dover","country":"Canada","coordinates":[-80.203278,42.786523]},"CAPML":{"name":"Port Mellon","country":"Canada","coordinates":[-123.488059,49.521748]},"CAPOI":{"name":"Pointe-Claire","country":"Canada","coordinates":[-73.73,45.43]},"CAPRR":{"name":"Prince Rupert","country":"Canada","coordinates":[-130.320819,54.315037]},"CAQUE":{"name":"Quebec","country":"Canada","coordinates":[-71.242796,46.803283]},"CASAK":{"name":"Saskatoon","country":"Canada","coordinates":[-106.670046,52.133214]},"CASCA":{"name":"Saint-Catharines","country":"Canada","coordinates":[-79.23,43.17]},"CASJB":{"name":"Saint-John","country":"Canada","coordinates":[-66.07,45.27]},"CASJF":{"name":"Saint-John\'s","country":"Canada","coordinates":[-52.73,47.57]},"CASNI":{"name":"Sarnia","country":"Canada","coordinates":[-82.40659,42.974536]},"CASOR":{"name":"Sorel","country":"Canada","coordinates":[-73.112302,46.042825]},"CASSM":{"name":"Sault-Sainte-Marie","country":"Canada","coordinates":[-84.34609,46.521858]},"CASTC":{"name":"Stoney Creek","country":"Canada","coordinates":[-79.719559,43.227218]},"CASUM":{"name":"Summerside","country":"Canada","coordinates":[-63.790233,46.393378]},"CATEX":{"name":"Texada Island","country":"Canada","coordinates":[-124.412195,49.659663]},"CATHU":{"name":"Thunder Bay","country":"Canada","coordinates":[-89.247682,48.380895]},"CATOR":{"name":"Toronto","country":"Canada","coordinates":[-79.383184,43.653226]},"CATRR":{"name":"Trois-Rivieres (Three Rivers)","country":"Canada","coordinates":[-72.547526,46.359242]},"CATTC":{"name":"Trenton","country":"Canada","coordinates":[-77.576313,44.101126]},"CAVAN":{"name":"Vancouver","country":"Canada","coordinates":[-123.120737,49.282729]},"CAVLF":{"name":"Valleyfield","country":"Canada","coordinates":[-74.13,45.25]},"CAWIN":{"name":"Winona","country":"Canada","coordinates":[-79.650916,43.20796]},"CAWNP":{"name":"Winnipeg","country":"Canada","coordinates":[-97.137494,49.899754]},"CDBOA":{"name":"Boma","country":"Congo, The Democratic Republic of the","coordinates":[-5.834722,13.022875]},"CDMAT":{"name":"Matadi","country":"Congo, The Democratic Republic of the","coordinates":[-5.823794,13.450986]},"CGPNR":{"name":"Pointe Noire","country":"Congo","coordinates":[11.866362,-4.769162]},"CIABJ":{"name":"Abidjan","country":"Côte d\'Ivoire","coordinates":[-4.008256,5.359952]},"CISPY":{"name":"San-Pedro","country":"Côte d\'Ivoire","coordinates":[-6.62,4.73]},"CLANF":{"name":"Antofagasta","country":"Chile","coordinates":[-70.38,-23.63]},"CLARI":{"name":"Arica","country":"Chile","coordinates":[-70.32,-18.48]},"CLBAG":{"name":"Bahia San Gregorio","country":"Chile","coordinates":[-70.13,-52.62]},"CLBAR":{"name":"Barquito","country":"Chile","coordinates":[-70.63,-26.35]},"CLCAP":{"name":"San Vicente","country":"Chile","coordinates":[-4.397058,43.381307]},"CLCBC":{"name":"Calbuco","country":"Chile","coordinates":[-73.13,-41.75]},"CLCCL":{"name":"Caleta Coloso","country":"Chile","coordinates":[-70.293718,-18.435035]},"CLCHB":{"name":"Chacabuco","country":"Chile","coordinates":[-72.82,-45.48]},"CLCLD":{"name":"Caldera","country":"Chile","coordinates":[-70.8,-27.03]},"CLCNL":{"name":"Coronel","country":"Chile","coordinates":[-73.15,-37.02]},"CLCNR":{"name":"Chanaral","country":"Chile","coordinates":[-70.62,-26.35]},"CLCNX":{"name":"Cabo Negro","country":"Chile","coordinates":[-70.85,-52.97]},"CLCQQ":{"name":"Coquimbo","country":"Chile","coordinates":[-71.33,-29.97]},"CLCRR":{"name":"Corral","country":"Chile","coordinates":[-73.43,-39.87]},"CLDCH":{"name":"Dalcahue","country":"Chile","coordinates":[-73.67,-42.38]},"CLGYC":{"name":"Guayacan","country":"Chile","coordinates":[-71.37,-29.98]},"CLHSO":{"name":"Huasco","country":"Chile","coordinates":[-71.22,-28.45]},"CLIPC":{"name":"Isla de Pascua","country":"Chile","coordinates":[-109.42,-27.15]},"CLIQQ":{"name":"Iquique","country":"Chile","coordinates":[-70.13,-20.22]},"CLJRL":{"name":"Jureles","country":"Chile","coordinates":[-36.972144,-73.226522]},"CLLOS":{"name":"Los Vilos","country":"Chile","coordinates":[-71.5,-31.9]},"CLLQN":{"name":"Lirquen","country":"Chile","coordinates":[-72.98,-36.7]},"CLLVS":{"name":"Las Ventanas","country":"Chile","coordinates":[-71.65,-33.27]},"CLMCH":{"name":"Michilla","country":"Chile","coordinates":[-70.27769,-22.718314]},"CLMJS":{"name":"Mejillones","country":"Chile","coordinates":[-70.45,-23.1]},"CLPAG":{"name":"Puerto Angamos","country":"Chile","coordinates":[-70.45,-23.08]},"CLPCH":{"name":"Puerto Chacabuco","country":"Chile","coordinates":[-72.83,-45.48]},"CLPEO":{"name":"Penco","country":"Chile","coordinates":[-72.98,-36.72]},"CLPGO":{"name":"Punta Chungo","country":"Chile","coordinates":[-31.885551,-71.503861]},"CLPMC":{"name":"Puerto Montt","country":"Chile","coordinates":[-72.95,-41.47]},"CLPNT":{"name":"Puerto Natales","country":"Chile","coordinates":[-72.5,-51.72]},"CLPPC":{"name":"Punta Patache","country":"Chile","coordinates":[-70.22,-20.82]},"CLPPD":{"name":"Punta Padrones","country":"Chile","coordinates":[-27.052295,-70.841904]},"CLPTE":{"name":"Quellon (Puerto Quellon)","country":"Chile","coordinates":[-73.62,-43.1]},"CLPTI":{"name":"Patillos","country":"Chile","coordinates":[-70.18,-20.75]},"CLPUQ":{"name":"Punta Arenas","country":"Chile","coordinates":[-70.93,-53.13]},"CLQMC":{"name":"Quemchi","country":"Chile","coordinates":[-73.48,-42.13]},"CLQTV":{"name":"Quintero","country":"Chile","coordinates":[-71.53,-32.77]},"CLSAI":{"name":"San Antonio","country":"Chile","coordinates":[-71.6,-33.6]},"CLSVE":{"name":"San Vicente","country":"Chile","coordinates":[-73.13,-36.7]},"CLTAL":{"name":"Talcahuano","country":"Chile","coordinates":[-73.12,-36.73]},"CLTHO":{"name":"Talcahuano","country":"Chile","coordinates":[-73.116981,-36.724783]},"CLTOM":{"name":"Tome","country":"Chile","coordinates":[-72.95,-36.6]},"CLTOQ":{"name":"Tocopilla","country":"Chile","coordinates":[-70.18,-22.08]},"CLTTC":{"name":"Taltal","country":"Chile","coordinates":[-70.47,-25.4]},"CLVAP":{"name":"Valparaiso","country":"Chile","coordinates":[-71.63,-33.03]},"CLWCA":{"name":"Castro","country":"Chile","coordinates":[-73.77,-42.47]},"CLWCH":{"name":"Chaiten","country":"Chile","coordinates":[-72.7,-42.92]},"CLWPA":{"name":"Puerto Aisen","country":"Chile","coordinates":[-72.7,-45.38]},"CLWPU":{"name":"Puerto Williams","country":"Chile","coordinates":[-67.62,-54.92]},"CLZUD":{"name":"Ancud","country":"Chile","coordinates":[-73.83,-41.85]},"CMDLA":{"name":"Douala","country":"Cameroon","coordinates":[9.767869,4.051056]},"CNBAY":{"name":"Bayuquan","country":"China","coordinates":[122.107814,40.230636]},"CNBHY":{"name":"Beihai Fucheng Apt","country":"China","coordinates":[108.327546,22.815478]},"CNBJO":{"name":"Beijiao","country":"China","coordinates":[119.92,26.35]},"CNCAN":{"name":"Guangzhou","country":"China","coordinates":[113.264385,23.12911]},"CNCGU":{"name":"Changshu","country":"China","coordinates":[120.752503,31.653686]},"CNCKG":{"name":"Chongqing","country":"China","coordinates":[106.551557,29.56301]},"CNCOZ":{"name":"Chaozhou Port","country":"China","coordinates":[116.63,23.67]},"CNCSX":{"name":"Changsha","country":"China","coordinates":[112.938814,28.228209]},"CNCTU":{"name":"Chengdu","country":"China","coordinates":[104.066541,30.572269]},"CNCWN":{"name":"Chiwan Port","country":"China","coordinates":[113.88,22.48]},"CNDAA":{"name":"Dagang","country":"China","coordinates":[120.103473,33.189192]},"CNDAL":{"name":"Dalian","country":"China","coordinates":[121.614682,38.914003]},"CNDGG":{"name":"Dongguan Port","country":"China","coordinates":[113.751765,23.020536]},"CNDLC":{"name":"Dalian","country":"China","coordinates":[121.614682,38.914003]},"CNDOU":{"name":"Doumen Port","country":"China","coordinates":[113.28,22.2]},"CNFAN":{"name":"Fangcheng Port","country":"China","coordinates":[108.35,21.77]},"CNFNG":{"name":"Fuyong","country":"China","coordinates":[113.78,22.65]},"CNFOS":{"name":"Foshan","country":"China","coordinates":[113.1,23.03]},"CNFUG":{"name":"Fuqing","country":"China","coordinates":[119.383811,25.720381]},"CNGGY":{"name":"Gongyi","country":"China","coordinates":[113.218364,23.403308]},"CNGOG":{"name":"Gongming","country":"China","coordinates":[113.87,22.78]},"CNGOM":{"name":"Gaoming Port","country":"China","coordinates":[112.83,22.88]},"CNGUG":{"name":"Guigang","country":"China","coordinates":[109.6,23.1]},"CNHAK":{"name":"Haikou","country":"China","coordinates":[110.198293,20.044002]},"CNHDU":{"name":"Huadu","country":"China","coordinates":[113.22,23.4]},"CNHEB":{"name":"Hebei","country":"China","coordinates":[115.27,39.88]},"CNHGH":{"name":"Hangzhou","country":"China","coordinates":[120.15507,30.274085]},"CNHME":{"name":"Haimen","country":"China","coordinates":[121.453079,32.104157]},"CNHMN":{"name":"Humen Port","country":"China","coordinates":[113.67,22.83]},"CNHSN":{"name":"Heshan Port","country":"China","coordinates":[112.95,22.77]},"CNHUA":{"name":"Huangpu Port","country":"China","coordinates":[113.459714,23.106899]},"CNHUD":{"name":"Huludao Port","country":"China","coordinates":[120.85,40.75]},"CNHUI":{"name":"Huizhou Port","country":"China","coordinates":[114.37,23.08]},"CNJGZ":{"name":"Jingzhou","country":"China","coordinates":[112.18,30.35]},"CNJIA":{"name":"Jiangyin Port","country":"China","coordinates":[120.28,31.9]},"CNJIN":{"name":"Jian","country":"China","coordinates":[114.97,27.12]},"CNJIU":{"name":"Jiujiang Port","country":"China","coordinates":[115.97,29.72]},"CNJMN":{"name":"Jiangmen Port","country":"China","coordinates":[113.07,22.58]},"CNJNZ":{"name":"Jinzhou Port","country":"China","coordinates":[121.15,41.12]},"CNJXN":{"name":"Jiao Xin","country":"China","coordinates":[113.313695,22.70248]},"CNJZU":{"name":"Jiuzhou Port","country":"China","coordinates":[116.122239,24.288615]},"CNKHN":{"name":"Nanchang","country":"China","coordinates":[115.858198,28.682892]},"CNKMG":{"name":"Kunming","country":"China","coordinates":[102.832892,24.880095]},"CNKPN":{"name":"Kaiping","country":"China","coordinates":[118.3,39.7]},"CNLAN":{"name":"Lanshi Port","country":"China","coordinates":[113.981592,22.97016]},"CNLGH":{"name":"Longhua","country":"China","coordinates":[114.020921,22.63964]},"CNLIH":{"name":"Lianhuashan Port","country":"China","coordinates":[115.27,23.02]},"CNLKU":{"name":"Longkou Port","country":"China","coordinates":[120.35,37.65]},"CNLSI":{"name":"Lanshi","country":"China","coordinates":[113.6,26.15]},"CNLYG":{"name":"Lianyungang","country":"China","coordinates":[119.43,34.72]},"CNLZH":{"name":"Liuzhou Port","country":"China","coordinates":[109.4,23.32]},"CNMAW":{"name":"Mawei Port","country":"China","coordinates":[119.45,25.98]},"CNMMI":{"name":"Maoming","country":"China","coordinates":[110.925456,21.662999]},"CNMWN":{"name":"Mawan Port","country":"China","coordinates":[113.88,22.45]},"CNMYG":{"name":"Mianyang","country":"China","coordinates":[104.7,31.5]},"CNNAH":{"name":"Nanhai Port","country":"China","coordinates":[113.15,23.02]},"CNNBO":{"name":"Ningbo","country":"China","coordinates":[121.54399,29.868336]},"CNNGB":{"name":"Ningbo","country":"China","coordinates":[121.54399,29.868336]},"CNNGG":{"name":"Nangang","country":"China","coordinates":[113.200452,23.037271]},"CNNKG":{"name":"Nanjing","country":"China","coordinates":[118.796877,32.060255]},"CNNNG":{"name":"Nanning","country":"China","coordinates":[108.366543,22.817002]},"CNNSA":{"name":"Nansha Port","country":"China","coordinates":[113.58,22.75]},"CNNTG":{"name":"Nantong Port","country":"China","coordinates":[120.85,32.02]},"CNPNY":{"name":"Shiqiao","country":"China","coordinates":[113.37,22.95]},"CNPUT":{"name":"Putian","country":"China","coordinates":[119.02,24.43]},"CNQAW":{"name":"Qianwan","country":"China","coordinates":[120.22,36.05]},"CNQIN":{"name":"Qingdao","country":"China","coordinates":[120.382612,36.067117]},"CNQYN":{"name":"Qingyuan","country":"China","coordinates":[113.056031,23.681764]},"CNQZJ":{"name":"Quanzhou Port","country":"China","coordinates":[118.6,24.93]},"CNROQ":{"name":"Rongqi Port","country":"China","coordinates":[113.27,22.78]},"CNRZH":{"name":"Rizhao Port","country":"China","coordinates":[119.53,35.38]},"CNSBU":{"name":"Sanbu Port","country":"China","coordinates":[112.716189,22.36821]},"CNSDG":{"name":"Shuidong Port","country":"China","coordinates":[111,21.52]},"CNSGH":{"name":"Shanghai","country":"China","coordinates":[121.473701,31.230416]},"CNSHA":{"name":"Shanghai","country":"China","coordinates":[121.473701,31.230416]},"CNSHK":{"name":"Shekou Port","country":"China","coordinates":[113.92,22.48]},"CNSHP":{"name":"Qinhuangdao Port","country":"China","coordinates":[119.58,39.92]},"CNSIN":{"name":"Shatian","country":"China","coordinates":[113.617788,22.919003]},"CNSJQ":{"name":"Sanshui","country":"China","coordinates":[112.87,23.17]},"CNSNW":{"name":"Shenwan","country":"China","coordinates":[113.364085,22.281534]},"CNSSH":{"name":"Sanshan (Nanhai)","country":"China","coordinates":[113.241327,23.035997]},"CNSUD":{"name":"Shunde Port","country":"China","coordinates":[113.25,22.83]},"CNSWA":{"name":"Jieyang Chaoshan Apt","country":"China","coordinates":[113.266531,23.132191]},"CNSWE":{"name":"Shanwei Port","country":"China","coordinates":[115.35,22.78]},"CNSZX":{"name":"Shenzhen Baoan","country":"China","coordinates":[113.88402,22.555259]},"CNTAG":{"name":"Taicang","country":"China","coordinates":[121.13055,31.457735]},"CNTAO":{"name":"Qingdao","country":"China","coordinates":[120.382612,36.067117]},"CNTAP":{"name":"Taiping","country":"China","coordinates":[110.17,21.03]},"CNTHG":{"name":"Taicheng (Taishan)","country":"China","coordinates":[112.78,22.25]},"CNTSN":{"name":"Tianjin","country":"China","coordinates":[117.200983,39.084158]},"CNTXG":{"name":"Tianjin New Port","country":"China","coordinates":[117.7,38.98]},"CNTZO":{"name":"Taizhou","country":"China","coordinates":[119.9,32.48]},"CNWAZ":{"name":"Wanzai","country":"China","coordinates":[113.52,22.18]},"CNWEI":{"name":"Weihai","country":"China","coordinates":[122.12,37.52]},"CNWHI":{"name":"Wuhu Port","country":"China","coordinates":[118.37,31.32]},"CNWNZ":{"name":"Wenzhou","country":"China","coordinates":[120.699367,27.994267]},"CNWUH":{"name":"Wuhan","country":"China","coordinates":[114.305393,30.593099]},"CNXAB":{"name":"Xinfeng","country":"China","coordinates":[114.206867,24.05976]},"CNXAM":{"name":"Xiamen","country":"China","coordinates":[118.089425,24.479834]},"CNXGA":{"name":"Xingang","country":"China","coordinates":[117.684933,39.006512]},"CNXGG":{"name":"Xingang","country":"China","coordinates":[117.684933,39.006512]},"CNXIL":{"name":"Xiaolan","country":"China","coordinates":[113.2514,22.671394]},"CNXIN":{"name":"Xinhui Port","country":"China","coordinates":[113.02,22.53]},"CNXIT":{"name":"Xintang Port","country":"China","coordinates":[113.62,23.12]},"CNXMN":{"name":"Xiamen","country":"China","coordinates":[118.089425,24.479834]},"CNYAN":{"name":"Yangshan","country":"China","coordinates":[121.869339,29.476705]},"CNYIC":{"name":"Yichang","country":"China","coordinates":[111.28,30.7]},"CNYIK":{"name":"Yingkou Port","country":"China","coordinates":[122.22,40.65]},"CNYJI":{"name":"Yangjiang Port","country":"China","coordinates":[111.97,21.83]},"CNYNF":{"name":"Yunfu","country":"China","coordinates":[112.044491,22.915094]},"CNYNT":{"name":"Yantai","country":"China","coordinates":[121.447935,37.463822]},"CNYPG":{"name":"Yangpu Port","country":"China","coordinates":[109.2,19.7]},"CNYQS":{"name":"Beijiao","country":"China","coordinates":[113.207187,22.928305]},"CNYTG":{"name":"Yantai","country":"China","coordinates":[121.447935,37.463822]},"CNYTN":{"name":"Yantian Port","country":"China","coordinates":[119.87,26.85]},"CNYZH":{"name":"Yangzhou Port","country":"China","coordinates":[119.43,32.38]},"CNZAP":{"name":"Zhapu (Zhejiang)","country":"China","coordinates":[121.095594,30.603546]},"CNZHA":{"name":"Zhanjiang Potou Apt","country":"China","coordinates":[113.266531,23.132191]},"CNZHE":{"name":"Zhenjiang Port","country":"China","coordinates":[119.45,32.18]},"CNZJG":{"name":"Zhangjiagang","country":"China","coordinates":[120.53,31.87]},"CNZOS":{"name":"Zhoushan Port","country":"China","coordinates":[122.207216,29.985295]},"CNZQG":{"name":"Zhaoqing Port","country":"China","coordinates":[112.45,23.05]},"CNZSN":{"name":"Zhongshan Port","country":"China","coordinates":[113.37,22.52]},"CNZUH":{"name":"Zhuhai Port","country":"China","coordinates":[113.57,22.28]},"COBAQ":{"name":"Barranquilla","country":"Colombia","coordinates":[-74.806981,11.004107]},"COBUN":{"name":"Buenaventura","country":"Colombia","coordinates":[-77.019721,3.883047]},"COCTG":{"name":"Cartagena","country":"Colombia","coordinates":[-75.479426,10.391049]},"COPBO":{"name":"Puerto Bolivar","country":"Colombia","coordinates":[-74.73,10.68]},"COSMR":{"name":"Santa Marta","country":"Colombia","coordinates":[-74.211023,11.240355]},"COTCO":{"name":"Tumaco","country":"Colombia","coordinates":[-78.802094,1.80009]},"CRCAL":{"name":"Caldera","country":"Costa Rica","coordinates":[-84.72,9.95]},"CRGLF":{"name":"Golfito","country":"Costa Rica","coordinates":[-83.15,8.65]},"CRLIO":{"name":"Puerto Limon","country":"Costa Rica","coordinates":[-83.03,10]},"CRMOB":{"name":"Moin","country":"Costa Rica","coordinates":[-83.08,10]},"CRPAS":{"name":"Puntarenas","country":"Costa Rica","coordinates":[-84.83,9.97]},"CRPCA":{"name":"Puerto Caldera","country":"Costa Rica","coordinates":[-84.086942,9.932853]},"CRSJO":{"name":"San Jose","country":"Costa Rica","coordinates":[-84.08,9.93]},"CUHAV":{"name":"La Habana","country":"Cuba","coordinates":[-82.35,23.12]},"CUMAR":{"name":"Mariel","country":"Cuba","coordinates":[-82.75,23]},"CUQMA":{"name":"Matanzas","country":"Cuba","coordinates":[-81.339941,22.576712]},"CUSCU":{"name":"Santiago de Cuba","country":"Cuba","coordinates":[-75.830154,20.01693]},"CVMIN":{"name":"Mindelo","country":"Cape Verde","coordinates":[-25,16.88]},"CVPAL":{"name":"Palmeira","country":"Cape Verde","coordinates":[-22.97886,16.75827]},"CVRAI":{"name":"Praia","country":"Cape Verde","coordinates":[-23.513327,14.93305]},"CYLMS":{"name":"Limassol","country":"Cyprus","coordinates":[33.05,34.68]},"CYNIC":{"name":"Nicosia","country":"Cyprus","coordinates":[33.37,35.17]},"CYPFO":{"name":"Paphos","country":"Cyprus","coordinates":[32.42,34.77]},"DEBEZ":{"name":"Bahlingen","country":"Germany","coordinates":[7.742534,48.121401]},"DEBIN":{"name":"Bingen am Rhein","country":"Germany","coordinates":[7.88,49.97]},"DEBKE":{"name":"Brake","country":"Germany","coordinates":[8.48,53.33]},"DEBOY":{"name":"Bad Oeynhausen","country":"Germany","coordinates":[8.800971,52.208462]},"DEBRA":{"name":"Bramel","country":"Germany","coordinates":[8.7,53.55]},"DEBRB":{"name":"Brunsbuttel","country":"Germany","coordinates":[9.133885,53.89889]},"DEBRE":{"name":"Bremen","country":"Germany","coordinates":[8.801694,53.079296]},"DEBRV":{"name":"Bremerhaven","country":"Germany","coordinates":[8.580942,53.539585]},"DEBSK":{"name":"Burgstaaken/Fehmarn","country":"Germany","coordinates":[11.19255,54.419198]},"DECGN":{"name":"Koln","country":"Germany","coordinates":[6.93,50.95]},"DECUX":{"name":"Cuxhaven","country":"Germany","coordinates":[8.7,53.88]},"DEDUS":{"name":"Dusseldorf","country":"Germany","coordinates":[6.78,51.23]},"DEELS":{"name":"Elsfleth","country":"Germany","coordinates":[8.47,53.23]},"DEEME":{"name":"Emden","country":"Germany","coordinates":[7.22,53.37]},"DEFLF":{"name":"Flensburg","country":"Germany","coordinates":[9.446996,54.793743]},"DEHAM":{"name":"Hamburg","country":"Germany","coordinates":[9.993682,53.551085]},"DEHBU":{"name":"Harburg","country":"Germany","coordinates":[9.983418,53.460779]},"DEHMM":{"name":"Hamm","country":"Germany","coordinates":[7.8,51.68]},"DEKEL":{"name":"Kiel","country":"Germany","coordinates":[10.13,54.33]},"DEKGU":{"name":"Kriegenbrunn","country":"Germany","coordinates":[10.955692,49.556396]},"DEKHO":{"name":"Kochendorf","country":"Germany","coordinates":[9.211615,49.217818]},"DELBC":{"name":"Lubeck","country":"Germany","coordinates":[10.686559,53.865467]},"DELEE":{"name":"Leer","country":"Germany","coordinates":[7.467902,53.235657]},"DEMHG":{"name":"Mannheim","country":"Germany","coordinates":[8.45,49.48]},"DENHA":{"name":"Nordenham","country":"Germany","coordinates":[8.48,53.5]},"DENOR":{"name":"Nordstrand","country":"Germany","coordinates":[8.924652,54.510888]},"DEPFR":{"name":"Pfronten","country":"Germany","coordinates":[10.526435,47.576642]},"DERSK":{"name":"Rostock","country":"Germany","coordinates":[12.13,54.08]},"DEWED":{"name":"Wedel","country":"Germany","coordinates":[9.713747,53.582381]},"DEWOR":{"name":"Worms","country":"Germany","coordinates":[8.350718,49.634137]},"DEWVN":{"name":"Wilhelmshaven","country":"Germany","coordinates":[8.13,53.52]},"DJJIB":{"name":"Djibouti","country":"Djibouti","coordinates":[43.145648,11.572077]},"DJPOD":{"name":"Djibouti","country":"Djibouti","coordinates":[43.145648,11.572077]},"DKAAL":{"name":"Aalborg","country":"Denmark","coordinates":[9.921747,57.04882]},"DKAAR":{"name":"Aarhus","country":"Denmark","coordinates":[10.22,56.15]},"DKASN":{"name":"Assens","country":"Denmark","coordinates":[9.900819,55.270226]},"DKCPH":{"name":"Kobenhavn","country":"Denmark","coordinates":[12.58,55.67]},"DKEBJ":{"name":"Esbjerg","country":"Denmark","coordinates":[8.459405,55.476466]},"DKFRC":{"name":"Fredericia","country":"Denmark","coordinates":[9.749517,55.56893]},"DKHIR":{"name":"Hirtshals","country":"Denmark","coordinates":[9.966838,57.586953]},"DKHLS":{"name":"Helsingor","country":"Denmark","coordinates":[12.592127,56.030787]},"DKHSB":{"name":"Holstebro","country":"Denmark","coordinates":[8.621727,56.361534]},"DKKAL":{"name":"Kalundborg","country":"Denmark","coordinates":[11.089188,55.686429]},"DKKTD":{"name":"Kerteminde","country":"Denmark","coordinates":[10.654979,55.455882]},"DKNBG":{"name":"Nyborg","country":"Denmark","coordinates":[10.799561,55.324169]},"DKODE":{"name":"Odense","country":"Denmark","coordinates":[10.40237,55.403756]},"DKRUG":{"name":"Rungsted","country":"Denmark","coordinates":[12.52,55.88]},"DKSKA":{"name":"Skagen","country":"Denmark","coordinates":[10.579186,57.725004]},"DKSVE":{"name":"Svendborg","country":"Denmark","coordinates":[10.607282,55.067434]},"DMRSU":{"name":"Roseau","country":"Dominica","coordinates":[-61.379355,15.309168]},"DOCAU":{"name":"Caucedo","country":"Dominican Republic","coordinates":[-69.63,18.42]},"DOHAI":{"name":"Rio Haina","country":"Dominican Republic","coordinates":[-70.073107,18.513269]},"DOMAN":{"name":"Manzanillo","country":"Dominican Republic","coordinates":[-71.75,19.7]},"DOPOP":{"name":"Puerto Plata","country":"Dominican Republic","coordinates":[-70.687109,19.780769]},"DOSDQ":{"name":"Santo Domingo","country":"Dominican Republic","coordinates":[-69.9,18.47]},"DOSPM":{"name":"San Pedro de Macoris","country":"Dominican Republic","coordinates":[-69.3,18.45]},"DZAAE":{"name":"Annaba (ex Bone)","country":"Algeria","coordinates":[36.917867,7.674065]},"DZALG":{"name":"Alger (Algiers)","country":"Algeria","coordinates":[3.05,36.75]},"DZBJA":{"name":"Bejaia (ex Bougie)","country":"Algeria","coordinates":[5.056733,36.75089]},"DZGHA":{"name":"Ghardaia","country":"Algeria","coordinates":[3.67,32.48]},"DZMOS":{"name":"Mostaganem","country":"Algeria","coordinates":[0.140138,36.013124]},"DZORN":{"name":"Oran","country":"Algeria","coordinates":[-0.633738,35.697654]},"DZSKI":{"name":"Skikda (ex Philippeville)","country":"Algeria","coordinates":[6.910181,36.87152]},"ECBHA":{"name":"Bahia de Caraquez","country":"Ecuador","coordinates":[-80.5,-0.55]},"ECCHA":{"name":"Chanduy","country":"Ecuador","coordinates":[-80.67,-2.42]},"ECESM":{"name":"Esmeraldas","country":"Ecuador","coordinates":[-79.7,0.98]},"ECGYE":{"name":"Guayaquil","country":"Ecuador","coordinates":[-79.9,-2.17]},"ECLLD":{"name":"La Libertad","country":"Ecuador","coordinates":[-80.9,-2.23]},"ECMEC":{"name":"Manta","country":"Ecuador","coordinates":[-80.73,-0.95]},"ECMTV":{"name":"Monteverde","country":"Ecuador","coordinates":[-80.72,-2.03]},"ECPBO":{"name":"Puerto Bolivar","country":"Ecuador","coordinates":[-79.98,-3.27]},"EEHLD":{"name":"Haldi","country":"Estonia","coordinates":[22.4659,58.8094]},"EEMUG":{"name":"Muuga","country":"Estonia","coordinates":[24.97,59.5]},"EETLL":{"name":"Tallinn","country":"Estonia","coordinates":[24.73,59.43]},"EGAIS":{"name":"Ain Sukhna","country":"Egypt","coordinates":[32.330017,29.632562]},"EGALY":{"name":"El Iskandariya (Alexandria)","country":"Egypt","coordinates":[30.062594,31.309112]},"EGDAM":{"name":"Dumyat (Damietta)","country":"Egypt","coordinates":[31.82,31.42]},"EGEDK":{"name":"El Dekheila","country":"Egypt","coordinates":[30.771631,30.705154]},"EGPSD":{"name":"Port Said","country":"Egypt","coordinates":[32.301866,31.265289]},"EGSOK":{"name":"Sokhna Port","country":"Egypt","coordinates":[32.35,29.65]},"EGSUZ":{"name":"As Suways (Suez)","country":"Egypt","coordinates":[32.52,29.97]},"ERASA":{"name":"Assab","country":"Eritrea","coordinates":[42.73,13]},"ERMSW":{"name":"Massawa (Mitsiwa)","country":"Eritrea","coordinates":[39.45,15.6]},"ESACE":{"name":"Lanzarote","country":"Spain","coordinates":[-13.53,28.97]},"ESAGP":{"name":"Malaga","country":"Spain","coordinates":[-4.42,36.72]},"ESALC":{"name":"Alicante","country":"Spain","coordinates":[-0.490685,38.345996]},"ESALG":{"name":"Algeciras","country":"Spain","coordinates":[-5.456233,36.140759]},"ESAVS":{"name":"Aviles","country":"Spain","coordinates":[-5.924665,43.557952]},"ESBCN":{"name":"Barcelona","country":"Spain","coordinates":[2.173404,41.385064]},"ESBIO":{"name":"Bilbao","country":"Spain","coordinates":[-2.97,43.25]},"ESBRM":{"name":"Bermeo","country":"Spain","coordinates":[-2.72,43.43]},"ESCAD":{"name":"Cadiz","country":"Spain","coordinates":[-6.288596,36.527061]},"ESCAR":{"name":"Cartagena","country":"Spain","coordinates":[-0.996584,37.625683]},"ESCAS":{"name":"Castellon de la Plana","country":"Spain","coordinates":[-0.03,39.98]},"ESCEU":{"name":"Ceuta","country":"Spain","coordinates":[-5.321345,35.889387]},"ESFER":{"name":"Ferez","country":"Spain","coordinates":[-2,38.35]},"ESFUE":{"name":"Puerto del Rosario-Fuerteventura","country":"Spain","coordinates":[-13.862837,28.500821]},"ESGIJ":{"name":"Gijon","country":"Spain","coordinates":[-5.67,43.53]},"ESHUV":{"name":"Huelva","country":"Spain","coordinates":[-6.944722,37.261421]},"ESIBZ":{"name":"Ibiza","country":"Spain","coordinates":[1.43,38.9]},"ESLCG":{"name":"La Coruna (A Coruna)","country":"Spain","coordinates":[-8.38,43.37]},"ESLEI":{"name":"Almeria","country":"Spain","coordinates":[-2.45,36.83]},"ESLPA":{"name":"Las Palmas de Gran Canaria","country":"Spain","coordinates":[-15.42,28.1]},"ESMAD":{"name":"Madrid","country":"Spain","coordinates":[-3.70379,40.416775]},"ESMAH":{"name":"Mahon, Menorca","country":"Spain","coordinates":[4.25,39.88]},"ESMLN":{"name":"Melilla","country":"Spain","coordinates":[-2.88,35.32]},"ESMOT":{"name":"Motril","country":"Spain","coordinates":[-3.518036,36.748975]},"ESMPG":{"name":"Marin, Pontevedra","country":"Spain","coordinates":[-8.702082,42.391485]},"ESPAL":{"name":"Palamos","country":"Spain","coordinates":[3.13,41.85]},"ESPAS":{"name":"Pasajes","country":"Spain","coordinates":[-1.93014,43.32547]},"ESPMI":{"name":"Palma de Mallorca","country":"Spain","coordinates":[2.63,39.57]},"ESSCT":{"name":"Santa Cruz de Tenerife","country":"Spain","coordinates":[-16.251847,28.46363]},"ESSDR":{"name":"Santander","country":"Spain","coordinates":[-3.80998,43.462306]},"ESSNR":{"name":"Santurce","country":"Spain","coordinates":[-3.03,43.33]},"ESSPC":{"name":"Santa Cruz de La Palma","country":"Spain","coordinates":[-17.77,28.68]},"ESSVQ":{"name":"Sevilla","country":"Spain","coordinates":[-5.984459,37.389092]},"ESTAR":{"name":"Tarragona","country":"Spain","coordinates":[1.244491,41.118883]},"ESTBZ":{"name":"Tabuenca","country":"Spain","coordinates":[-1.54326,41.694936]},"ESTCI":{"name":"Tenerife","country":"Spain","coordinates":[-16.57,28.32]},"ESTOT":{"name":"Tortosa","country":"Spain","coordinates":[0.521442,40.812578]},"ESVGO":{"name":"Vigo","country":"Spain","coordinates":[-8.720727,42.240599]},"ESVLC":{"name":"Valencia","country":"Spain","coordinates":[-0.376288,39.469908]},"ESZAZ":{"name":"Zaragoza","country":"Spain","coordinates":[-0.889085,41.648823]},"FIHEL":{"name":"Helsingfors (Helsinki)","country":"Finland","coordinates":[24.941025,60.173324]},"FIHKO":{"name":"Hango (Hanko)","country":"Finland","coordinates":[22.965856,59.828346]},"FIHMN":{"name":"Fredrikshamn (Hamina)","country":"Finland","coordinates":[27.2,60.57]},"FIKEM":{"name":"Kemi/Tornea (Kemi/Tornio)","country":"Finland","coordinates":[24.57,65.73]},"FIKOK":{"name":"Karleby (Kokkola)","country":"Finland","coordinates":[23.12,63.83]},"FIKTK":{"name":"Kotka","country":"Finland","coordinates":[26.92,60.47]},"FIMTL":{"name":"Mantyluoto","country":"Finland","coordinates":[21.482277,61.591877]},"FIOLU":{"name":"Oulu","country":"Finland","coordinates":[25.471453,65.012615]},"FIOUL":{"name":"Oulu (Uleaborg)","country":"Finland","coordinates":[25.47,65.02]},"FIPOR":{"name":"Bjorneborg (Pori)","country":"Finland","coordinates":[21.8,61.48]},"FIRAU":{"name":"Rauma (Raumo)","country":"Finland","coordinates":[21.5,61.13]},"FITKU":{"name":"Abo (Turku)","country":"Finland","coordinates":[22.28,60.45]},"FITOR":{"name":"Tornea (Tornio)","country":"Finland","coordinates":[24.18,65.85]},"FJLTK":{"name":"Lautoka","country":"Fiji","coordinates":[177.45,-17.62]},"FJSUV":{"name":"Suva","country":"Fiji","coordinates":[178.450079,-18.124809]},"FKPSY":{"name":"Port Stanley","country":"Falkland Islands (Malvinas)","coordinates":[-57.851663,-51.697713]},"FMPNI":{"name":"Pohnpei (ex Ponape)","country":"Micronesia, Federated States of","coordinates":[158.262382,6.854125]},"FOTHO":{"name":"Thorshavn","country":"Faroe Islands","coordinates":[-6.77,62.02]},"FRBAS":{"name":"Bassens","country":"France","coordinates":[-0.516238,44.899835]},"FRBAY":{"name":"Bayonne","country":"France","coordinates":[-1.474841,43.492949]},"FRBES":{"name":"Brest","country":"France","coordinates":[-4.48,48.4]},"FRBOD":{"name":"Bordeaux","country":"France","coordinates":[-0.57918,44.837789]},"FRBOL":{"name":"Boulogne-sur-Mer","country":"France","coordinates":[1.62,50.72]},"FRCQF":{"name":"Calais","country":"France","coordinates":[1.858686,50.95129]},"FRDKK":{"name":"Dunkerque","country":"France","coordinates":[2.376776,51.034368]},"FRDPE":{"name":"Dieppe","country":"France","coordinates":[1.077483,49.922992]},"FRFOS":{"name":"Fos-sur-Mer","country":"France","coordinates":[4.93,43.43]},"FRHGT":{"name":"Chitry","country":"France","coordinates":[3.702397,47.762686]},"FRHON":{"name":"Honfleur","country":"France","coordinates":[5.130768,46.247571]},"FRJAV":{"name":"Javrezac","country":"France","coordinates":[5.130768,46.247571]},"FRLCT":{"name":"La Ciotat","country":"France","coordinates":[5.605155,43.173653]},"FRLEH":{"name":"Le Havre","country":"France","coordinates":[0.107929,49.49437]},"FRLPC":{"name":"La Planche","country":"France","coordinates":[-1.43,47]},"FRLPE":{"name":"La Pallice","country":"France","coordinates":[-1.205105,46.162575]},"FRLRH":{"name":"La Rochelle","country":"France","coordinates":[-1.151139,46.160329]},"FRLRT":{"name":"Lorient","country":"France","coordinates":[-3.370245,47.748252]},"FRLTR":{"name":"Le Treport","country":"France","coordinates":[1.382472,50.05917]},"FRLVE":{"name":"Le Verdon-sur-Mer","country":"France","coordinates":[-1.07,45.55]},"FRMDH":{"name":"Mundolsheim","country":"France","coordinates":[7.714441,48.643483]},"FRMRS":{"name":"Marseille","country":"France","coordinates":[5.4,43.3]},"FRMT7":{"name":"Monteton","country":"France","coordinates":[0.257688,44.622205]},"FRMTX":{"name":"Montoir-de-Bretagne","country":"France","coordinates":[-2.15,47.33]},"FRNTE":{"name":"Nantes","country":"France","coordinates":[-1.553621,47.218371]},"FROR6":{"name":"La Groise","country":"France","coordinates":[3.681767,50.078788]},"FRPAR":{"name":"Paris","country":"France","coordinates":[2.352222,48.856614]},"FRPIR":{"name":"Piriac-sur-Mer","country":"France","coordinates":[-2.55,47.38]},"FRPOV":{"name":"Port-Vendres","country":"France","coordinates":[3.12,42.52]},"FRSET":{"name":"Sete","country":"France","coordinates":[3.700822,43.407876]},"FRSNR":{"name":"Saint-Nazaire","country":"France","coordinates":[-2.2,47.28]},"FRTLN":{"name":"Toulon","country":"France","coordinates":[5.928,43.124228]},"FRURO":{"name":"Rouen","country":"France","coordinates":[1.099971,49.443232]},"FRUSJ":{"name":"Mus","country":"France","coordinates":[5.241851,43.951727]},"GACLZ":{"name":"Cap Lopez","country":"Gabon","coordinates":[8.781165,-0.772646]},"GALBV":{"name":"Libreville","country":"Gabon","coordinates":[9.467268,0.416198]},"GAOWE":{"name":"Owendo","country":"Gabon","coordinates":[9.483283,0.337956]},"GAPOG":{"name":"Port Gentil","country":"Gabon","coordinates":[8.759131,-0.735103]},"GBABD":{"name":"Aberdeen","country":"United Kingdom","coordinates":[-2.1,57.13]},"GBAVO":{"name":"Avonmouth","country":"United Kingdom","coordinates":[-2.68,51.5]},"GBBAD":{"name":"Barry Dock","country":"United Kingdom","coordinates":[-3.25,51.38]},"GBBDT":{"name":"Broadstairs","country":"United Kingdom","coordinates":[1.42,51.37]},"GBBEL":{"name":"Belfast","country":"United Kingdom","coordinates":[-5.92,54.98]},"GBBOC":{"name":"Burnham on Crouch","country":"United Kingdom","coordinates":[0.83,51.62]},"GBBOE":{"name":"Bootle","country":"United Kingdom","coordinates":[-3.02,53.47]},"GBBOS":{"name":"Boston","country":"United Kingdom","coordinates":[-0.026577,52.97894]},"GBBRK":{"name":"Birkenhead","country":"United Kingdom","coordinates":[-3.03,53.4]},"GBBRS":{"name":"Bristol","country":"United Kingdom","coordinates":[-2.58791,51.454513]},"GBBRT":{"name":"Bridlington","country":"United Kingdom","coordinates":[-0.2,54.08]},"GBBRW":{"name":"Bridgwater","country":"United Kingdom","coordinates":[-3.003632,51.127889]},"GBCDF":{"name":"Cardiff","country":"United Kingdom","coordinates":[-3.2,51.5]},"GBCFG":{"name":"Carrickfergus","country":"United Kingdom","coordinates":[-5.810121,54.726187]},"GBCHD":{"name":"Charleston","country":"United Kingdom","coordinates":[-3.006302,56.599797]},"GBCYN":{"name":"Cairnryan","country":"United Kingdom","coordinates":[-5.02,54.97]},"GBDUN":{"name":"Dundee","country":"United Kingdom","coordinates":[-2.97,56.5]},"GBDVR":{"name":"Dover","country":"United Kingdom","coordinates":[1.313403,51.127876]},"GBEDI":{"name":"Edinburgh","country":"United Kingdom","coordinates":[-3.2,55.95]},"GBELL":{"name":"Ellesmere Port","country":"United Kingdom","coordinates":[-2.887398,53.206828]},"GBEON":{"name":"Seaton","country":"United Kingdom","coordinates":[-3.071873,50.705313]},"GBFAL":{"name":"Falmouth","country":"United Kingdom","coordinates":[-5.06627,50.152571]},"GBFAW":{"name":"Fawley","country":"United Kingdom","coordinates":[-1.350227,50.826814]},"GBFRB":{"name":"Fraserburgh","country":"United Kingdom","coordinates":[-2.02,57.68]},"GBFXT":{"name":"Felixstowe","country":"United Kingdom","coordinates":[1.351255,51.961726]},"GBGCI":{"name":"Guernsey","country":"United Kingdom","coordinates":[-2.58949,49.448198]},"GBGLW":{"name":"Glasgow","country":"United Kingdom","coordinates":[-4.25,55.83]},"GBGOO":{"name":"Goole","country":"United Kingdom","coordinates":[-0.87,53.72]},"GBGRG":{"name":"Grangemouth","country":"United Kingdom","coordinates":[-3.72,56]},"GBGRK":{"name":"Greenock","country":"United Kingdom","coordinates":[-4.75,55.93]},"GBGSY":{"name":"Grimsby","country":"United Kingdom","coordinates":[-0.5,53.53]},"GBGTY":{"name":"Great Yarmouth","country":"United Kingdom","coordinates":[1.728047,52.598233]},"GBGVS":{"name":"Gravesend","country":"United Kingdom","coordinates":[0.370759,51.441884]},"GBHRW":{"name":"Harwich","country":"United Kingdom","coordinates":[1.260297,51.934731]},"GBHUL":{"name":"Hull","country":"United Kingdom","coordinates":[-0.32,53.73]},"GBIMM":{"name":"Immingham","country":"United Kingdom","coordinates":[-0.22,53.6]},"GBINK":{"name":"Inverkeithing","country":"United Kingdom","coordinates":[-3.398795,56.030043]},"GBINV":{"name":"Inverness","country":"United Kingdom","coordinates":[-4.224721,57.477773]},"GBIPS":{"name":"Ipswich","country":"United Kingdom","coordinates":[1.14822,52.056736]},"GBIRL":{"name":"Irlam","country":"United Kingdom","coordinates":[-2.42,53.43]},"GBKLN":{"name":"King\'s Lynn","country":"United Kingdom","coordinates":[0.38,52.75]},"GBLAN":{"name":"Lancaster","country":"United Kingdom","coordinates":[-2.80074,54.046575]},"GBLGP":{"name":"London Gateway Port","country":"United Kingdom","coordinates":[0.48,51.5]},"GBLIV":{"name":"Liverpool","country":"United Kingdom","coordinates":[-3,53.42]},"GBLON":{"name":"London","country":"United Kingdom","coordinates":[-0.127758,51.507351]},"GBMAL":{"name":"Maldon","country":"United Kingdom","coordinates":[0.671448,51.731805]},"GBMCD":{"name":"Macduff","country":"United Kingdom","coordinates":[-2.48,57.67]},"GBMID":{"name":"Middlesbrough","country":"United Kingdom","coordinates":[-1.15,54.57]},"GBMNS":{"name":"Moaness, Hoy","country":"United Kingdom","coordinates":[-3.310807,58.916352]},"GBMON":{"name":"Montrose","country":"United Kingdom","coordinates":[-2.45,56.7]},"GBMRY":{"name":"Maryport","country":"United Kingdom","coordinates":[-3.49494,54.714441]},"GBNCL":{"name":"Newcastle upon Tyne","country":"United Kingdom","coordinates":[-1.6,54.97]},"GBNFT":{"name":"Northfleet","country":"United Kingdom","coordinates":[0.33694,51.441072]},"GBNHV":{"name":"Newhaven","country":"United Kingdom","coordinates":[0.045574,50.79307]},"GBNLN":{"name":"Nelson","country":"United Kingdom","coordinates":[-2.219377,53.835187]},"GBNPT":{"name":"Newport","country":"United Kingdom","coordinates":[-3.030594,51.581186]},"GBPFT":{"name":"Purfleet","country":"United Kingdom","coordinates":[0.243312,51.486711]},"GBPLY":{"name":"Plymouth","country":"United Kingdom","coordinates":[-4.12,50.4]},"GBPME":{"name":"Portsmouth","country":"United Kingdom","coordinates":[-1.087977,50.819768]},"GBPOO":{"name":"Poole","country":"United Kingdom","coordinates":[-1.987248,50.71505]},"GBPRE":{"name":"Preston","country":"United Kingdom","coordinates":[-2.72,53.75]},"GBPRU":{"name":"Portbury","country":"United Kingdom","coordinates":[-2.72,51.47]},"GBPST":{"name":"Parkeston Quay","country":"United Kingdom","coordinates":[1.255846,51.94448]},"GBQUB":{"name":"Queenborough","country":"United Kingdom","coordinates":[0.747021,51.417285]},"GBRYE":{"name":"Rye","country":"United Kingdom","coordinates":[0.73726,50.949708]},"GBSCA":{"name":"Scarborough","country":"United Kingdom","coordinates":[-0.399752,54.283113]},"GBSHS":{"name":"Sheerness","country":"United Kingdom","coordinates":[0.764158,51.44011]},"GBSOU":{"name":"Southampton","country":"United Kingdom","coordinates":[-1.404351,50.9097]},"GBSOW":{"name":"Stanlow","country":"United Kingdom","coordinates":[-2.3,52.58]},"GBSSH":{"name":"South Shields","country":"United Kingdom","coordinates":[-1.42,54.98]},"GBSUN":{"name":"Sunderland","country":"United Kingdom","coordinates":[-1.38,54.9]},"GBSVT":{"name":"Silvertown","country":"United Kingdom","coordinates":[0.03,51.5]},"GBSWA":{"name":"Swansea","country":"United Kingdom","coordinates":[-3.97,51.63]},"GBTEE":{"name":"Teesport","country":"United Kingdom","coordinates":[-1.15,54.58]},"GBTHP":{"name":"Thamesport","country":"United Kingdom","coordinates":[0.521725,51.278708]},"GBTIL":{"name":"Tilbury","country":"United Kingdom","coordinates":[0.360498,51.463024]},"GBTYN":{"name":"Tyne","country":"United Kingdom","coordinates":[-1.43,55]},"GBWDN":{"name":"Widnes","country":"United Kingdom","coordinates":[-2.73,53.35]},"GBWOR":{"name":"Workington","country":"United Kingdom","coordinates":[-3.542752,54.643569]},"GBYVZ":{"name":"Stonehouse","country":"United Kingdom","coordinates":[-2.279028,51.743881]},"GDSTG":{"name":"Saint George\'s","country":"Grenada","coordinates":[-61.7488,12.056098]},"GEBUS":{"name":"Batumi","country":"Georgia","coordinates":[41.636746,41.616755]},"GEPTI":{"name":"Poti","country":"Georgia","coordinates":[41.67137,42.15826]},"GETBS":{"name":"Tbilisi","country":"Georgia","coordinates":[44.827096,41.715138]},"GFCAY":{"name":"Cayenne","country":"French Guiana","coordinates":[-52.313453,4.92242]},"GFDDC":{"name":"Degrad des Cannes","country":"French Guiana","coordinates":[-52.27,4.85]},"GHTEM":{"name":"Tema","country":"Ghana","coordinates":[0.018656,5.708924]},"GHTKD":{"name":"Takoradi","country":"Ghana","coordinates":[-1.783097,4.901579]},"GIGIB":{"name":"Gibraltar","country":"Gibraltar","coordinates":[-5.353599,36.140773]},"GMBJL":{"name":"Banjul","country":"Gambia","coordinates":[-16.579032,13.454876]},"GNCKY":{"name":"Conakry","country":"Guinea","coordinates":[-13.578401,9.641185]},"GPBBR":{"name":"Basse-Terre","country":"Guadeloupe","coordinates":[-61.72,16]},"GPGUS":{"name":"Gustavia","country":"Guadeloupe","coordinates":[-62.848653,17.896877]},"GPPTP":{"name":"Pointe-a-Pitre","country":"Guadeloupe","coordinates":[-61.534042,16.237687]},"GPSBH":{"name":"Saint Barthelemy","country":"Guadeloupe","coordinates":[-61.532353,16.254406]},"GQBSG":{"name":"Bata","country":"Equatorial Guinea","coordinates":[9.779023,1.853318]},"GQSSG":{"name":"Malabo","country":"Equatorial Guinea","coordinates":[8.737104,3.750412]},"GRAST":{"name":"Astakos","country":"Greece","coordinates":[21.08,38.53]},"GREEU":{"name":"Eleusina","country":"Greece","coordinates":[23.53,38.03]},"GRHER":{"name":"Heraklion (Iraklion)","country":"Greece","coordinates":[25.17,35.33]},"GRPIR":{"name":"Piraeus","country":"Greece","coordinates":[23.62,37.93]},"GRRHO":{"name":"Rhodes","country":"Greece","coordinates":[28.217483,36.434963]},"GRSKG":{"name":"Thessaloniki","country":"Greece","coordinates":[22.95,40.63]},"GRVOL":{"name":"Volos","country":"Greece","coordinates":[22.95,39.37]},"GTCHR":{"name":"Champerico","country":"Guatemala","coordinates":[-91.92,14.3]},"GTGUA":{"name":"Guatemala City","country":"Guatemala","coordinates":[-90.506882,14.634915]},"GTPBR":{"name":"Puerto Barrios","country":"Guatemala","coordinates":[-88.585517,15.695364]},"GTPRQ":{"name":"Puerto Quetzal","country":"Guatemala","coordinates":[-90.758034,13.930629]},"GTSTC":{"name":"Puerto Santo Tomas de Castilla","country":"Guatemala","coordinates":[-88.62,15.7]},"GUGUM":{"name":"Guam","country":"Guam","coordinates":[144.793731,13.444304]},"GWOXB":{"name":"Bissau","country":"Guinea-Bissau","coordinates":[-15.58,11.85]},"GYGEO":{"name":"Georgetown","country":"Guyana","coordinates":[-58.155125,6.801279]},"HKHKG":{"name":"Hong Kong","country":"Hong Kong","coordinates":[114.109497,22.396428]},"HNAMP":{"name":"Amapala","country":"Honduras","coordinates":[-87.651947,13.291654]},"HNLCE":{"name":"La Ceiba","country":"Honduras","coordinates":[-86.791901,15.770288]},"HNPCA":{"name":"Puerto Castilla","country":"Honduras","coordinates":[-85.958554,16.017327]},"HNPCR":{"name":"Puerto Cortes","country":"Honduras","coordinates":[-87.928578,15.825072]},"HNRTB":{"name":"Roatan","country":"Honduras","coordinates":[-86.55,16.3]},"HNSLO":{"name":"San Lorenzo","country":"Honduras","coordinates":[-87.455443,13.43262]},"HNTEA":{"name":"Tela","country":"Honduras","coordinates":[-87.46535,15.77326]},"HRDBV":{"name":"Dubrovnik","country":"Croatia","coordinates":[18.08,42.65]},"HRPLE":{"name":"Ploce","country":"Croatia","coordinates":[17.43,43.05]},"HRRJK":{"name":"Rijeka","country":"Croatia","coordinates":[14.4,45.33]},"HRSPU":{"name":"Split","country":"Croatia","coordinates":[16.45,43.5]},"HTCAP":{"name":"Cap-Haitien","country":"Haiti","coordinates":[-72.2,19.75]},"HTPAP":{"name":"Port-au-Prince","country":"Haiti","coordinates":[-72.307433,18.594395]},"HUBUD":{"name":"Budapest","country":"Hungary","coordinates":[19.040235,47.497912]},"IDAMA":{"name":"Amamapare, Ij","country":"Indonesia","coordinates":[136.797607,-4.86529]},"IDBDJ":{"name":"Banjarmasin","country":"Indonesia","coordinates":[114.594378,-3.318607]},"IDBIT":{"name":"Bitung, Sulawesi","country":"Indonesia","coordinates":[124.630337,1.132181]},"IDBLW":{"name":"Belawan, Sumatra","country":"Indonesia","coordinates":[98.694221,3.784303]},"IDBNT":{"name":"Belanak Terminal","country":"Indonesia","coordinates":[101.706829,0.293347]},"IDBPN":{"name":"Balikpapan","country":"Indonesia","coordinates":[116.83,-1.28]},"IDBTM":{"name":"Batam Island","country":"Indonesia","coordinates":[104.030454,1.082828]},"IDBUN":{"name":"Buatan","country":"Indonesia","coordinates":[101.818901,0.736198]},"IDBUR":{"name":"Batu Ampal","country":"Indonesia","coordinates":[116.856639,-1.245998]},"IDCIG":{"name":"Cigading","country":"Indonesia","coordinates":[105.987668,-6.011873]},"IDDJB":{"name":"Jambi, Sumatra","country":"Indonesia","coordinates":[103.61312,-1.610123]},"IDDJJ":{"name":"Jayapura, Irian Jaya","country":"Indonesia","coordinates":[140.669,-2.591602]},"IDJKT":{"name":"Jakarta, Java","country":"Indonesia","coordinates":[106.83,-6.13]},"IDKAB":{"name":"Kabil","country":"Indonesia","coordinates":[104.13,1.05]},"IDLBM":{"name":"Lobam","country":"Indonesia","coordinates":[104.27,1]},"IDMAK":{"name":"Makassar","country":"Indonesia","coordinates":[119.4,-5.12]},"IDMES":{"name":"Medan, Sumatra","country":"Indonesia","coordinates":[98.672223,3.595196]},"IDMRK":{"name":"Merak, Java","country":"Indonesia","coordinates":[106.006463,-5.934933]},"IDPDG":{"name":"Padang","country":"Indonesia","coordinates":[100.35,-0.95]},"IDPJG":{"name":"Panjang (Lampung, Sumatra)","country":"Indonesia","coordinates":[105.3219,-5.464116]},"IDPKU":{"name":"Pekanbaru, Sumatra","country":"Indonesia","coordinates":[101.447779,0.507068]},"IDPLM":{"name":"Palembang, Sumatra","country":"Indonesia","coordinates":[104.775431,-2.976073]},"IDPNJ":{"name":"Panjang","country":"Indonesia","coordinates":[105.3219,-5.464116]},"IDPNK":{"name":"Pontianak, Kalimantan","country":"Indonesia","coordinates":[109.342504,-0.02633]},"IDPWG":{"name":"Perawang","country":"Indonesia","coordinates":[101.62,0.63]},"IDSEQ":{"name":"Sungai Pakning, Sumatra","country":"Indonesia","coordinates":[102.077541,1.350141]},"IDSRG":{"name":"Semarang","country":"Indonesia","coordinates":[110.48,-6.97]},"IDSRI":{"name":"Samarinda, Kalimantan","country":"Indonesia","coordinates":[117.143615,-0.494823]},"IDSUB":{"name":"Surabaya","country":"Indonesia","coordinates":[112.75,-7.23]},"IDTAB":{"name":"Taboneo","country":"Indonesia","coordinates":[114.42,-3.68]},"IDTBG":{"name":"Teluk Betung, Sumatra","country":"Indonesia","coordinates":[105.267025,-5.445867]},"IDTPP":{"name":"Tanjung Priok","country":"Indonesia","coordinates":[106.871485,-6.132055]},"IDTTI":{"name":"Tebingtinggi","country":"Indonesia","coordinates":[99.156686,3.326288]},"IDUPG":{"name":"Ujung Pandang, Sulawesi","country":"Indonesia","coordinates":[119.549484,-5.077747]},"IEDRO":{"name":"Drogheda","country":"Ireland","coordinates":[-6.356098,53.717856]},"IEDUB":{"name":"Dublin","country":"Ireland","coordinates":[-6.26031,53.349805]},"IEFOV":{"name":"Foynes","country":"Ireland","coordinates":[-9.105112,52.610525]},"IEGWY":{"name":"Galway","country":"Ireland","coordinates":[-9.05679,53.270668]},"IEKLN":{"name":"Kinsale","country":"Ireland","coordinates":[-8.522501,51.70605]},"IEORK":{"name":"Cork","country":"Ireland","coordinates":[-8.486316,51.896892]},"IEWAT":{"name":"Waterford","country":"Ireland","coordinates":[-7.1,52.25]},"ILASH":{"name":"Ashdod","country":"Israel","coordinates":[34.655314,31.804381]},"ILHAD":{"name":"Hadera","country":"Israel","coordinates":[34.919652,32.434046]},"ILHFA":{"name":"Haifa","country":"Israel","coordinates":[34.989571,32.794046]},"ILNAT":{"name":"Netanya","country":"Israel","coordinates":[34.87,32.33]},"ILRMG":{"name":"Ramat Gan","country":"Israel","coordinates":[34.8,32.08]},"ILTLV":{"name":"Tel Aviv-Yafo","country":"Israel","coordinates":[34.75,32.05]},"INAGR":{"name":"Agra","country":"India","coordinates":[78.008074,27.17667]},"INAMD":{"name":"Ahmedabad","country":"India","coordinates":[72.62,23.03]},"INBLR":{"name":"Bangalore","country":"India","coordinates":[77.58,12.98]},"INBOM":{"name":"Mumbai (ex Bombay)","country":"India","coordinates":[72.82,18.97]},"INCCU":{"name":"Kolkata (ex Calcutta)","country":"India","coordinates":[88.35,22.57]},"INCDL":{"name":"Cuddalore","country":"India","coordinates":[79.768024,11.744699]},"INCJB":{"name":"Coimbatore","country":"India","coordinates":[76.955832,11.016845]},"INCOK":{"name":"Cochin","country":"India","coordinates":[76.23,9.97]},"INDAM":{"name":"Daman","country":"India","coordinates":[72.832799,20.397374]},"INDRI":{"name":"Dadri","country":"India","coordinates":[77.55,28.57]},"INGOI":{"name":"Goa","country":"India","coordinates":[74.123996,15.299327]},"INHAL":{"name":"Haldia","country":"India","coordinates":[88.069812,22.066674]},"INHYD":{"name":"Hyderabad","country":"India","coordinates":[79.176611,15.128385]},"INICD":{"name":"New Delhi","country":"India","coordinates":[77.2,28.6]},"INIXE":{"name":"Mangalore","country":"India","coordinates":[74.855957,12.914142]},"INIXY":{"name":"Kandla","country":"India","coordinates":[70.22,23.03]},"INJAI":{"name":"Jaipur","country":"India","coordinates":[75.82,26.92]},"INJDH":{"name":"Jodhpur","country":"India","coordinates":[73.03,26.25]},"INKAK":{"name":"Kakinada","country":"India","coordinates":[82.2475,16.9891]},"INKNU":{"name":"Kanpur","country":"India","coordinates":[80.331874,26.449923]},"INKRI":{"name":"Krishnapatnam","country":"India","coordinates":[80.124848,14.288601]},"INLUH":{"name":"Ludhiana","country":"India","coordinates":[75.857276,30.900965]},"INMAA":{"name":"Chennai (ex Madras)","country":"India","coordinates":[80.28,13.08]},"INMOR":{"name":"Moradabad","country":"India","coordinates":[78.78,28.83]},"INMRM":{"name":"Marmagao (Marmugao)","country":"India","coordinates":[73.78,15.4]},"INMUN":{"name":"Mundra","country":"India","coordinates":[69.721327,22.83952]},"INNAG":{"name":"Nagpur","country":"India","coordinates":[79.088155,21.1458]},"INNSA":{"name":"Jawaharlal Nehru (Nhava Sheva)","country":"India","coordinates":[72.950486,18.949716]},"INPAP":{"name":"Panipat","country":"India","coordinates":[76.963502,29.390946]},"INPAV":{"name":"Pipavav (Victor) Port","country":"India","coordinates":[71.55,20.97]},"INPIR":{"name":"Pithampur","country":"India","coordinates":[75.68229,22.613252]},"INPNQ":{"name":"Pune","country":"India","coordinates":[73.856744,18.52043]},"INSTV":{"name":"Surat","country":"India","coordinates":[72.83,21.17]},"INTUT":{"name":"Tuticorin","country":"India","coordinates":[78.13,8.78]},"INVTZ":{"name":"Visakhapatnam","country":"India","coordinates":[83.3,17.7]},"INVVA":{"name":"Veraval","country":"India","coordinates":[70.362852,20.915898]},"IQUQR":{"name":"Umm Qasr Port","country":"Iraq","coordinates":[47.93,30.03]},"IRBKM":{"name":"Bandar Khomeini","country":"Iran, Islamic Republic of","coordinates":[49.1,30.43]},"IRBND":{"name":"Bandar Abbas","country":"Iran, Islamic Republic of","coordinates":[56.27,27.18]},"IRBUZ":{"name":"Bushehr","country":"Iran, Islamic Republic of","coordinates":[50.83,28.97]},"IRKHO":{"name":"Khorramshahr","country":"Iran, Islamic Republic of","coordinates":[48.189119,30.425622]},"IRZBR":{"name":"Chah Bahar","country":"Iran, Islamic Republic of","coordinates":[60.645929,25.296878]},"ISESK":{"name":"Eskifjordur - hofn","country":"Iceland","coordinates":[65.072652,-14.053896]},"ISGRT":{"name":"Grundartangi","country":"Iceland","coordinates":[-21.775805,64.36038]},"ISREY":{"name":"Reykjavik","country":"Iceland","coordinates":[-21.95,64.15]},"ITAOI":{"name":"Ancona","country":"Italy","coordinates":[13.5,43.63]},"ITAUG":{"name":"Augusta","country":"Italy","coordinates":[15.22,37.22]},"ITBRI":{"name":"Bari","country":"Italy","coordinates":[16.85,41.13]},"ITCAG":{"name":"Cagliari","country":"Italy","coordinates":[9.12,39.22]},"ITCRV":{"name":"Crotone","country":"Italy","coordinates":[17.13,39.08]},"ITCTA":{"name":"Catania","country":"Italy","coordinates":[15.1,37.5]},"ITCVV":{"name":"Civitavecchia","country":"Italy","coordinates":[11.8,42.1]},"ITFAE":{"name":"Faenza","country":"Italy","coordinates":[11.877409,44.289853]},"ITGAE":{"name":"Gaeta","country":"Italy","coordinates":[13.58,41.2]},"ITGIT":{"name":"Gioia Tauro","country":"Italy","coordinates":[15.9,38.42]},"ITGOA":{"name":"Genova","country":"Italy","coordinates":[8.95,44.42]},"ITLIV":{"name":"Livorno","country":"Italy","coordinates":[10.32,43.55]},"ITMIL":{"name":"Milano","country":"Italy","coordinates":[9.185924,45.465422]},"ITMNF":{"name":"Monfalcone","country":"Italy","coordinates":[13.533173,45.805047]},"ITMOD":{"name":"Modena","country":"Italy","coordinates":[10.925227,44.647128]},"ITNAP":{"name":"Napoli","country":"Italy","coordinates":[14.25,40.83]},"ITPDA":{"name":"Padova","country":"Italy","coordinates":[11.876761,45.406435]},"ITPES":{"name":"Pesaro","country":"Italy","coordinates":[12.92,43.9]},"ITPGA":{"name":"Porto Garibaldi","country":"Italy","coordinates":[12.239566,44.678065]},"ITPMO":{"name":"Palermo","country":"Italy","coordinates":[13.37,38.12]},"ITPRJ":{"name":"Capri","country":"Italy","coordinates":[14.23,40.55]},"ITPSR":{"name":"Pescara","country":"Italy","coordinates":[14.23,42.47]},"ITPVE":{"name":"Portoscuso (Porto Vesme)","country":"Italy","coordinates":[8.38,39.2]},"ITRAN":{"name":"Ravenna","country":"Italy","coordinates":[12.203529,44.41836]},"ITSAL":{"name":"Salerno","country":"Italy","coordinates":[14.768096,40.682441]},"ITSPE":{"name":"La Spezia","country":"Italy","coordinates":[9.83,44.12]},"ITSVN":{"name":"Savona","country":"Italy","coordinates":[8.5,44.28]},"ITTAR":{"name":"Taranto","country":"Italy","coordinates":[17.23,40.48]},"ITTPS":{"name":"Trapani","country":"Italy","coordinates":[12.48,38.02]},"ITTRN":{"name":"Torino","country":"Italy","coordinates":[7.686856,45.070312]},"ITTRS":{"name":"Trieste","country":"Italy","coordinates":[13.776818,45.649526]},"ITVCE":{"name":"Venezia","country":"Italy","coordinates":[12.33,45.43]},"ITVDL":{"name":"Vado Ligure","country":"Italy","coordinates":[8.45,44.27]},"ITVIA":{"name":"Viareggio","country":"Italy","coordinates":[10.25131,43.865727]},"JMKIN":{"name":"Kingston","country":"Jamaica","coordinates":[-76.802893,18.042327]},"JMMBJ":{"name":"Montego Bay","country":"Jamaica","coordinates":[-77.893889,18.476223]},"JMOCJ":{"name":"Ocho Rios","country":"Jamaica","coordinates":[-77.096736,18.405682]},"JMPKS":{"name":"Port Kaiser","country":"Jamaica","coordinates":[-77.131978,17.893985]},"JOAQJ":{"name":"Al \'Aqabah","country":"Jordan","coordinates":[35,29.53]},"JPABU":{"name":"Aburatsu","country":"Japan","coordinates":[131.404163,31.582976]},"JPAOJ":{"name":"Aomori","country":"Japan","coordinates":[140.747365,40.822072]},"JPAXT":{"name":"Akita","country":"Japan","coordinates":[140.102564,39.720008]},"JPCHB":{"name":"Chiba","country":"Japan","coordinates":[140.106291,35.607267]},"JPFKY":{"name":"Fukuyama, Hiroshima","country":"Japan","coordinates":[133.362313,34.485899]},"JPHHE":{"name":"Hachinohe","country":"Japan","coordinates":[141.488399,40.512284]},"JPHIB":{"name":"Hibi","country":"Japan","coordinates":[133.922139,34.457482]},"JPHIC":{"name":"Hitachinaka","country":"Japan","coordinates":[140.534676,36.396615]},"JPHIJ":{"name":"Hiroshima","country":"Japan","coordinates":[132.455293,34.385203]},"JPHIM":{"name":"Himeji","country":"Japan","coordinates":[134.685353,34.815149]},"JPHKD":{"name":"Hakodate","country":"Japan","coordinates":[140.72881,41.768793]},"JPHKT":{"name":"Hakata/Fukuoka","country":"Japan","coordinates":[130.42,33.6]},"JPHMD":{"name":"Hamada","country":"Japan","coordinates":[132.079783,34.899303]},"JPHMM":{"name":"Hamamatsu","country":"Japan","coordinates":[137.726126,34.710834]},"JPHSM":{"name":"Hososhima","country":"Japan","coordinates":[131.669023,32.427517]},"JPHTC":{"name":"Hitachi","country":"Japan","coordinates":[140.651469,36.599154]},"JPIHA":{"name":"Niihama","country":"Japan","coordinates":[133.283351,33.96029]},"JPIMB":{"name":"Imabari","country":"Japan","coordinates":[132.99786,34.066098]},"JPIMI":{"name":"Imari","country":"Japan","coordinates":[129.880269,33.264586]},"JPISG":{"name":"Ishigaki","country":"Japan","coordinates":[124.15,24.33]},"JPISI":{"name":"Ishikari","country":"Japan","coordinates":[141.31552,43.171384]},"JPISM":{"name":"Ishinomaki","country":"Japan","coordinates":[141.302917,38.43448]},"JPISS":{"name":"Isa","country":"Japan","coordinates":[130.6,32.05]},"JPIWK":{"name":"Iwakuni","country":"Japan","coordinates":[132.218848,34.166562]},"JPIYM":{"name":"Iyomishima","country":"Japan","coordinates":[132.765681,33.841624]},"JPKCZ":{"name":"Kochi","country":"Japan","coordinates":[133.531168,33.558804]},"JPKGA":{"name":"Kakogawa","country":"Japan","coordinates":[134.841178,34.75695]},"JPKIJ":{"name":"Niigata","country":"Japan","coordinates":[139.036413,37.916192]},"JPKMJ":{"name":"Kumamoto","country":"Japan","coordinates":[130.707891,32.8031]},"JPKND":{"name":"Kanda, Fukuoka","country":"Japan","coordinates":[130.401716,33.590355]},"JPKNU":{"name":"Kinuura","country":"Japan","coordinates":[136.906565,35.180188]},"JPKNZ":{"name":"Kanazawa","country":"Japan","coordinates":[136.656205,36.561325]},"JPKOJ":{"name":"Kagoshima","country":"Japan","coordinates":[130.557116,31.596554]},"JPKRE":{"name":"Kure, Hiroshima","country":"Japan","coordinates":[132.565805,34.249254]},"JPKSM":{"name":"Kashima, Ibaraki","country":"Japan","coordinates":[140.644813,35.965756]},"JPKUH":{"name":"Kushiro","country":"Japan","coordinates":[144.381356,42.984854]},"JPKWS":{"name":"Kawasaki","country":"Japan","coordinates":[139.703051,35.530906]},"JPKZU":{"name":"Kisarazu","country":"Japan","coordinates":[140.106291,35.607267]},"JPMAI":{"name":"Maizuru","country":"Japan","coordinates":[135.768029,35.011636]},"JPMII":{"name":"Miike, Fukuoka","country":"Japan","coordinates":[130.480188,33.049747]},"JPMOJ":{"name":"Moji/Kitakyushu","country":"Japan","coordinates":[130.97,33.9]},"JPMUR":{"name":"Muroran","country":"Japan","coordinates":[140.973799,42.315231]},"JPMYJ":{"name":"Matsuyama","country":"Japan","coordinates":[132.765575,33.839157]},"JPNAH":{"name":"Naha, Okinawa","country":"Japan","coordinates":[127.679157,26.212312]},"JPNAN":{"name":"Nakanoseki","country":"Japan","coordinates":[131.53,34]},"JPNAO":{"name":"Naoetsu","country":"Japan","coordinates":[138.255038,37.18586]},"JPNAS":{"name":"Naoshima","country":"Japan","coordinates":[133.995689,34.459703]},"JPNGO":{"name":"Nagoya, Aichi","country":"Japan","coordinates":[136.906398,35.181446]},"JPNGS":{"name":"Nagasaki","country":"Japan","coordinates":[129.877667,32.750286]},"JPOIT":{"name":"Oita","country":"Japan","coordinates":[131.609272,33.239558]},"JPOKA":{"name":"Okinawa","country":"Japan","coordinates":[127.805583,26.334427]},"JPOMZ":{"name":"Omaezaki","country":"Japan","coordinates":[138.128127,34.637987]},"JPONA":{"name":"Onahama","country":"Japan","coordinates":[140.905586,36.949088]},"JPONO":{"name":"Onomichi","country":"Japan","coordinates":[133.204889,34.408876]},"JPOSA":{"name":"Osaka","country":"Japan","coordinates":[135.5,34.67]},"JPOTK":{"name":"Otake","country":"Japan","coordinates":[132.222475,34.237977]},"JPSAG":{"name":"Saganoseki","country":"Japan","coordinates":[131.875516,33.248948]},"JPSBS":{"name":"Shibushi","country":"Japan","coordinates":[131.045336,31.495442]},"JPSDJ":{"name":"Sendai, Miyagi","country":"Japan","coordinates":[140.92,38.13]},"JPSDS":{"name":"Sendaishinko","country":"Japan","coordinates":[140.8721,38.268837]},"JPSDU":{"name":"Sodegaura","country":"Japan","coordinates":[140.106291,35.607267]},"JPSEN":{"name":"Satsumasendai","country":"Japan","coordinates":[130.25,31.82]},"JPSHS":{"name":"Shimonoseki","country":"Japan","coordinates":[130.941459,33.957831]},"JPSKD":{"name":"Sakaide","country":"Japan","coordinates":[133.86051,34.316319]},"JPSKM":{"name":"Shikama","country":"Japan","coordinates":[135.183071,34.691269]},"JPSKT":{"name":"Sakata","country":"Japan","coordinates":[139.836515,38.914433]},"JPSMA":{"name":"Souma","country":"Japan","coordinates":[140.91947,37.796624]},"JPSMN":{"name":"Sakaiminato","country":"Japan","coordinates":[133.231486,35.539592]},"JPSMZ":{"name":"Shimizu","country":"Japan","coordinates":[138.489528,35.015888]},"JPTAK":{"name":"Takamatsu","country":"Japan","coordinates":[134.046574,34.342788]},"JPTBT":{"name":"Tobata/Kitakyushu","country":"Japan","coordinates":[130.83,33.88]},"JPTGO":{"name":"Tagonoura","country":"Japan","coordinates":[138.38276,34.975562]},"JPTHS":{"name":"Toyohashi","country":"Japan","coordinates":[136.906565,35.180188]},"JPTKS":{"name":"Tokushima","country":"Japan","coordinates":[134.554844,34.07027]},"JPTKY":{"name":"Tokuyama","country":"Japan","coordinates":[131.81444,34.064752]},"JPTMI":{"name":"Tsukumi","country":"Japan","coordinates":[131.861277,33.072183]},"JPTMK":{"name":"Tomakomai","country":"Japan","coordinates":[141.605372,42.634302]},"JPTND":{"name":"Tonda","country":"Japan","coordinates":[131.760053,34.074885]},"JPTOK":{"name":"Tokachi","country":"Japan","coordinates":[143.691,42.915]},"JPTOS":{"name":"Toyamashinko","country":"Japan","coordinates":[137.213677,36.695952]},"JPTOY":{"name":"Toyama","country":"Japan","coordinates":[137.213677,36.695952]},"JPTRG":{"name":"Tsuruga","country":"Japan","coordinates":[136.055441,35.645244]},"JPTYO":{"name":"Tokyo","country":"Japan","coordinates":[139.75,35.68]},"JPUBJ":{"name":"Ube","country":"Japan","coordinates":[131.246786,33.951509]},"JPUKB":{"name":"Kobe","country":"Japan","coordinates":[135.17,34.68]},"JPUNT":{"name":"Unten","country":"Japan","coordinates":[128.003306,26.681767]},"JPWAK":{"name":"Wakayama","country":"Japan","coordinates":[135.170808,34.230511]},"JPYAT":{"name":"Yatsushiro","country":"Japan","coordinates":[130.601743,32.507437]},"JPYKK":{"name":"Yokkaichi","country":"Japan","coordinates":[136.624485,34.965157]},"JPYOK":{"name":"Yokohama","country":"Japan","coordinates":[139.65,35.45]},"JPYOS":{"name":"Yokosuka","country":"Japan","coordinates":[139.67,35.28]},"JPYWT":{"name":"Yawata/Kitakyushu","country":"Japan","coordinates":[130.8,33.85]},"KEMBA":{"name":"Mombasa","country":"Kenya","coordinates":[39.668207,-4.043477]},"KENBO":{"name":"Nairobi","country":"Kenya","coordinates":[36.821946,-1.292066]},"KHKOS":{"name":"Kampong Saom","country":"Cambodia","coordinates":[103.52,10.6]},"KHPNH":{"name":"Phnom Penh","country":"Cambodia","coordinates":[104.892167,11.544873]},"KITRW":{"name":"Tarawa","country":"Kiribati","coordinates":[172.979053,1.329053]},"KMMUT":{"name":"Mutsamudu, Anjouan","country":"Comoros","coordinates":[44.38,-12.15]},"KMYVA":{"name":"Moroni","country":"Comoros","coordinates":[43.247315,-11.717216]},"KNBAS":{"name":"Basseterre, Saint Kitts","country":"Saint Kitts and Nevis","coordinates":[-62.717692,17.302606]},"KNNEV":{"name":"Nevis","country":"Saint Kitts and Nevis","coordinates":[-62.58,17.15]},"KPCHO":{"name":"Chongjin","country":"Korea, Democratic People\'s Republic of","coordinates":[129.723402,41.766967]},"KPWON":{"name":"Wonsan","country":"Korea, Democratic People\'s Republic of","coordinates":[127.446,39.153853]},"KRCHA":{"name":"Jeju","country":"South Korea","coordinates":[126.52,33.5]},"KRCHF":{"name":"Jinhae","country":"South Korea","coordinates":[128.65,35.13]},"KRCHG":{"name":"Janghang/Seocheon-gun","country":"South Korea","coordinates":[126.68,36]},"KRCHO":{"name":"Cheonan","country":"South Korea","coordinates":[127.15,36.8]},"KRCHU":{"name":"Chungju","country":"South Korea","coordinates":[127.92,36.97]},"KRCHW":{"name":"Changwon","country":"South Korea","coordinates":[128.67,35.22]},"KRCWO":{"name":"Cheongwon-gun","country":"South Korea","coordinates":[127.48,36.63]},"KRGYG":{"name":"Goyang","country":"South Korea","coordinates":[126.82,37.65]},"KRHIN":{"name":"Jinju","country":"South Korea","coordinates":[128.08,35.17]},"KRHYG":{"name":"Hamyang-gun","country":"South Korea","coordinates":[127.72,35.52]},"KRHYP":{"name":"Hyeonpoong/Daegu","country":"South Korea","coordinates":[128.43,35.68]},"KRICH":{"name":"Incheon","country":"South Korea","coordinates":[126.616896,37.476789]},"KRINC":{"name":"Incheon","country":"South Korea","coordinates":[126.62,37.45]},"KRJEO":{"name":"Jeongeup","country":"South Korea","coordinates":[126.85,35.57]},"KRJGE":{"name":"Geoje","country":"South Korea","coordinates":[128.68,34.9]},"KRJHG":{"name":"Jangheung-gun","country":"South Korea","coordinates":[126.9,34.67]},"KRKAN":{"name":"Gwangyang","country":"South Korea","coordinates":[127.7,34.93]},"KRKJE":{"name":"Geoje","country":"South Korea","coordinates":[128.58,34.85]},"KRKMC":{"name":"Gimcheon","country":"South Korea","coordinates":[128.1,36.12]},"KRKPO":{"name":"Pohang","country":"South Korea","coordinates":[129.37,36.03]},"KRKUM":{"name":"Gumi","country":"South Korea","coordinates":[128.33,36.12]},"KRKUV":{"name":"Gunsan","country":"South Korea","coordinates":[126.72,35.98]},"KRKWJ":{"name":"Gwangju","country":"South Korea","coordinates":[126.9,35.15]},"KRKWU":{"name":"Gwangju","country":"South Korea","coordinates":[127.25,37.4]},"KRMAS":{"name":"Masan","country":"South Korea","coordinates":[128.57,35.2]},"KRMOK":{"name":"Mokpo","country":"South Korea","coordinates":[126.38,34.8]},"KRNYJ":{"name":"Namyang/Goheung-gun","country":"South Korea","coordinates":[127.33,34.72]},"KRNYU":{"name":"Namyangju","country":"South Korea","coordinates":[127.2,37.63]},"KROKP":{"name":"Okpo/Geoje","country":"South Korea","coordinates":[128.7,34.88]},"KRONS":{"name":"Onsan/Ulsan","country":"South Korea","coordinates":[129.35,35.43]},"KRPUS":{"name":"Busan","country":"South Korea","coordinates":[129.05,35.13]},"KRSCP":{"name":"Samcheonpo/Sacheon","country":"South Korea","coordinates":[128.07,34.92]},"KRSEL":{"name":"Seoul","country":"South Korea","coordinates":[126.93,37.52]},"KRSSA":{"name":"Seosan","country":"South Korea","coordinates":[126.45,36.78]},"KRTAE":{"name":"Daegu","country":"South Korea","coordinates":[128.58,35.87]},"KRTJN":{"name":"Daejeon","country":"South Korea","coordinates":[127.42,36.32]},"KRTYG":{"name":"Tongyeong","country":"South Korea","coordinates":[128.42,34.83]},"KRUSN":{"name":"Ulsan","country":"South Korea","coordinates":[129.32,35.53]},"KRYES":{"name":"Yesan","country":"South Korea","coordinates":[126.83,36.68]},"KRYOS":{"name":"Yeosu","country":"South Korea","coordinates":[127.72,34.75]},"KWKWI":{"name":"Kuwait","country":"Kuwait","coordinates":[47.977405,29.375859]},"KWSAA":{"name":"Shuaiba","country":"Kuwait","coordinates":[48.13,29.03]},"KWSWK":{"name":"Shuwaikh","country":"Kuwait","coordinates":[47.967089,29.367192]},"KYGCM":{"name":"Grand Cayman","country":"Cayman Islands","coordinates":[-81.35,19.28]},"KYGEC":{"name":"Georgetown, Grand Cayman","country":"Cayman Islands","coordinates":[-81.38,19.3]},"LBBEY":{"name":"Beirut","country":"Lebanon","coordinates":[35.48,33.83]},"LBKYE":{"name":"Tripoli","country":"Lebanon","coordinates":[35.830837,34.438094]},"LCCAS":{"name":"Castries","country":"Saint Lucia","coordinates":[-60.97,14.02]},"LCSLU":{"name":"Saint Lucia Apt","country":"Saint Lucia","coordinates":[-60.93,13.7]},"LCVIF":{"name":"Vieux Fort","country":"Saint Lucia","coordinates":[-60.949643,13.720608]},"LKCMB":{"name":"Colombo","country":"Sri Lanka","coordinates":[79.85,6.92]},"LRMLW":{"name":"Monrovia","country":"Liberia","coordinates":[-10.8,6.3]},"LTKLJ":{"name":"Klaipeda","country":"Lithuania","coordinates":[21.144279,55.703295]},"LVAIZ":{"name":"Aizkraukle","country":"Latvia","coordinates":[25.27645,57.694876]},"LVRIX":{"name":"Riga","country":"Latvia","coordinates":[24.093533,56.961029]},"LVSAL":{"name":"Salacgriva","country":"Latvia","coordinates":[24.49239,57.706961]},"LVVNT":{"name":"Ventspils","country":"Latvia","coordinates":[21.858756,57.283368]},"LYBEN":{"name":"Bingazi (Benghazi)","country":"Libya","coordinates":[20.187911,32.094771]},"LYKHO":{"name":"Al Khums","country":"Libya","coordinates":[14.27,32.65]},"LYMRA":{"name":"Misurata","country":"Libya","coordinates":[15.102508,32.319683]},"LYTIP":{"name":"Tripoli","country":"Libya","coordinates":[13.191338,32.887209]},"MAAGA":{"name":"Agadir","country":"Morocco","coordinates":[-9.598107,30.427755]},"MACAS":{"name":"Casablanca","country":"Morocco","coordinates":[-7.6,33.58]},"MAELJ":{"name":"El Jadida","country":"Morocco","coordinates":[-8.5,33.25]},"MAMOH":{"name":"Mohammedia","country":"Morocco","coordinates":[-7.384855,33.683509]},"MANNA":{"name":"Kenitra (ex Port Lyautey)","country":"Morocco","coordinates":[-6.589017,34.25405]},"MAPTM":{"name":"Tanger Med","country":"Morocco","coordinates":[-5.79073,35.740772]},"MASFI":{"name":"Safi","country":"Morocco","coordinates":[-9.227203,32.300815]},"MATNG":{"name":"Tangier","country":"Morocco","coordinates":[-5.82,35.78]},"MCMON":{"name":"Monaco","country":"Monaco","coordinates":[7.420816,43.737411]},"MDGIU":{"name":"Giurgiulesti","country":"Moldova, Republic of","coordinates":[28.18,45.47]},"MEBAR":{"name":"Bar","country":"Montenegro","coordinates":[19.08,42.08]},"MGDIE":{"name":"Antsiranana","country":"Madagascar","coordinates":[49.294283,-12.323135]},"MGEHL":{"name":"Ehoala","country":"Madagascar","coordinates":[46.95,-25.1]},"MGMJN":{"name":"Majunga (Mahajanga)","country":"Madagascar","coordinates":[46.333686,-15.692149]},"MGNOS":{"name":"Nosy-Be","country":"Madagascar","coordinates":[48.25,-13.33]},"MGTLE":{"name":"Tulear (Toliara)","country":"Madagascar","coordinates":[43.685494,-23.351619]},"MGTMM":{"name":"Tamatave (Toamasina)","country":"Madagascar","coordinates":[49.395784,-18.144281]},"MGTOA":{"name":"Toamasina","country":"Madagascar","coordinates":[49.395784,-18.144281]},"MHMAJ":{"name":"Majuro","country":"Marshall Islands","coordinates":[171.38,7.1]},"MLBKO":{"name":"Bamako","country":"Mali","coordinates":[-8.002889,12.639232]},"MMRGN":{"name":"Yangon","country":"Myanmar","coordinates":[96.195132,16.866069]},"MOMFM":{"name":"Macau","country":"Macao","coordinates":[113.53,22.2]},"MPSPN":{"name":"Saipan","country":"Northern Mariana Islands","coordinates":[145.750967,15.177801]},"MQFDF":{"name":"Fort-de-France","country":"Martinique","coordinates":[-61.05878,14.616065]},"MRNDB":{"name":"Nouadhibou","country":"Mauritania","coordinates":[-17.036227,20.942518]},"MRNKC":{"name":"Nouakchott","country":"Mauritania","coordinates":[-15.958237,18.07353]},"MTMAR":{"name":"Marsaxlokk","country":"Malta","coordinates":[14.53,35.83]},"MTMGZ":{"name":"Mgarr, Gozo","country":"Malta","coordinates":[14.419932,35.890497]},"MTMLA":{"name":"Valletta","country":"Malta","coordinates":[14.5,35.88]},"MUPLU":{"name":"Port Louis","country":"Mauritius","coordinates":[57.502332,-20.166896]},"MVMLE":{"name":"Male","country":"Maldives","coordinates":[73.5,4.17]},"MWBLZ":{"name":"Blantyre","country":"Malawi","coordinates":[35.016787,-15.766671]},"MXACA":{"name":"Acapulco","country":"Mexico","coordinates":[-99.87,16.85]},"MXATM":{"name":"Altamira","country":"Mexico","coordinates":[-97.92,22.4]},"MXCOA":{"name":"Coatzacoalcos","country":"Mexico","coordinates":[-94.4,18.13]},"MXESE":{"name":"Ensenada","country":"Mexico","coordinates":[-116.6,31.85]},"MXGYM":{"name":"Guaymas","country":"Mexico","coordinates":[-110.88,27.92]},"MXMEX":{"name":"Ciudad de Mexico","country":"Mexico","coordinates":[-99.13,19.43]},"MXPGO":{"name":"Progreso","country":"Mexico","coordinates":[-89.65,21.27]},"MXPMS":{"name":"Puerto Morelos","country":"Mexico","coordinates":[-86.87,20.83]},"MXREX":{"name":"Reynosa","country":"Mexico","coordinates":[-98.28,26.08]},"MXSCX":{"name":"Salina Cruz","country":"Mexico","coordinates":[-95.18,16.17]},"MXTAM":{"name":"Tampico","country":"Mexico","coordinates":[-97.87,22.25]},"MXTPB":{"name":"Topolobampo","country":"Mexico","coordinates":[-109.05,25.6]},"MXVER":{"name":"Veracruz","country":"Mexico","coordinates":[-96.08,19.2]},"MXZLO":{"name":"Manzanillo","country":"Mexico","coordinates":[-104.3,19.05]},"MYBKI":{"name":"Kota Kinabalu, Sabah","country":"Malaysia","coordinates":[116.073457,5.980408]},"MYBTU":{"name":"Bintulu, Sarawak","country":"Malaysia","coordinates":[113.041907,3.171322]},"MYBWH":{"name":"Bagan Luar (Butterworth)","country":"Malaysia","coordinates":[100.370839,5.41051]},"MYJHB":{"name":"Johor Bahru","country":"Malaysia","coordinates":[103.741359,1.492659]},"MYKCH":{"name":"Kuching, Sarawak","country":"Malaysia","coordinates":[110.378544,1.607681]},"MYKUA":{"name":"Kuantan (Tanjong Gelang)","country":"Malaysia","coordinates":[103.43,3.960833]},"MYLBU":{"name":"Labuan, Sabah","country":"Malaysia","coordinates":[115.230825,5.283146]},"MYMEP":{"name":"Mersing","country":"Malaysia","coordinates":[103.836115,2.430917]},"MYMKZ":{"name":"Malacca","country":"Malaysia","coordinates":[102.250087,2.189594]},"MYMUA":{"name":"Muar","country":"Malaysia","coordinates":[102.584872,2.063052]},"MYMYY":{"name":"Miri, Sarawak","country":"Malaysia","coordinates":[113.991383,4.399493]},"MYPEN":{"name":"Penang (Georgetown)","country":"Malaysia","coordinates":[100.323373,5.416303]},"MYPGU":{"name":"Pasir Gudang, Johor","country":"Malaysia","coordinates":[103.902969,1.470288]},"MYPKG":{"name":"Port Klang (Pelabuhan Klang)","country":"Malaysia","coordinates":[101.4,3]},"MYSBW":{"name":"Sibu, Sarawak","country":"Malaysia","coordinates":[111.830535,2.287284]},"MYSDK":{"name":"Sandakan, Sabah","country":"Malaysia","coordinates":[118.117173,5.839444]},"MYTBA":{"name":"Tanjong Baran","country":"Malaysia","coordinates":[113.976944,4.593333]},"MYTGG":{"name":"Kuala Terengganu","country":"Malaysia","coordinates":[103.137014,5.329624]},"MYTPP":{"name":"Tanjung Pelepas","country":"Malaysia","coordinates":[103.55,1.37]},"MYTWU":{"name":"Tawau, Sabah","country":"Malaysia","coordinates":[117.891186,4.244651]},"MYWSP":{"name":"Westport/Port Klang","country":"Malaysia","coordinates":[101.3,2.95]},"MZBEW":{"name":"Beira","country":"Mozambique","coordinates":[34.884487,-19.796382]},"MZMNC":{"name":"Nacala","country":"Mozambique","coordinates":[40.685431,-14.565606]},"MZMPM":{"name":"Maputo","country":"Mozambique","coordinates":[32.605135,-25.891968]},"MZPOL":{"name":"Pemba","country":"Mozambique","coordinates":[40.517801,-12.973203]},"MZUEL":{"name":"Quelimane","country":"Mozambique","coordinates":[36.921858,-17.850264]},"NALUD":{"name":"Luderitz","country":"Namibia","coordinates":[15.17,-26.65]},"NAWVB":{"name":"Walvis Bay","country":"Namibia","coordinates":[14.524746,-22.938959]},"NCNOU":{"name":"Noumea","country":"New Caledonia","coordinates":[166.45,-22.27]},"NGAPP":{"name":"Apapa","country":"Nigeria","coordinates":[3.364084,6.45528]},"NGCBQ":{"name":"Calabar","country":"Nigeria","coordinates":[8.341701,4.975717]},"NGLOS":{"name":"Lagos","country":"Nigeria","coordinates":[3.379206,6.524379]},"NGONN":{"name":"Onne","country":"Nigeria","coordinates":[7.15,4.72]},"NGPHC":{"name":"Port Harcourt","country":"Nigeria","coordinates":[7.049844,4.815554]},"NGTIN":{"name":"Tincan/Lagos","country":"Nigeria","coordinates":[3.342908,6.433479]},"NGWAR":{"name":"Warri","country":"Nigeria","coordinates":[5.793201,5.5544]},"NICIO":{"name":"Corinto","country":"Nicaragua","coordinates":[-87.178433,12.490741]},"NIMGA":{"name":"Managua","country":"Nicaragua","coordinates":[-86.236174,12.114993]},"NLALK":{"name":"Alkmaar","country":"Netherlands","coordinates":[4.753375,52.632381]},"NLAMS":{"name":"Amsterdam","country":"Netherlands","coordinates":[4.895168,52.370216]},"NLBZM":{"name":"Bergen op Zoom","country":"Netherlands","coordinates":[4.3,51.5]},"NLDOR":{"name":"Dordrecht","country":"Netherlands","coordinates":[4.690093,51.813298]},"NLEEM":{"name":"Eemshaven","country":"Netherlands","coordinates":[6.835494,53.438589]},"NLGRQ":{"name":"Groningen","country":"Netherlands","coordinates":[6.566502,53.219384]},"NLHVH":{"name":"Hoek van Holland","country":"Netherlands","coordinates":[4.134185,51.980632]},"NLIJM":{"name":"IJmuiden/Velsen","country":"Netherlands","coordinates":[4.6,52.47]},"NLMOE":{"name":"Moerdijk","country":"Netherlands","coordinates":[4.62735,51.701817]},"NLRTM":{"name":"Rotterdam","country":"Netherlands","coordinates":[4.5,51.92]},"NLTNZ":{"name":"Terneuzen","country":"Netherlands","coordinates":[3.832427,51.332285]},"NLVEL":{"name":"Velsen","country":"Netherlands","coordinates":[4.630587,52.452059]},"NLVLA":{"name":"Vlaardingen","country":"Netherlands","coordinates":[4.349437,51.912067]},"NLVLI":{"name":"Vlissingen","country":"Netherlands","coordinates":[3.570913,51.453667]},"NLZAA":{"name":"Zaandam","country":"Netherlands","coordinates":[4.829199,52.44204]},"NLZEW":{"name":"Zeewolde","country":"Netherlands","coordinates":[5.55,52.35]},"NOAAV":{"name":"Alvik","country":"Norway","coordinates":[6.42,60.42]},"NOAES":{"name":"Alesund","country":"Norway","coordinates":[6.15,62.47]},"NOAGO":{"name":"Agotnes","country":"Norway","coordinates":[5.02,60.4]},"NOAND":{"name":"Andalsnes","country":"Norway","coordinates":[7.7,62.57]},"NOASV":{"name":"Austevoll","country":"Norway","coordinates":[5.15,60.1]},"NOBGO":{"name":"Bergen","country":"Norway","coordinates":[5.32,60.38]},"NOBOO":{"name":"Bodo","country":"Norway","coordinates":[14.37,67.28]},"NOBVK":{"name":"Brevik","country":"Norway","coordinates":[9.7,59.05]},"NODRM":{"name":"Drammen","country":"Norway","coordinates":[10.23,59.73]},"NOEGD":{"name":"Eigersund","country":"Norway","coordinates":[5.899172,58.373005]},"NOFKG":{"name":"Frekhaug","country":"Norway","coordinates":[5.239714,60.511941]},"NOFRK":{"name":"Fredrikstad","country":"Norway","coordinates":[10.92,59.2]},"NOFRO":{"name":"Floro","country":"Norway","coordinates":[5.02,61.6]},"NOFUS":{"name":"Fusa","country":"Norway","coordinates":[5.62,60.2]},"NOGJM":{"name":"Gjemnes","country":"Norway","coordinates":[7.762893,62.951026]},"NOHAL":{"name":"Halden","country":"Norway","coordinates":[11.37,59.1]},"NOHAU":{"name":"Haugesund","country":"Norway","coordinates":[5.25,59.4]},"NOHES":{"name":"Hestvika","country":"Norway","coordinates":[9.193158,63.563768]},"NOHLA":{"name":"Holla","country":"Norway","coordinates":[9.18,59.6]},"NOHOG":{"name":"Hogset","country":"Norway","coordinates":[15.02,67.13]},"NOHOY":{"name":"Husoy - Tonsberg","country":"Norway","coordinates":[10.45,59.23]},"NOHRI":{"name":"Hareid","country":"Norway","coordinates":[6.03,62.37]},"NOHRY":{"name":"Heroya","country":"Norway","coordinates":[9.62,59.12]},"NOHUS":{"name":"Husnes","country":"Norway","coordinates":[5.77,59.87]},"NOHVI":{"name":"Havik","country":"Norway","coordinates":[5.32,59.3]},"NOIKR":{"name":"Ikornnes","country":"Norway","coordinates":[6.55,62.38]},"NOKMY":{"name":"Karmoy","country":"Norway","coordinates":[5.263402,59.276775]},"NOKRS":{"name":"Kristiansand","country":"Norway","coordinates":[7.98,58.13]},"NOKRV":{"name":"Knarrevik","country":"Norway","coordinates":[5.15,60.37]},"NOKSU":{"name":"Kristiansund","country":"Norway","coordinates":[7.73,63.1]},"NOLAR":{"name":"Larvik","country":"Norway","coordinates":[10.02,59.03]},"NOLND":{"name":"Lyngdal","country":"Norway","coordinates":[7.070436,58.137898]},"NOMAY":{"name":"Maloy","country":"Norway","coordinates":[5.1,61.93]},"NOMOL":{"name":"Molde","country":"Norway","coordinates":[7.15,62.73]},"NOMON":{"name":"Mongstad","country":"Norway","coordinates":[5.02,60.82]},"NOMQN":{"name":"Mo i Rana","country":"Norway","coordinates":[14.13,66.32]},"NOMSS":{"name":"Moss","country":"Norway","coordinates":[10.65,59.42]},"NOODD":{"name":"Odda","country":"Norway","coordinates":[6.53,60.07]},"NOORK":{"name":"Orkanger","country":"Norway","coordinates":[9.83,63.32]},"NOOSL":{"name":"Oslo","country":"Norway","coordinates":[10.73,59.9]},"NOPOR":{"name":"Porsgrunn","country":"Norway","coordinates":[9.63,59.13]},"NOSAD":{"name":"Sandefjord","country":"Norway","coordinates":[10.22,59.12]},"NOSAU":{"name":"Sauda","country":"Norway","coordinates":[6.35,59.63]},"NOSKE":{"name":"Skien","country":"Norway","coordinates":[9.6,59.2]},"NOSKX":{"name":"Skogn","country":"Norway","coordinates":[11.15,63.7]},"NOSLX":{"name":"Sortland","country":"Norway","coordinates":[15.42,68.7]},"NOSPG":{"name":"Sarpsborg","country":"Norway","coordinates":[11.1,59.27]},"NOSRP":{"name":"Stord","country":"Norway","coordinates":[5.48,59.75]},"NOSTM":{"name":"Straumen","country":"Norway","coordinates":[15.6,67.35]},"NOSUN":{"name":"Sunndalsora","country":"Norway","coordinates":[8.55,62.67]},"NOSVE":{"name":"Svelgen","country":"Norway","coordinates":[5.28,61.77]},"NOSVG":{"name":"Stavanger","country":"Norway","coordinates":[5.75,58.97]},"NOTAE":{"name":"Tananger","country":"Norway","coordinates":[5.58,58.92]},"NOTON":{"name":"Tonsberg","country":"Norway","coordinates":[10.42,59.25]},"NOTOS":{"name":"Tromso","country":"Norway","coordinates":[18.97,69.67]},"NOTRD":{"name":"Trondheim","country":"Norway","coordinates":[10.4,63.43]},"NZAKL":{"name":"Auckland","country":"New Zealand","coordinates":[174.8,-36.83]},"NZBLU":{"name":"Bluff","country":"New Zealand","coordinates":[168.32,-46.6]},"NZCHC":{"name":"Christchurch","country":"New Zealand","coordinates":[172.63,-43.53]},"NZDUD":{"name":"Dunedin","country":"New Zealand","coordinates":[170.5,-45.87]},"NZHLZ":{"name":"Hamilton","country":"New Zealand","coordinates":[175.28,-37.78]},"NZLYT":{"name":"Lyttelton","country":"New Zealand","coordinates":[172.72,-43.6]},"NZMNR":{"name":"Middleton/Christchurch","country":"New Zealand","coordinates":[172.590944,-43.544204]},"NZNPE":{"name":"Napier","country":"New Zealand","coordinates":[176.9,-39.47]},"NZNPL":{"name":"New Plymouth","country":"New Zealand","coordinates":[174.02,-39.05]},"NZNSN":{"name":"Nelson","country":"New Zealand","coordinates":[173.27,-41.25]},"NZPMR":{"name":"Palmerston North","country":"New Zealand","coordinates":[175.608215,-40.352306]},"NZPOE":{"name":"Port Chalmers","country":"New Zealand","coordinates":[170.6,-45.82]},"NZTIU":{"name":"Timaru","country":"New Zealand","coordinates":[171.25,-44.38]},"NZTKA":{"name":"Temuka","country":"New Zealand","coordinates":[171.27,-44.25]},"NZTRG":{"name":"Tauranga","country":"New Zealand","coordinates":[176.17,-37.68]},"NZWAG":{"name":"Wanganui","country":"New Zealand","coordinates":[174.98,-39.93]},"NZWLG":{"name":"Wellington","country":"New Zealand","coordinates":[174.78,-41.27]},"NZWRE":{"name":"Whangarei","country":"New Zealand","coordinates":[174.48,-35.83]},"OMMCT":{"name":"Muscat","country":"Oman","coordinates":[58.405923,23.58589]},"OMMNQ":{"name":"Mina\' Qabus","country":"Oman","coordinates":[58.57,23.62]},"OMOPQ":{"name":"Port Qaboos","country":"Oman","coordinates":[58.561524,23.627548]},"OMSLL":{"name":"Salalah","country":"Oman","coordinates":[54.106586,17.050668]},"OMSOH":{"name":"Sohar","country":"Oman","coordinates":[56.707514,24.346096]},"PAAML":{"name":"Puerto Armuelles","country":"Panama","coordinates":[-82.928643,8.311765]},"PABLB":{"name":"Balboa","country":"Panama","coordinates":[-79.563154,8.961396]},"PACFZ":{"name":"Colon Free Zone","country":"Panama","coordinates":[-79.9,9.35]},"PACSO":{"name":"Coco Solo","country":"Panama","coordinates":[-79.879913,9.368985]},"PACTB":{"name":"Cristobal","country":"Panama","coordinates":[-79.9,9.35]},"PACTD":{"name":"Chitre","country":"Panama","coordinates":[-80.438215,7.952312]},"PACZZ":{"name":"Canazas","country":"Panama","coordinates":[-80.7,8.13]},"PAMIT":{"name":"Manzanillo","country":"Panama","coordinates":[-81.166667,7.533333]},"PAONX":{"name":"Colon","country":"Panama","coordinates":[-79.87,9.35]},"PAPSA":{"name":"Rodman","country":"Panama","coordinates":[-79.588437,8.959389]},"PAPTY":{"name":"Panama, Ciudad de","country":"Panama","coordinates":[-79.53,8.97]},"PEATI":{"name":"Atico","country":"Peru","coordinates":[-73.60965,-16.228332]},"PECHM":{"name":"Chimbote","country":"Peru","coordinates":[-78.589011,-9.063336]},"PECLL":{"name":"Callao","country":"Peru","coordinates":[-77.125984,-12.050849]},"PEGSM":{"name":"General San Martin","country":"Peru","coordinates":[-76.924254,-12.197591]},"PEHCO":{"name":"Huacho","country":"Peru","coordinates":[-77.610568,-11.122722]},"PEILQ":{"name":"Ilo","country":"Peru","coordinates":[-71.346809,-17.668084]},"PEIQT":{"name":"Iquitos","country":"Peru","coordinates":[-73.251633,-3.743673]},"PELOB":{"name":"Lobitos","country":"Peru","coordinates":[-81.282165,-4.456435]},"PEMRI":{"name":"Matarani","country":"Peru","coordinates":[-72.100143,-17.005406]},"PEPAC":{"name":"Pacasmayo","country":"Peru","coordinates":[-79.565078,-7.404005]},"PEPAI":{"name":"Paita","country":"Peru","coordinates":[-81.096217,-5.093849]},"PEPCH":{"name":"Puerto Chicama","country":"Peru","coordinates":[-79.140742,-7.847995]},"PEPIO":{"name":"Pisco","country":"Peru","coordinates":[-76.18417,-13.713456]},"PESNX":{"name":"San Nicolas","country":"Peru","coordinates":[-77.505327,-6.386708]},"PESUP":{"name":"Supe","country":"Peru","coordinates":[-77.713731,-10.798752]},"PESVY":{"name":"Salaverry","country":"Peru","coordinates":[-78.9769,-8.21412]},"PETYL":{"name":"Talara","country":"Peru","coordinates":[-81.262896,-4.581152]},"PFPPT":{"name":"Papeete","country":"French Polynesia","coordinates":[-149.558476,-17.551625]},"PGLAE":{"name":"Lae","country":"Papua New Guinea","coordinates":[146.999905,-6.715525]},"PGMAG":{"name":"Madang","country":"Papua New Guinea","coordinates":[145.785632,-5.221884]},"PGPOM":{"name":"Port Moresby","country":"Papua New Guinea","coordinates":[147.180267,-9.4438]},"PGRAB":{"name":"Rabaul","country":"Papua New Guinea","coordinates":[152.164461,-4.199965]},"PHCEB":{"name":"Cebu","country":"Philippines","coordinates":[123.885437,10.315699]},"PHCGY":{"name":"Cagayan de Oro, Mindanao","country":"Philippines","coordinates":[124.631898,8.454236]},"PHDVO":{"name":"Davao, Mindanao","country":"Philippines","coordinates":[125.455341,7.190708]},"PHGES":{"name":"General Santos","country":"Philippines","coordinates":[125.15,6.12]},"PHIGN":{"name":"Iligan, Mindanao","country":"Philippines","coordinates":[124.245242,8.228021]},"PHISB":{"name":"Isabela (Basilan)","country":"Philippines","coordinates":[121.969,6.702945]},"PHMLS":{"name":"Magellanes/Masao","country":"Philippines","coordinates":[121.774017,12.879721]},"PHMNI":{"name":"Manicani","country":"Philippines","coordinates":[125.636886,10.995523]},"PHMNL":{"name":"Manila","country":"Philippines","coordinates":[120.98422,14.599512]},"PHMNN":{"name":"Manila North Harbour","country":"Philippines","coordinates":[120.956667,14.616111]},"PHMNS":{"name":"Manila South Harbour","country":"Philippines","coordinates":[120.969167,14.575833]},"PHOZC":{"name":"Ozamis, Mindanao","country":"Philippines","coordinates":[123.77412,8.162411]},"PHPSL":{"name":"Pasaleng, Luzon","country":"Philippines","coordinates":[120.711559,18.164728]},"PHSFS":{"name":"Subic Bay","country":"Philippines","coordinates":[120.239734,14.791058]},"PHZAM":{"name":"Zamboanga","country":"Philippines","coordinates":[122.079027,6.921442]},"PKBQM":{"name":"Karachi-muhammad Bin Qasim","country":"Pakistan","coordinates":[67.015268,24.855007]},"PKKHI":{"name":"Karachi","country":"Pakistan","coordinates":[67.009939,24.861462]},"PLGDN":{"name":"Gdansk","country":"Poland","coordinates":[18.646638,54.352025]},"PLGDY":{"name":"Gdynia","country":"Poland","coordinates":[18.55,54.5]},"PLSWI":{"name":"Swinoujscie","country":"Poland","coordinates":[14.25,53.9]},"PLSZZ":{"name":"Szczecin","country":"Poland","coordinates":[14.552812,53.428544]},"PRBQN":{"name":"Aguadilla","country":"Puerto Rico","coordinates":[-67.15407,18.427445]},"PRMAZ":{"name":"Mayaguez","country":"Puerto Rico","coordinates":[-67.145152,18.201346]},"PRPNU":{"name":"Puerto Nuevo","country":"Puerto Rico","coordinates":[-66.082737,18.417318]},"PRPSE":{"name":"Ponce","country":"Puerto Rico","coordinates":[-66.614062,18.011077]},"PRSJU":{"name":"San Juan","country":"Puerto Rico","coordinates":[-66.08,18.45]},"PTADH":{"name":"Angra do Heroismo","country":"Portugal","coordinates":[-27.22,38.65]},"PTCNL":{"name":"Canical","country":"Portugal","coordinates":[-16.73,32.73]},"PTENT":{"name":"Entroncamento","country":"Portugal","coordinates":[-8.47,39.47]},"PTFDF":{"name":"Figueira da Foz","country":"Portugal","coordinates":[-8.87,40.15]},"PTFNC":{"name":"Funchal, Madeira","country":"Portugal","coordinates":[-16.9,32.63]},"PTHOR":{"name":"Horta","country":"Portugal","coordinates":[-28.63,38.53]},"PTLEI":{"name":"Leixoes","country":"Portugal","coordinates":[-8.68,41.18]},"PTLIS":{"name":"Lisboa","country":"Portugal","coordinates":[-9.13,38.72]},"PTOPO":{"name":"Porto","country":"Portugal","coordinates":[-8.62,41.15]},"PTPDL":{"name":"Ponta Delgada","country":"Portugal","coordinates":[-25.67,37.73]},"PTPRV":{"name":"Praia da Vitoria","country":"Portugal","coordinates":[-27.07,38.73]},"PTSET":{"name":"Setubal","country":"Portugal","coordinates":[-8.9,38.53]},"PTSIE":{"name":"Sines","country":"Portugal","coordinates":[-8.87,37.95]},"PTTER":{"name":"Terceira Island Apt","country":"Portugal","coordinates":[-27.22,38.72]},"PTVDC":{"name":"Viana do Castelo","country":"Portugal","coordinates":[-8.83,41.7]},"PYASU":{"name":"Asuncion","country":"Paraguay","coordinates":[-57.67,-25.27]},"PYBCM":{"name":"Barrio Caacupe Mi","country":"Paraguay","coordinates":[-57.55,-25.22]},"PYFNX":{"name":"Fenix","country":"Paraguay","coordinates":[-57.575926,-25.26374]},"PYPIL":{"name":"Pilar","country":"Paraguay","coordinates":[-58.291855,-26.863426]},"PYPSE":{"name":"Pto Seguro/col Mariano Roque Alonso","country":"Paraguay","coordinates":[-57.55,-25.166667]},"PYSAN":{"name":"San Antonio","country":"Paraguay","coordinates":[-57.547614,-25.420006]},"PYTER":{"name":"Terport (San Antonio)","country":"Paraguay","coordinates":[-57.63,-25.38]},"QADOH":{"name":"Doha","country":"Qatar","coordinates":[51.493985,25.352144]},"QAMES":{"name":"Mesaieed","country":"Qatar","coordinates":[51.92,25.63]},"QAUMS":{"name":"Umm Sa\'id (Mesaieed)","country":"Qatar","coordinates":[51.53,24.98]},"RELPT":{"name":"Le Port","country":"Réunion","coordinates":[55.3,-20.92]},"REPDG":{"name":"Port de Pointe des Galets","country":"Réunion","coordinates":[-20.923917,55.285606]},"REREU":{"name":"Reunion","country":"Réunion","coordinates":[55.53,-20.88]},"RERUN":{"name":"Saint-Denis de la Reunion Apt","country":"Réunion","coordinates":[55.53,-20.88]},"ROAGI":{"name":"Agigea","country":"Romania","coordinates":[28.62,44.08]},"ROBRA":{"name":"Braila","country":"Romania","coordinates":[27.98,45.27]},"ROCND":{"name":"Constanta","country":"Romania","coordinates":[28.65,44.18]},"ROGAL":{"name":"Galati","country":"Romania","coordinates":[28.05,45.43]},"ROMAG":{"name":"Mangalia","country":"Romania","coordinates":[28.57415,43.815244]},"RUARH":{"name":"Arkhangelsk","country":"Russian Federation","coordinates":[40.560155,64.547251]},"RUGDX":{"name":"Magadan","country":"Russian Federation","coordinates":[150.830141,59.561153]},"RUKGD":{"name":"Kaliningrad","country":"Russian Federation","coordinates":[20.452214,54.710426]},"RUKHO":{"name":"Kholmsk","country":"Russian Federation","coordinates":[142.05,47.05]},"RUKOR":{"name":"Korsakov","country":"Russian Federation","coordinates":[142.784188,46.63538]},"RUKRR":{"name":"Krasnodar","country":"Russian Federation","coordinates":[38.987221,45.039267]},"RULED":{"name":"Saint Petersburg (ex Leningrad)","country":"Russian Federation","coordinates":[30.25,59.88]},"RUMMK":{"name":"Murmansk","country":"Russian Federation","coordinates":[33.08266,68.958524]},"RUNJK":{"name":"Nakhodka","country":"Russian Federation","coordinates":[132.883404,42.822275]},"RUNVS":{"name":"Novorossiysk","country":"Russian Federation","coordinates":[37.77,44.72]},"RUPTP":{"name":"Potapova","country":"Russian Federation","coordinates":[103.948625,56.132142]},"RUROV":{"name":"Rostov","country":"Russian Federation","coordinates":[39.701505,47.235714]},"RUTAG":{"name":"Taganrog","country":"Russian Federation","coordinates":[38.867601,47.241633]},"RUVVO":{"name":"Vladivostok","country":"Russian Federation","coordinates":[132.006451,43.173739]},"RUVYG":{"name":"Vyborg","country":"Russian Federation","coordinates":[28.757157,60.713953]},"RUVYP":{"name":"Vostochnyy Port","country":"Russian Federation","coordinates":[133.05,42.77]},"RWKGL":{"name":"Kigali","country":"Rwanda","coordinates":[30.112735,-1.957875]},"SADMM":{"name":"Ad Dammam","country":"Saudi Arabia","coordinates":[50.1,26.42]},"SADMN":{"name":"Dammam","country":"Saudi Arabia","coordinates":[49.977714,26.392667]},"SAJED":{"name":"Jeddah","country":"Saudi Arabia","coordinates":[39.237551,21.285407]},"SAJUB":{"name":"Jubail","country":"Saudi Arabia","coordinates":[49.568742,26.959771]},"SAKAC":{"name":"King Abdullah City","country":"Saudi Arabia","coordinates":[39.08,22.4]},"SARUH":{"name":"Riyadh","country":"Saudi Arabia","coordinates":[46.902838,24.749403]},"SBHIR":{"name":"Honiara, Guadalcanal Is","country":"Solomon Islands","coordinates":[159.9729,-9.445638]},"SBNOR":{"name":"Noro, New Georgia","country":"Solomon Islands","coordinates":[157.199876,-8.240987]},"SCMAW":{"name":"Mahe","country":"Seychelles","coordinates":[55.480396,-4.682669]},"SCPOV":{"name":"Port Victoria","country":"Seychelles","coordinates":[55.483333,-4.616667]},"SDPZU":{"name":"Port Sudan","country":"Sudan","coordinates":[37.190162,19.590347]},"SEAHU":{"name":"Ahus","country":"Sweden","coordinates":[14.297995,55.925039]},"SEFAG":{"name":"Falkenberg","country":"Sweden","coordinates":[12.488801,56.902733]},"SEGOT":{"name":"Goteborg","country":"Sweden","coordinates":[11.97,57.72]},"SEGVX":{"name":"Gavle","country":"Sweden","coordinates":[17.141273,60.67488]},"SEHAD":{"name":"Halmstad","country":"Sweden","coordinates":[12.857788,56.674375]},"SEHEL":{"name":"Helsingborg","country":"Sweden","coordinates":[12.694512,56.046467]},"SEKAN":{"name":"Karlshamn","country":"Sweden","coordinates":[14.863073,56.170303]},"SEKSD":{"name":"Karlstad","country":"Sweden","coordinates":[13.511498,59.402181]},"SELAA":{"name":"Landskrona","country":"Sweden","coordinates":[12.83008,55.870348]},"SEMMA":{"name":"Malmo","country":"Sweden","coordinates":[13,55.6]},"SENRK":{"name":"Norrkoping","country":"Sweden","coordinates":[16.192421,58.587745]},"SEOSK":{"name":"Oskarshamn","country":"Sweden","coordinates":[16.447398,57.265699]},"SEOXE":{"name":"Oxelosund","country":"Sweden","coordinates":[17.103733,58.670174]},"SESDL":{"name":"Sundsvall","country":"Sweden","coordinates":[17.3,62.38]},"SESFT":{"name":"Skelleftea","country":"Sweden","coordinates":[20.950917,64.750244]},"SESKE":{"name":"Skelleftehamn","country":"Sweden","coordinates":[21.236126,64.686103]},"SESOE":{"name":"Sodertalje","country":"Sweden","coordinates":[17.62,59.2]},"SESTO":{"name":"Stockholm","country":"Sweden","coordinates":[18.068581,59.329323]},"SETRG":{"name":"Trelleborg","country":"Sweden","coordinates":[13.157423,55.376243]},"SEUDD":{"name":"Uddevalla","country":"Sweden","coordinates":[11.935649,58.3498]},"SEUME":{"name":"Umea","country":"Sweden","coordinates":[20.25,63.83]},"SEVST":{"name":"Vasteras","country":"Sweden","coordinates":[16.544809,59.6099]},"SGJUR":{"name":"Jurong/Singapore","country":"Singapore","coordinates":[103.7,1.33]},"SGSIN":{"name":"Singapore","country":"Singapore","coordinates":[103.850949,1.280095]},"SIKOP":{"name":"Koper","country":"Slovenia","coordinates":[13.730188,45.548059]},"SKBTS":{"name":"Bratislava","country":"Slovakia","coordinates":[17.12,48.15]},"SLFNA":{"name":"Freetown","country":"Sierra Leone","coordinates":[-13.231722,8.465677]},"SNDKR":{"name":"Dakar","country":"Senegal","coordinates":[-17.366029,14.764504]},"SOBBO":{"name":"Berbera","country":"Somalia","coordinates":[45.01399,10.434794]},"SOKMU":{"name":"Kismayu","country":"Somalia","coordinates":[42.546057,-0.356045]},"SOMGQ":{"name":"Mogadishu","country":"Somalia","coordinates":[45.318162,2.046934]},"SRPBM":{"name":"Paramaribo","country":"Suriname","coordinates":[-55.203828,5.852036]},"STTMS":{"name":"Sao Tome Island","country":"Sao Tome and Principe","coordinates":[6.621061,0.260257]},"SVAQJ":{"name":"Acajutla","country":"El Salvador","coordinates":[-89.827089,13.5957]},"SVLLD":{"name":"La Libertad","country":"El Salvador","coordinates":[-89.32,13.48]},"SVLUN":{"name":"La Union","country":"El Salvador","coordinates":[-87.850064,13.332346]},"SVSAL":{"name":"San Salvador","country":"El Salvador","coordinates":[-89.218191,13.69294]},"SYDAM":{"name":"Damascus (Damas)","country":"Syrian Arab Republic","coordinates":[36.276528,33.513807]},"SYLTK":{"name":"Latakia","country":"Syrian Arab Republic","coordinates":[35.78,35.52]},"SYTTS":{"name":"Tartus","country":"Syrian Arab Republic","coordinates":[35.9,34.9]},"TCPLS":{"name":"Providenciales","country":"Turks and Caicos Islands","coordinates":[-72.271908,21.773765]},"TGLFW":{"name":"Lome","country":"Togo","coordinates":[1.231362,6.172497]},"THBGP":{"name":"Bangpakong","country":"Thailand","coordinates":[100.654172,13.725517]},"THBKK":{"name":"Bangkok","country":"Thailand","coordinates":[100.52,13.75]},"THBMT":{"name":"Bangkok Modern Terminals/Bangkok","country":"Thailand","coordinates":[100.58,13.6]},"THKSI":{"name":"Koh Sichang","country":"Thailand","coordinates":[100.808468,13.149915]},"THKTY":{"name":"Khlong Toei","country":"Thailand","coordinates":[100.57,13.72]},"THLCH":{"name":"Laem Chabang","country":"Thailand","coordinates":[100.88,13.08]},"THMAT":{"name":"Mab Tapud","country":"Thailand","coordinates":[100.531365,13.693503]},"THPAT":{"name":"Pat Bangkok","country":"Thailand","coordinates":[100.57,13.7]},"THPKN":{"name":"Paknam","country":"Thailand","coordinates":[103.167505,15.905038]},"THSBP":{"name":"Siam Bangkok Port","country":"Thailand","coordinates":[100.55,13.65]},"THSGZ":{"name":"Songkhla","country":"Thailand","coordinates":[100.6,7.2]},"THSPR":{"name":"Samut Prakarn","country":"Thailand","coordinates":[100.599832,13.599096]},"THSRI":{"name":"Sriracha","country":"Thailand","coordinates":[100.87508,13.133661]},"TNGAE":{"name":"Gabes","country":"Tunisia","coordinates":[10.12,33.88]},"TNJAR":{"name":"Jarjis","country":"Tunisia","coordinates":[-11.1,33.5]},"TNLGN":{"name":"La Goulette Nord (Halqueloued)","country":"Tunisia","coordinates":[10.302084,36.82002]},"TNRDS":{"name":"Rades","country":"Tunisia","coordinates":[10.27,36.77]},"TNSFA":{"name":"Sfax","country":"Tunisia","coordinates":[10.766163,34.747847]},"TNSUS":{"name":"Sousse","country":"Tunisia","coordinates":[10.608395,35.825603]},"TNTUN":{"name":"Tunis","country":"Tunisia","coordinates":[10.181532,36.806495]},"TOTBU":{"name":"Nuku\'alofa","country":"Tonga","coordinates":[-175.2,-21.13]},"TRALI":{"name":"Aliaga","country":"Turkey","coordinates":[26.97074,38.799561]},"TRAMB":{"name":"Ambarli","country":"Turkey","coordinates":[41.68,41.32]},"TRAVC":{"name":"Avcilar","country":"Turkey","coordinates":[28.7,40.97]},"TRAYT":{"name":"Antalya","country":"Turkey","coordinates":[30.713323,36.896891]},"TRBAY":{"name":"Bayrampasa","country":"Turkey","coordinates":[28.900455,41.04815]},"TRBDM":{"name":"Bandirma","country":"Turkey","coordinates":[27.97,40.35]},"TREYP":{"name":"Evyap Port /Kocaeli","country":"Turkey","coordinates":[29.7,40.77]},"TRGEB":{"name":"Gebze","country":"Turkey","coordinates":[29.42,40.78]},"TRGEM":{"name":"Gemlik","country":"Turkey","coordinates":[29.155416,40.43182]},"TRHAY":{"name":"Haydarpasa","country":"Turkey","coordinates":[29.011395,41.00463]},"TRHER":{"name":"Hereke","country":"Turkey","coordinates":[29.626024,40.793768]},"TRISK":{"name":"Iskenderun","country":"Turkey","coordinates":[36.175616,36.584673]},"TRIST":{"name":"Istanbul","country":"Turkey","coordinates":[28.978359,41.008238]},"TRIZM":{"name":"Izmir","country":"Turkey","coordinates":[27.15,38.42]},"TRIZT":{"name":"Izmit","country":"Turkey","coordinates":[29.95,40.78]},"TRKMX":{"name":"Kumport","country":"Turkey","coordinates":[28.82,40.95]},"TRMAD":{"name":"Mardas","country":"Turkey","coordinates":[28.95,41.02]},"TRMAR":{"name":"Marmara Ereglisi","country":"Turkey","coordinates":[27.901465,41.041446]},"TRMER":{"name":"Mersin","country":"Turkey","coordinates":[34.63,36.72]},"TRNEM":{"name":"Nemrut Bay","country":"Turkey","coordinates":[27.142826,38.423734]},"TRSSX":{"name":"Samsun","country":"Turkey","coordinates":[36.336067,41.279703]},"TRTEK":{"name":"Tekirdag","country":"Turkey","coordinates":[27.511674,40.978092]},"TRTZX":{"name":"Trabzon","country":"Turkey","coordinates":[39.716763,41.002697]},"TRYAR":{"name":"Yarimca","country":"Turkey","coordinates":[29.727171,40.771592]},"TTPOS":{"name":"Port-of-Spain","country":"Trinidad and Tobago","coordinates":[-61.52,10.65]},"TTPTP":{"name":"Pointe a Pierre","country":"Trinidad and Tobago","coordinates":[-61.459599,10.31554]},"TTPTS":{"name":"Point Lisas","country":"Trinidad and Tobago","coordinates":[-61.463065,10.405699]},"TTSFE":{"name":"San Fernando","country":"Trinidad and Tobago","coordinates":[-61.449384,10.290585]},"TWKEL":{"name":"Keelung (Chilung)","country":"Taiwan","coordinates":[121.75,25.133333]},"TWKHH":{"name":"Kaohsiung","country":"Taiwan","coordinates":[120.301435,22.627278]},"TWMLI":{"name":"Mai-liao","country":"Taiwan","coordinates":[120.13,23.1]},"TWSUO":{"name":"Suao","country":"Taiwan","coordinates":[121.853326,24.594229]},"TWTXG":{"name":"Taichung","country":"Taiwan","coordinates":[120.673648,24.147736]},"TWTYN":{"name":"Taoyuan","country":"Taiwan","coordinates":[121.30098,24.993628]},"TZDAR":{"name":"Dar es Salaam","country":"Tanzania, United Republic of","coordinates":[39.28,-6.8]},"TZTGT":{"name":"Tanga","country":"Tanzania, United Republic of","coordinates":[39.102323,-5.088875]},"TZZNZ":{"name":"Zanzibar","country":"Tanzania, United Republic of","coordinates":[39.202641,-6.165917]},"UAIEV":{"name":"Kiev","country":"Ukraine","coordinates":[30.5234,50.4501]},"UAILK":{"name":"Illichivs\'k","country":"Ukraine","coordinates":[30.67,46.3]},"UAKHE":{"name":"Kherson","country":"Ukraine","coordinates":[32.616867,46.635417]},"UAMPW":{"name":"Mariupol (ex Zhdanov)","country":"Ukraine","coordinates":[37.55,47.1]},"UAODS":{"name":"Odessa","country":"Ukraine","coordinates":[30.72331,46.482526]},"UASVP":{"name":"Sevastopol","country":"Ukraine","coordinates":[33.53,44.6]},"UGJIN":{"name":"Jinja","country":"Uganda","coordinates":[33.202612,0.447857]},"UGKLA":{"name":"Kampala","country":"Uganda","coordinates":[32.58252,0.347596]},"USADQ":{"name":"Kodiak","country":"United States","coordinates":[-152.407222,57.79]},"USANC":{"name":"Anchorage","country":"United States","coordinates":[-149.900278,61.218056]},"USANP":{"name":"Annapolis","country":"United States","coordinates":[-76.492183,38.978445]},"USAR5":{"name":"Damascus","country":"United States","coordinates":[-92.4096,35.367302]},"USATL":{"name":"Atlanta","country":"United States","coordinates":[-84.387982,33.748995]},"USBAL":{"name":"Baltimore","country":"United States","coordinates":[-76.62,39.28]},"USBCC":{"name":"Bear Creek","country":"United States","coordinates":[-149.395,60.164167]},"USBNA":{"name":"Nashville","country":"United States","coordinates":[-86.781602,36.162664]},"USBOS":{"name":"Boston","country":"United States","coordinates":[-71.05888,42.360083]},"USBPT":{"name":"Beaumont","country":"United States","coordinates":[-94.126556,30.080174]},"USBRO":{"name":"Brownsville","country":"United States","coordinates":[-97.48,25.9]},"USBUF":{"name":"Buffalo","country":"United States","coordinates":[-78.878369,42.886447]},"USCHA":{"name":"Chattanooga","country":"United States","coordinates":[-85.3,35.03]},"USCHI":{"name":"Chicago","country":"United States","coordinates":[-87.629798,41.878114]},"USCHS":{"name":"Charleston","country":"United States","coordinates":[-79.931051,32.776475]},"USCLE":{"name":"Cleveland","country":"United States","coordinates":[-81.67,41.5]},"USCLM":{"name":"Port Angeles","country":"United States","coordinates":[-123.430741,48.118146]},"USCLT":{"name":"Charlotte","country":"United States","coordinates":[-80.843127,35.227087]},"USCMH":{"name":"Columbus","country":"United States","coordinates":[-82.998794,39.961176]},"USCPV":{"name":"Cape Canaveral","country":"United States","coordinates":[-80.607713,28.392218]},"USCRP":{"name":"Corpus Christi","country":"United States","coordinates":[-97.396381,27.800583]},"USCVG":{"name":"Cincinnati","country":"United States","coordinates":[-84.51202,39.103118]},"USCZF":{"name":"Cape Romanzof","country":"United States","coordinates":[-166.097778,61.812222]},"USDA8":{"name":"Dauphin","country":"United States","coordinates":[-76.931086,40.367309]},"USDAL":{"name":"Dallas","country":"United States","coordinates":[-96.796988,32.776664]},"USDEC":{"name":"Decatur","country":"United States","coordinates":[-88.9548,39.840315]},"USDEN":{"name":"Denver","country":"United States","coordinates":[-104.990251,39.739236]},"USDET":{"name":"Detroit","country":"United States","coordinates":[-83.03,42.32]},"USDNN":{"name":"Dalton","country":"United States","coordinates":[-84.970223,34.769802]},"USDSM":{"name":"Des Moines","country":"United States","coordinates":[-93.609106,41.600545]},"USDUT":{"name":"Dutch Harbor","country":"United States","coordinates":[-166.5422,53.8898]},"USELP":{"name":"El Paso","country":"United States","coordinates":[-106.48,31.75]},"USENA":{"name":"Kenai","country":"United States","coordinates":[-151.258333,60.554444]},"USEWR":{"name":"Newark","country":"United States","coordinates":[-74.17,40.73]},"USFEB":{"name":"Fernandina Beach","country":"United States","coordinates":[-81.45,30.67]},"USFLL":{"name":"Fort Lauderdale","country":"United States","coordinates":[-80.13,26.12]},"USFPO":{"name":"Freeport","country":"United States","coordinates":[-95.359662,28.954137]},"USFRP":{"name":"Fresh Water Bay","country":"United States","coordinates":[-135.016037,57.874199]},"USFWT":{"name":"Fort Worth","country":"United States","coordinates":[-97.330766,32.755488]},"USGLS":{"name":"Galveston","country":"United States","coordinates":[-94.797696,29.301348]},"USGPT":{"name":"Gulfport","country":"United States","coordinates":[-89.092815,30.36742]},"USHNL":{"name":"Honolulu","country":"United States","coordinates":[-157.85,21.3]},"USHOU":{"name":"Houston","country":"United States","coordinates":[-95.35,29.75]},"USHSV":{"name":"Huntsville","country":"United States","coordinates":[-86.586104,34.730369]},"USILM":{"name":"Wilmington","country":"United States","coordinates":[-77.93,34.22]},"USIND":{"name":"Indianapolis","country":"United States","coordinates":[-86.15,39.77]},"USJ3C":{"name":"Jacksonville","country":"United States","coordinates":[-122.966986,42.313458]},"USJAX":{"name":"Jacksonville","country":"United States","coordinates":[-81.655651,30.332184]},"USKCK":{"name":"Kansas City","country":"United States","coordinates":[-94.627464,39.114053]},"USLAS":{"name":"Las Vegas","country":"United States","coordinates":[-115.15,36.07]},"USLAT":{"name":"Los Alamitos","country":"United States","coordinates":[-118.072564,33.803072]},"USLAX":{"name":"Los Angeles","country":"United States","coordinates":[-118.243685,34.052234]},"USLCH":{"name":"Lake Charles","country":"United States","coordinates":[-93.217376,30.226595]},"USLGB":{"name":"Long Beach","country":"United States","coordinates":[-118.18,33.77]},"USLRD":{"name":"Laredo","country":"United States","coordinates":[-99.48,27.5]},"USLUI":{"name":"Louisville","country":"United States","coordinates":[-85.77,38.23]},"USMEM":{"name":"Memphis","country":"United States","coordinates":[-90.04898,35.149534]},"USMES":{"name":"Minneapolis","country":"United States","coordinates":[-93.265011,44.977753]},"USMIA":{"name":"Miami","country":"United States","coordinates":[-80.19179,25.76168]},"USMKC":{"name":"Kansas City","country":"United States","coordinates":[-94.57,39.1]},"USMKE":{"name":"Milwaukee","country":"United States","coordinates":[-87.906474,43.038903]},"USMOB":{"name":"Mobile","country":"United States","coordinates":[-88.039891,30.695366]},"USMRH":{"name":"Morehead City","country":"United States","coordinates":[-76.726044,34.722939]},"USMSP":{"name":"Minneapolis/St Paul Apt","country":"United States","coordinates":[-93.2,44.88]},"USMSY":{"name":"New Orleans","country":"United States","coordinates":[-90.071532,29.951066]},"USNNS":{"name":"Newport News","country":"United States","coordinates":[-76.42,36.97]},"USNTD":{"name":"Port Hueneme","country":"United States","coordinates":[-119.18,34.13]},"USNTS":{"name":"North Charleston","country":"United States","coordinates":[-79.97481,32.85462]},"USNYC":{"name":"New York","country":"United States","coordinates":[-74,40.7]},"USOAK":{"name":"Oakland","country":"United States","coordinates":[-122.271114,37.804364]},"USOCN":{"name":"Oceanside","country":"United States","coordinates":[-117.379483,33.19587]},"USOCW":{"name":"Washington","country":"United States","coordinates":[-77.052174,35.546552]},"USOLM":{"name":"Olympia","country":"United States","coordinates":[-122.900695,47.037874]},"USOMA":{"name":"Omaha","country":"United States","coordinates":[-95.997988,41.252363]},"USORF":{"name":"Norfolk","country":"United States","coordinates":[-76.285873,36.850769]},"USORL":{"name":"Orlando","country":"United States","coordinates":[-81.37,28.53]},"USOYM":{"name":"Otay Mesa","country":"United States","coordinates":[-117.02,32.57]},"USPAB":{"name":"Palm Beach","country":"United States","coordinates":[-80.03643,26.705621]},"USPCA":{"name":"Portage Creek","country":"United States","coordinates":[-157.7175,58.906111]},"USPCV":{"name":"Port Canaveral","country":"United States","coordinates":[-80.36,28.24]},"USPDX":{"name":"Portland","country":"United States","coordinates":[-122.676482,45.523062]},"USPEB":{"name":"Port Elizabeth","country":"United States","coordinates":[-74.97,39.3]},"USPEF":{"name":"Port Everglades","country":"United States","coordinates":[-80.13,26.1]},"USPFN":{"name":"Panama City","country":"United States","coordinates":[-85.660206,30.158813]},"USPGL":{"name":"Pascagoula","country":"United States","coordinates":[-88.556127,30.365755]},"USPHF":{"name":"Hampton-Williamsburg-Newport News Apt","country":"United States","coordinates":[-76.48,37.12]},"USPHL":{"name":"Philadelphia","country":"United States","coordinates":[-75.165221,39.952584]},"USPHX":{"name":"Phoenix","country":"United States","coordinates":[-112.074037,33.448377]},"USPIA":{"name":"Peoria","country":"United States","coordinates":[-89.588986,40.693649]},"USPIT":{"name":"Pittsburgh","country":"United States","coordinates":[-79.995886,40.440625]},"USPME":{"name":"Port Manatee","country":"United States","coordinates":[-82.55,27.63]},"USPNS":{"name":"Pensacola","country":"United States","coordinates":[-87.216915,30.421309]},"USPOA":{"name":"Port Arthur","country":"United States","coordinates":[-93.92,29.88]},"USPPS":{"name":"Pennsauken","country":"United States","coordinates":[-75.05,39.95]},"USPS6":{"name":"Pearson","country":"United States","coordinates":[-89.016502,45.364131]},"USPTM":{"name":"Portsmouth","country":"United States","coordinates":[-76.298274,36.835426]},"USPVD":{"name":"Providence","country":"United States","coordinates":[-71.412834,41.823989]},"USPWM":{"name":"Portland","country":"United States","coordinates":[-70.255326,43.661471]},"USPWN":{"name":"Port Wentworth","country":"United States","coordinates":[-81.163168,32.149092]},"USPYM":{"name":"Plymouth","country":"United States","coordinates":[-70.667262,41.958446]},"USRAJ":{"name":"Rotterdam","country":"United States","coordinates":[-73.970958,42.787021]},"USRBC":{"name":"Riviera Beach","country":"United States","coordinates":[-80.058097,26.775341]},"USRIC":{"name":"Richmond","country":"United States","coordinates":[-77.45,37.55]},"USSAC":{"name":"Sacramento","country":"United States","coordinates":[-121.48,38.57]},"USSAN":{"name":"San Diego","country":"United States","coordinates":[-117.15,32.7]},"USSAV":{"name":"Savannah","country":"United States","coordinates":[-81.099834,32.083541]},"USSCK":{"name":"Stockton","country":"United States","coordinates":[-121.28,37.95]},"USSEA":{"name":"Seattle","country":"United States","coordinates":[-122.332071,47.60621]},"USSFO":{"name":"San Francisco","country":"United States","coordinates":[-122.419415,37.77493]},"USSLC":{"name":"Salt Lake City","country":"United States","coordinates":[-111.891047,40.760779]},"USSPT":{"name":"South Saint Paul","country":"United States","coordinates":[-93.05,44.88]},"USSSI":{"name":"Brunswick","country":"United States","coordinates":[-81.491489,31.149953]},"USSTL":{"name":"Saint Louis","country":"United States","coordinates":[-90.199404,38.627003]},"USSTP":{"name":"Saint Paul","country":"United States","coordinates":[-93.089958,44.953703]},"USSUE":{"name":"Sturgeon Bay","country":"United States","coordinates":[-87.377042,44.834164]},"USSUW":{"name":"Superior","country":"United States","coordinates":[-92.10408,46.720774]},"USSWF":{"name":"Newburgh","country":"United States","coordinates":[-74.010418,41.503427]},"USSXZ":{"name":"Swartz Creek","country":"United States","coordinates":[-83.82,42.95]},"USTIW":{"name":"Tacoma","country":"United States","coordinates":[-122.444291,47.252877]},"USTMM":{"name":"Tamms","country":"United States","coordinates":[-89.270081,37.23505]},"USTOL":{"name":"Toledo","country":"United States","coordinates":[-83.555212,41.663938]},"USTPA":{"name":"Tampa","country":"United States","coordinates":[-82.457178,27.950575]},"USTSA":{"name":"Savannah","country":"United States","coordinates":[-88.249204,35.224803]},"USTUL":{"name":"Tulsa","country":"United States","coordinates":[-95.992775,36.153982]},"USUGN":{"name":"Waukegan","country":"United States","coordinates":[-87.83,42.35]},"USUXZ":{"name":"Port Chester","country":"United States","coordinates":[-73.665683,41.001764]},"USUZF":{"name":"Tonawanda","country":"United States","coordinates":[-78.880315,43.020335]},"USVAB":{"name":"Virginia Beach","country":"United States","coordinates":[-75.977985,36.852926]},"USXGO":{"name":"Howard Beach/Queens/New York","country":"United States","coordinates":[-73.83,40.65]},"USXHF":{"name":"Kirkland","country":"United States","coordinates":[-122.208735,47.681488]},"USXHO":{"name":"Long Island City/Queens/New York","country":"United States","coordinates":[-73.93,40.73]},"USYOB":{"name":"Oakboro","country":"United States","coordinates":[-80.328952,35.225701]},"UYCYR":{"name":"Colonia","country":"Uruguay","coordinates":[-55.765835,-32.522779]},"UYMVD":{"name":"Montevideo","country":"Uruguay","coordinates":[-56.164531,-34.901113]},"UYNVP":{"name":"Nueva Palmira","country":"Uruguay","coordinates":[-58.400216,-33.877469]},"UYPDU":{"name":"Paysandu","country":"Uruguay","coordinates":[-57.462726,-32.221659]},"VCKTN":{"name":"Kingstown","country":"Saint Vincent and the Grenadines","coordinates":[-61.22,13.13]},"VEAMY":{"name":"Amuay","country":"Venezuela","coordinates":[-70.242321,11.776897]},"VEBLA":{"name":"Barcelona","country":"Venezuela","coordinates":[-64.67768,10.144569]},"VEEGU":{"name":"El Guamache","country":"Venezuela","coordinates":[-64.066505,10.895896]},"VEETV":{"name":"El Tablazo/Maracaibo L","country":"Venezuela","coordinates":[-71.641934,10.596956]},"VEGUB":{"name":"Guaranao Bay","country":"Venezuela","coordinates":[11.665667,-70.214782]},"VEGUT":{"name":"Guanta","country":"Venezuela","coordinates":[-64.591805,10.234369]},"VELAG":{"name":"La Guaira","country":"Venezuela","coordinates":[-66.921363,10.587508]},"VELSV":{"name":"La Salina/Maracaibo L","country":"Venezuela","coordinates":[-71.677718,10.715674]},"VEMAR":{"name":"Maracaibo","country":"Venezuela","coordinates":[-71.714795,10.654451]},"VEMTV":{"name":"Matanzas","country":"Venezuela","coordinates":[-62.8,8.316667]},"VEPBL":{"name":"Puerto Cabello","country":"Venezuela","coordinates":[-68.029865,10.469769]},"VEPCN":{"name":"Punta Cardon","country":"Venezuela","coordinates":[-70.22,11.65]},"VEPSU":{"name":"Puerto Sucre","country":"Venezuela","coordinates":[-68.383333,8.966667]},"VEPZO":{"name":"Puerto Ordaz","country":"Venezuela","coordinates":[-62.767663,8.275092]},"VGRAD":{"name":"Road Town, Tortola","country":"Virgin Islands, British","coordinates":[-64.614122,18.422995]},"VGTOV":{"name":"Tortola","country":"Virgin Islands, British","coordinates":[-64.614122,18.422995]},"VICHA":{"name":"Charlotte Amalie, Saint Thomas","country":"Virgin Islands, U.S.","coordinates":[-64.930701,18.3419]},"VICTD":{"name":"Christiansted, Saint Croix","country":"Virgin Islands, U.S.","coordinates":[-64.703198,17.74664]},"VISTT":{"name":"Saint Thomas","country":"Virgin Islands, U.S.","coordinates":[-64.93,18.35]},"VISTX":{"name":"Saint Croix","country":"Virgin Islands, U.S.","coordinates":[-64.73,17.75]},"VNCLI":{"name":"Cat Lai","country":"Viet Nam","coordinates":[106.78,10.77]},"VNCLN":{"name":"Cai Lan","country":"Viet Nam","coordinates":[107.05,21.42]},"VNDAD":{"name":"Da Nang","country":"Viet Nam","coordinates":[108.202167,16.054407]},"VNHPH":{"name":"Haiphong","country":"Viet Nam","coordinates":[106.688084,20.844912]},"VNNHA":{"name":"Nha Trang","country":"Viet Nam","coordinates":[109.196749,12.238791]},"VNPHG":{"name":"Phuoc Long","country":"Viet Nam","coordinates":[106.95,10.72]},"VNSGN":{"name":"Ho Chi Minh City","country":"Viet Nam","coordinates":[106.67,10.77]},"VNSTD":{"name":"Su Tu Den","country":"Viet Nam","coordinates":[108.261478,10.98046]},"VNTOT":{"name":"Cai Mep","country":"Viet Nam","coordinates":[107.155151,10.585022]},"VNTTD":{"name":"Tan Thuan Dong","country":"Viet Nam","coordinates":[106.72,10.73]},"VNUIH":{"name":"Qui Nhon","country":"Viet Nam","coordinates":[109.22,13.77]},"VNVIC":{"name":"Ho Chi Minh, VICT","country":"Viet Nam","coordinates":[106.67215,10.796011]},"VNVUT":{"name":"Vung Tau","country":"Viet Nam","coordinates":[107.07,10.35]},"VUSON":{"name":"Espiritu Santo","country":"Vanuatu","coordinates":[166.91821,-15.300355]},"VUVLI":{"name":"Port Vila","country":"Vanuatu","coordinates":[168.327325,-17.733251]},"WSAPW":{"name":"Apia","country":"Samoa","coordinates":[-171.751355,-13.850696]},"YEADE":{"name":"Aden","country":"Yemen","coordinates":[45.018655,12.785497]},"YEHOD":{"name":"Hodeidah","country":"Yemen","coordinates":[42.9,14.83]},"YELAD":{"name":"Little Aden","country":"Yemen","coordinates":[44.87,12.73]},"YEMKX":{"name":"Mukalla","country":"Yemen","coordinates":[49.127197,14.540433]},"YTLON":{"name":"Longoni","country":"Mayotte","coordinates":[45.17,-12.75]},"ZABFN":{"name":"Bloemfontein","country":"South Africa","coordinates":[26.159576,-29.085214]},"ZACPT":{"name":"Cape Town","country":"South Africa","coordinates":[18.42,-33.92]},"ZADUR":{"name":"Durban","country":"South Africa","coordinates":[31.02184,-29.85868]},"ZAELS":{"name":"East London","country":"South Africa","coordinates":[27.854587,-33.029158]},"ZAJNB":{"name":"Johannesburg","country":"South Africa","coordinates":[28.08,-26.2]},"ZAPLZ":{"name":"Port Elizabeth","country":"South Africa","coordinates":[25.520736,-33.713925]},"ZAPRY":{"name":"Pretoria","country":"South Africa","coordinates":[28.22,-25.7]},"ZARCB":{"name":"Richards Bay","country":"South Africa","coordinates":[32.038286,-28.780728]},"ZAZBA":{"name":"Coega","country":"South Africa","coordinates":[25.67,-33.77]},"ZMLUN":{"name":"Lusaka","country":"Zambia","coordinates":[28.322817,-15.387526]},"ZWBUQ":{"name":"Bulawayo","country":"Zimbabwe","coordinates":[28.626479,-20.132507]},"ZWHRE":{"name":"Harare","country":"Zimbabwe","coordinates":[31.03351,-17.825166]},"ZWUTA":{"name":"Mutare","country":"Zimbabwe","coordinates":[32.650351,-18.975771]}}', +) as Record; + +export default PORTS; diff --git a/src/ports/index.spec.ts b/src/ports/index.spec.ts new file mode 100644 index 0000000..504e278 --- /dev/null +++ b/src/ports/index.spec.ts @@ -0,0 +1,63 @@ +import test from 'ava'; + +// Importing the subpath registers the UN/LOCODE resolver into the core as a +// side effect, enabling seaRoute('CNSHA', 'NLRTM'). +import { lookupPort, PORT_COUNT, PORTS, resolvePort, UnknownPortError } from './index'; +import { clearFinderCache, seaRoute } from '../index'; + +test.beforeEach(() => clearFinderCache()); + +test('bundles a substantial seaport dataset', (t) => { + t.true(PORT_COUNT > 1000, `expected >1000 codes, got ${PORT_COUNT}`); + t.is(PORT_COUNT, Object.keys(PORTS).length); +}); + +test('lookupPort resolves a major port with plausible coordinates', (t) => { + const shanghai = lookupPort('CNSHA'); + t.truthy(shanghai); + t.is(shanghai?.code, 'CNSHA'); + t.is(shanghai?.name, 'Shanghai'); + const [lon, lat] = shanghai!.coordinates; + t.true(lon > 120 && lon < 123, `lon ${lon}`); + t.true(lat > 30 && lat < 32, `lat ${lat}`); +}); + +test('lookupPort is case-insensitive and ignores whitespace', (t) => { + t.deepEqual(lookupPort(' cnsha '), lookupPort('CNSHA')); + t.deepEqual(lookupPort('nl rtm'), lookupPort('NLRTM')); +}); + +test('UN/LOCODE aliases resolve to the same port', (t) => { + // Shanghai carries both CNSHA and CNSGH in the source dataset. + t.deepEqual(lookupPort('CNSGH')?.coordinates, lookupPort('CNSHA')?.coordinates); +}); + +test('lookupPort returns undefined for an unknown code', (t) => { + t.is(lookupPort('ZZZZZ'), undefined); +}); + +test('resolvePort returns [lon, lat] and throws UnknownPortError for unknown codes', (t) => { + const rtm = resolvePort('NLRTM'); + t.is(rtm.length, 2); + t.true(rtm[0] > 3 && rtm[0] < 6); + const err = t.throws(() => resolvePort('ZZZZZ'), { instanceOf: UnknownPortError }); + t.is(err?.code, 'ZZZZZ'); +}); + +test("seaRoute accepts UN/LOCODE strings once 'searoute-ts/ports' is imported", (t) => { + const r = seaRoute('CNSHA', 'NLRTM', { units: 'kilometers' }); + // Shanghai → Rotterdam via Suez ≈ 19 000–21 000 km (matches the coordinate test). + t.true(r.properties.length > 19000 && r.properties.length < 21000, `${r.properties.length} km`); +}); + +test('seaRoute accepts a mix of port code and coordinates', (t) => { + const byCode = seaRoute('CNSHA', 'NLRTM', { units: 'kilometers' }); + const mixed = seaRoute('CNSHA', [4.5, 51.92], { units: 'kilometers' }); + // NLRTM resolves to ~[4.5, 51.92], so the two routes should be identical. + t.is(Math.round(byCode.properties.length), Math.round(mixed.properties.length)); +}); + +test('an unknown port code throws UnknownPortError through seaRoute', (t) => { + const err = t.throws(() => seaRoute('ZZZZZ', 'NLRTM'), { instanceOf: UnknownPortError }); + t.is(err?.code, 'ZZZZZ'); +}); diff --git a/src/ports/index.ts b/src/ports/index.ts new file mode 100644 index 0000000..6ae2b4f --- /dev/null +++ b/src/ports/index.ts @@ -0,0 +1,43 @@ +import { normalizePortCode, registerPortResolver, UnknownPortError } from '../lib/ports.js'; +import PORT_RECORDS, { type PortRecord } from './data.js'; + +export { UnknownPortError } from '../lib/ports.js'; +export type { PortRecord } from './data.js'; + +/** A resolved port: its UN/LOCODE plus name, country and `[lon, lat]`. */ +export type Port = PortRecord & { code: string }; + +/** + * The bundled seaport dataset, keyed by UN/LOCODE. Derived from + * [marchah/sea-ports](https://github.com/marchah/sea-ports) (MIT), itself based + * on UN/LOCODE. Codes include primary locations and their aliases. + */ +export const PORTS: Readonly> = PORT_RECORDS; + +/** Number of UN/LOCODE keys in the bundled dataset. */ +export const PORT_COUNT = Object.keys(PORT_RECORDS).length; + +/** + * Look up a port by UN/LOCODE (case-insensitive, whitespace ignored). + * Returns `undefined` when the code is not in the dataset. + */ +export function lookupPort(code: string): Port | undefined { + const normalized = normalizePortCode(code); + const record = PORT_RECORDS[normalized]; + return record ? { code: normalized, ...record } : undefined; +} + +/** + * Resolve a UN/LOCODE to `[lon, lat]`. + * + * @throws {UnknownPortError} when the code is not in the dataset. + */ +export function resolvePort(code: string): [number, number] { + const record = PORT_RECORDS[normalizePortCode(code)]; + if (!record) throw new UnknownPortError(code, true); + return record.coordinates; +} + +// Enable `seaRoute('CNSHA', 'NLRTM')` in the core: importing this module wires +// the UN/LOCODE resolver into it. This is the module's intended side effect. +registerPortResolver((code) => PORT_RECORDS[code]?.coordinates);