From e6d8ef2bbc24f64fbf6266382e7a88361fbcc553 Mon Sep 17 00:00:00 2001 From: davd-gzl <60177543+davd-gzl@users.noreply.github.com> Date: Wed, 30 Sep 2026 21:28:08 +0900 Subject: [PATCH 1/5] Map: stop a visited Russia or Fiji painting a band across the map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The country outlines come from world-atlas in spherical coordinates, so a ring crossing the antimeridian jumps from +180 to -180 between two vertices and Antarctica's coast wraps the pole. Drawn on a plane, that jump became an edge spanning the whole map. The rings are now unwrapped, closed through the pole when they circle one, and cut at ±180 once when the geometry loads. --- apps/postcards/src/features/map/MapView.tsx | 3 +- .../src/features/map/antimeridian.ts | 106 ++++++++++++++++++ .../postcards/tests/unit/antimeridian.spec.ts | 79 +++++++++++++ 3 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 apps/postcards/src/features/map/antimeridian.ts create mode 100644 apps/postcards/tests/unit/antimeridian.spec.ts diff --git a/apps/postcards/src/features/map/MapView.tsx b/apps/postcards/src/features/map/MapView.tsx index 531abee..45c65a3 100644 --- a/apps/postcards/src/features/map/MapView.tsx +++ b/apps/postcards/src/features/map/MapView.tsx @@ -24,6 +24,7 @@ import { } from "./visitedLayers"; import { prefetchAroundBounds, prefetchAroundPoint, OSM_TILE_TEMPLATE } from "../../lib/offline/tiles"; import { markerCitiesInView, type Bounds } from "./viewport"; +import { splitAntimeridian } from "./antimeridian"; import { statusShows, type FilterStatus } from "../../lib/store/useFilters"; import type { City } from "../../lib/reference/types"; import type { PlaceRef, Visit } from "../../lib/schema/models"; @@ -69,7 +70,7 @@ async function fetchCountries(): Promise 180) off -= 360; + while (lon! + off - prev < -180) off += 360; + } + prev = lon! + off; + out.push([prev, lat!]); + } + // A ring around a pole ends 360° from where it started: close it along the + // pole's latitude so it encloses the cap instead of a sliver. + const first = out[0]!; + const last = out[out.length - 1]!; + if (Math.abs(last[0]! - first[0]!) > 180) { + const poleLat = out.reduce((s, p) => s + p[1]!, 0) < 0 ? -90 : 90; + out.push([last[0]!, poleLat], [first[0]!, poleLat], [first[0]!, first[1]!]); + } + return out; +} + +/** Sutherland-Hodgman against one vertical line; `keepLeft` keeps x <= x0. */ +function clipX(ring: Ring, x0: number, keepLeft: boolean): Ring { + const inside = (p: Position) => (keepLeft ? p[0]! <= x0 : p[0]! >= x0); + const out: Ring = []; + for (let i = 0; i < ring.length; i++) { + const a = ring[i]!; + const b = ring[(i + 1) % ring.length]!; + const ia = inside(a); + const ib = inside(b); + if (ia) out.push(a); + if (ia !== ib) { + const t = (x0 - a[0]!) / (b[0]! - a[0]!); + out.push([x0, a[1]! + t * (b[1]! - a[1]!)]); + } + } + return out; +} + +function area(ring: Ring): number { + let s = 0; + for (let i = 0; i < ring.length; i++) { + const a = ring[i]!; + const b = ring[(i + 1) % ring.length]!; + s += a[0]! * b[1]! - b[0]! * a[1]!; + } + return s / 2; +} + +/** The part of `ring` inside the world copy starting at `west`, shifted into + * [-180, 180] and closed; null when nothing with an area is left. */ +function ringInWorld(ring: Ring, west: number): Ring | null { + const clipped = clipX(clipX(ring, west, false), west + 360, true); + if (clipped.length < 3 || Math.abs(area(clipped)) < 1e-9) return null; + const shift = -180 - west; + const out = clipped.map(([x, y]) => [x! + shift, y!]); + out.push([...out[0]!]); + return out; +} + +/** Split a polygon's rings at the antimeridian; untouched when it does not cross. */ +function splitPolygon(rings: Ring[]): Ring[][] { + const unwrapped = rings.map(unwrap); + let min = Infinity; + let max = -Infinity; + for (const r of unwrapped) + for (const [x] of r) { + if (x! < min) min = x!; + if (x! > max) max = x!; + } + if (min >= -180 && max <= 180 && unwrapped.every((r, i) => r.length === rings[i]!.length)) + return [rings]; + const parts: Ring[][] = []; + for (let west = Math.floor((min + 180) / 360) * 360 - 180; west < max; west += 360) { + const kept = unwrapped.map((r) => ringInWorld(r, west)).filter((r): r is Ring => r !== null); + if (kept.length) parts.push(kept); + } + return parts; +} + +/** Country outlines safe to draw on a plane: no ring crosses ±180°. */ +export function splitAntimeridian>(fc: T): T { + for (const f of fc.features) { + const g = f.geometry; + const polys = g.type === "Polygon" ? [g.coordinates] : g.coordinates; + const split = polys.flatMap(splitPolygon); + if (split.length === polys.length && split.every((p, i) => p === polys[i])) continue; + f.geometry = { type: "MultiPolygon", coordinates: split }; + } + return fc; +} diff --git a/apps/postcards/tests/unit/antimeridian.spec.ts b/apps/postcards/tests/unit/antimeridian.spec.ts new file mode 100644 index 0000000..eb4fae2 --- /dev/null +++ b/apps/postcards/tests/unit/antimeridian.spec.ts @@ -0,0 +1,79 @@ +import { describe, it, expect } from "vitest"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; +import { feature } from "topojson-client"; +import type { FeatureCollection, MultiPolygon, Polygon, Position } from "geojson"; +import { splitAntimeridian } from "../../src/features/map/antimeridian"; + +const here = dirname(fileURLToPath(import.meta.url)); + +function countries(): FeatureCollection { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const topo: any = JSON.parse( + readFileSync(join(here, "..", "..", "public", "basemap", "countries-50m.json"), "utf8"), + ); + return feature(topo, topo.objects.countries) as unknown as FeatureCollection; +} + +function rings(g: Polygon | MultiPolygon): Position[][] { + return g.type === "Polygon" ? g.coordinates : g.coordinates.flat(); +} + +/** Planar area (deg²), the measure MapLibre fills. */ +function area(g: Polygon | MultiPolygon): number { + const polys = g.type === "Polygon" ? [g.coordinates] : g.coordinates; + let total = 0; + for (const p of polys) + p.forEach((r, i) => { + let s = 0; + for (let k = 0; k < r.length - 1; k++) s += r[k]![0]! * r[k + 1]![1]! - r[k + 1]![0]! * r[k]![1]!; + total += (i === 0 ? 1 : -1) * Math.abs(s / 2); + }); + return total; +} + +/** Widest east-west edge, leaving out the one along a pole (off the map). */ +function widestEdge(g: Polygon | MultiPolygon): number { + let w = 0; + for (const r of rings(g)) + for (let k = 1; k < r.length; k++) { + if (Math.abs(r[k]![1]!) === 90 && Math.abs(r[k - 1]![1]!) === 90) continue; + w = Math.max(w, Math.abs(r[k]![0]! - r[k - 1]![0]!)); + } + return w; +} + +describe("map: country shapes across the antimeridian", () => { + const fc = splitAntimeridian(countries()); + const byName = (n: string) => fc.features.find((f) => f.properties?.name === n)!; + + it("leaves no edge spanning the map, so no country paints a band", () => { + for (const f of fc.features) { + expect(widestEdge(f.geometry), f.properties?.name).toBeLessThan(180); + for (const r of rings(f.geometry)) for (const [x] of r) expect(Math.abs(x!)).toBeLessThanOrEqual(180); + } + }); + + it("keeps Russia's and Fiji's land on both sides of 180°", () => { + for (const name of ["Russia", "Fiji"]) { + const xs = rings(byName(name).geometry).flat().map((p) => p[0]!); + expect(Math.min(...xs), name).toBeLessThan(-179); + expect(Math.max(...xs), name).toBeGreaterThan(179); + } + // Chukotka and Wrangel east of 180° stay a sliver, not a map-wide slab. + expect(area(byName("Russia").geometry)).toBeLessThan(3500); + expect(area(byName("Russia").geometry)).toBeGreaterThan(2500); + }); + + it("closes Antarctica through the pole", () => { + const ys = rings(byName("Antarctica").geometry).flat().map((p) => p[1]!); + expect(Math.min(...ys)).toBe(-90); + expect(area(byName("Antarctica").geometry)).toBeGreaterThan(360 * 15); + }); + + it("leaves a country that does not cross untouched", () => { + const before = countries().features.find((f) => f.properties?.name === "France")!; + expect(byName("France").geometry).toEqual(before.geometry); + }); +}); From 5e743ffa03bdeff6b665dd4bea0995aa5e61a14f Mon Sep 17 00:00:00 2001 From: davd-gzl <60177543+davd-gzl@users.noreply.github.com> Date: Wed, 30 Sep 2026 23:53:28 +0900 Subject: [PATCH 2/5] Map: draw no border line down the 180th meridian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cutting the unwrapped country rings at ±180 left an edge on the meridian, which the country outline and the land fill's outline drew as a border through Chukotka, Fiji and Antarctica. The rings are now left whole past 180: MapLibre wraps a shape running off the world into the next copy itself, clipping it outside the tile. Antarctica's outline in world-atlas is a zero-area circle just off the pole with the coast as its hole; that circle is dropped, so the coast becomes the outline, closed through the pole. --- apps/postcards/src/features/map/MapView.tsx | 4 +- .../src/features/map/antimeridian.ts | 94 +++++++------------ ...meridian.spec.ts => antimeridian.spec.tsx} | 57 +++++++---- 3 files changed, 73 insertions(+), 82 deletions(-) rename apps/postcards/tests/unit/{antimeridian.spec.ts => antimeridian.spec.tsx} (54%) diff --git a/apps/postcards/src/features/map/MapView.tsx b/apps/postcards/src/features/map/MapView.tsx index 45c65a3..610741d 100644 --- a/apps/postcards/src/features/map/MapView.tsx +++ b/apps/postcards/src/features/map/MapView.tsx @@ -24,7 +24,7 @@ import { } from "./visitedLayers"; import { prefetchAroundBounds, prefetchAroundPoint, OSM_TILE_TEMPLATE } from "../../lib/offline/tiles"; import { markerCitiesInView, type Bounds } from "./viewport"; -import { splitAntimeridian } from "./antimeridian"; +import { unwrapAntimeridian } from "./antimeridian"; import { statusShows, type FilterStatus } from "../../lib/store/useFilters"; import type { City } from "../../lib/reference/types"; import type { PlaceRef, Visit } from "../../lib/schema/models"; @@ -70,7 +70,7 @@ async function fetchCountries(): Promise 180) { - const poleLat = out.reduce((s, p) => s + p[1]!, 0) < 0 ? -90 : 90; - out.push([last[0]!, poleLat], [first[0]!, poleLat], [first[0]!, first[1]!]); - } - return out; -} - -/** Sutherland-Hodgman against one vertical line; `keepLeft` keeps x <= x0. */ -function clipX(ring: Ring, x0: number, keepLeft: boolean): Ring { - const inside = (p: Position) => (keepLeft ? p[0]! <= x0 : p[0]! >= x0); - const out: Ring = []; - for (let i = 0; i < ring.length; i++) { - const a = ring[i]!; - const b = ring[(i + 1) % ring.length]!; - const ia = inside(a); - const ib = inside(b); - if (ia) out.push(a); - if (ia !== ib) { - const t = (x0 - a[0]!) / (b[0]! - a[0]!); - out.push([x0, a[1]! + t * (b[1]! - a[1]!)]); - } - } return out; } @@ -62,45 +38,41 @@ function area(ring: Ring): number { return s / 2; } -/** The part of `ring` inside the world copy starting at `west`, shifted into - * [-180, 180] and closed; null when nothing with an area is left. */ -function ringInWorld(ring: Ring, west: number): Ring | null { - const clipped = clipX(clipX(ring, west, false), west + 360, true); - if (clipped.length < 3 || Math.abs(area(clipped)) < 1e-9) return null; - const shift = -180 - west; - const out = clipped.map(([x, y]) => [x! + shift, y!]); - out.push([...out[0]!]); - return out; +/** A ring around a pole ends 360° from where it started: close it along the + * pole's latitude so it encloses the cap instead of a sliver. */ +function closePole(ring: Ring): Ring { + const first = ring[0]!; + const last = ring[ring.length - 1]!; + if (Math.abs(last[0]! - first[0]!) <= 180) return ring; + const poleLat = ring.reduce((s, p) => s + p[1]!, 0) < 0 ? -90 : 90; + return [...ring, [last[0]!, poleLat], [first[0]!, poleLat], [first[0]!, first[1]!]]; } -/** Split a polygon's rings at the antimeridian; untouched when it does not cross. */ -function splitPolygon(rings: Ring[]): Ring[][] { - const unwrapped = rings.map(unwrap); - let min = Infinity; - let max = -Infinity; - for (const r of unwrapped) - for (const [x] of r) { - if (x! < min) min = x!; - if (x! > max) max = x!; - } - if (min >= -180 && max <= 180 && unwrapped.every((r, i) => r.length === rings[i]!.length)) - return [rings]; - const parts: Ring[][] = []; - for (let west = Math.floor((min + 180) / 360) * 360 - 180; west < max; west += 360) { - const kept = unwrapped.map((r) => ringInWorld(r, west)).filter((r): r is Ring => r !== null); - if (kept.length) parts.push(kept); - } - return parts; +const minLon = (ring: Ring) => ring.reduce((m, p) => Math.min(m, p[0]!), Infinity); + +/** Shift `ring` by whole turns so its westernmost point lies in [west, west + 360). */ +function shiftInto(ring: Ring, west: number): Ring { + const turns = Math.floor((minLon(ring) - west) / 360); + return turns === 0 ? ring : ring.map(([x, y]) => [x! - turns * 360, y!]); +} + +/** Unwrap a polygon's rings, its holes kept in the same world copy as its + * outline. A ring with no area on the plane goes: Antarctica's outline is a + * circle just off the south pole, with its coast as the hole around it. */ +function unwrapPolygon(rings: Ring[]): Ring[] { + const kept = rings.map(unwrap).filter((r) => Math.abs(area(r)) > 1e-6); + if (!kept.length) return rings; + const outer = shiftInto(closePole(kept[0]!), -180); + const west = minLon(outer); + return [outer, ...kept.slice(1).map((r) => shiftInto(closePole(r), west))]; } -/** Country outlines safe to draw on a plane: no ring crosses ±180°. */ -export function splitAntimeridian>(fc: T): T { +/** Country outlines safe to draw on a plane: no edge jumps across ±180°. */ +export function unwrapAntimeridian>(fc: T): T { for (const f of fc.features) { const g = f.geometry; - const polys = g.type === "Polygon" ? [g.coordinates] : g.coordinates; - const split = polys.flatMap(splitPolygon); - if (split.length === polys.length && split.every((p, i) => p === polys[i])) continue; - f.geometry = { type: "MultiPolygon", coordinates: split }; + if (g.type === "Polygon") g.coordinates = unwrapPolygon(g.coordinates); + else g.coordinates = g.coordinates.map(unwrapPolygon); } return fc; } diff --git a/apps/postcards/tests/unit/antimeridian.spec.ts b/apps/postcards/tests/unit/antimeridian.spec.tsx similarity index 54% rename from apps/postcards/tests/unit/antimeridian.spec.ts rename to apps/postcards/tests/unit/antimeridian.spec.tsx index eb4fae2..269593d 100644 --- a/apps/postcards/tests/unit/antimeridian.spec.ts +++ b/apps/postcards/tests/unit/antimeridian.spec.tsx @@ -4,15 +4,14 @@ import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; import { feature } from "topojson-client"; import type { FeatureCollection, MultiPolygon, Polygon, Position } from "geojson"; -import { splitAntimeridian } from "../../src/features/map/antimeridian"; +import { unwrapAntimeridian } from "../../src/features/map/antimeridian"; const here = dirname(fileURLToPath(import.meta.url)); +const GEOMETRY = join(here, "..", "..", "public", "basemap", "countries-50m.json"); function countries(): FeatureCollection { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const topo: any = JSON.parse( - readFileSync(join(here, "..", "..", "public", "basemap", "countries-50m.json"), "utf8"), - ); + const topo: any = JSON.parse(readFileSync(GEOMETRY, "utf8")); return feature(topo, topo.objects.countries) as unknown as FeatureCollection; } @@ -33,37 +32,57 @@ function area(g: Polygon | MultiPolygon): number { return total; } +const atPole = (p: Position) => Math.abs(p[1]!) === 90; +const onMeridian = (p: Position) => Math.abs(p[0]!) % 360 === 180; + /** Widest east-west edge, leaving out the one along a pole (off the map). */ -function widestEdge(g: Polygon | MultiPolygon): number { +function widestEdge(rs: Position[][]): number { let w = 0; - for (const r of rings(g)) + for (const r of rs) for (let k = 1; k < r.length; k++) { - if (Math.abs(r[k]![1]!) === 90 && Math.abs(r[k - 1]![1]!) === 90) continue; + if (atPole(r[k]!) && atPole(r[k - 1]!)) continue; w = Math.max(w, Math.abs(r[k]![0]! - r[k - 1]![0]!)); } return w; } +/** Edges running down the 180th meridian that do not reach a pole: a cut there + * is drawn as a border line through Chukotka, Fiji and Antarctica. */ +function seams(rs: Position[][]): number { + let n = 0; + for (const r of rs) + for (let k = 1; k < r.length; k++) { + const a = r[k - 1]!; + const b = r[k]!; + if (onMeridian(a) && onMeridian(b) && !atPole(a) && !atPole(b)) n++; + } + return n; +} + describe("map: country shapes across the antimeridian", () => { - const fc = splitAntimeridian(countries()); + const fc = unwrapAntimeridian(countries()); const byName = (n: string) => fc.features.find((f) => f.properties?.name === n)!; it("leaves no edge spanning the map, so no country paints a band", () => { - for (const f of fc.features) { - expect(widestEdge(f.geometry), f.properties?.name).toBeLessThan(180); - for (const r of rings(f.geometry)) for (const [x] of r) expect(Math.abs(x!)).toBeLessThanOrEqual(180); - } + for (const f of fc.features) expect(widestEdge(rings(f.geometry)), f.properties?.name).toBeLessThan(180); }); - it("keeps Russia's and Fiji's land on both sides of 180°", () => { - for (const name of ["Russia", "Fiji"]) { - const xs = rings(byName(name).geometry).flat().map((p) => p[0]!); - expect(Math.min(...xs), name).toBeLessThan(-179); - expect(Math.max(...xs), name).toBeGreaterThan(179); - } - // Chukotka and Wrangel east of 180° stay a sliver, not a map-wide slab. + it("cuts no country at 180°, so no border line runs down the meridian", () => { + // Natural Earth itself splits two Fiji islands at 180°; nothing is added to that. + const raw = countries(); + fc.features.forEach((f, i) => + expect(seams(rings(f.geometry)), f.properties?.name).toBe(seams(rings(raw.features[i]!.geometry))), + ); + }); + + it("keeps Chukotka joined to Russia past 180° instead of a map-wide slab", () => { + const russia = rings(byName("Russia").geometry); + expect(Math.max(...russia.flat().map((p) => p[0]!))).toBeGreaterThan(185); expect(area(byName("Russia").geometry)).toBeLessThan(3500); expect(area(byName("Russia").geometry)).toBeGreaterThan(2500); + // Every outline starts in the [-180, 180) world MapLibre wraps from. + for (const f of fc.features) + for (const r of rings(f.geometry)) expect(Math.min(...r.map((p) => p[0]!))).toBeGreaterThanOrEqual(-180); }); it("closes Antarctica through the pole", () => { From b177145456aa835dd123c06450389013c66f5f52 Mon Sep 17 00:00:00 2001 From: davd-gzl <60177543+davd-gzl@users.noreply.github.com> Date: Wed, 30 Sep 2026 23:53:46 +0900 Subject: [PATCH 3/5] Map: stop the trip and coverage maps drawing lines across them The composer's route map and the Stats coverage card read the same world-atlas land through getLand, raw, so Russia, Fiji and Antarctica drew edges spanning the map: two lines across the route map and a stroke across the Fiji card. The land is now unwrapped once when it loads. The coverage card also unwraps a country whose land runs past 180, so Chukotka's towns stay on Russia's card. --- .../src/features/stats/CountryCoverageMap.tsx | 13 +++-- .../src/features/travel/landGeometry.ts | 7 ++- .../tests/unit/antimeridian.spec.tsx | 56 ++++++++++++++++++- 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/apps/postcards/src/features/stats/CountryCoverageMap.tsx b/apps/postcards/src/features/stats/CountryCoverageMap.tsx index 49dc22d..13bd8fc 100644 --- a/apps/postcards/src/features/stats/CountryCoverageMap.tsx +++ b/apps/postcards/src/features/stats/CountryCoverageMap.tsx @@ -117,11 +117,14 @@ export function CountryCoverageMap({ iso2, name }: { iso2: string; name: string } } if (!frameLons.length) return null; - // Unwrap across the antimeridian: when the frame spans > 180° of raw longitude - // the land wraps the date line (Russia, Fiji…), so shift western lons by +360 - // and project EVERYTHING (rings, dots, blobs) in that continuous space — else - // the silhouette collapses to a distorted, off-centre sliver. - const unwrap = Math.max(...frameLons) - Math.min(...frameLons) > 180; + // Unwrap across the antimeridian: when the frame runs past 180° (the land + // arrives unwrapped, so Russia's mainland reaches 191°) or spans > 180° of + // raw longitude, the country wraps the date line (Russia, Fiji…), so shift + // western lons by +360 and project EVERYTHING (rings, dots, blobs) in that + // continuous space — else the silhouette collapses to a distorted, off-centre + // sliver and Chukotka's towns fall off the map. + const unwrap = + Math.max(...frameLons) > 180 || Math.max(...frameLons) - Math.min(...frameLons) > 180; const wrapLon = (lon: number) => (unwrap && lon < 0 ? lon + 360 : lon); const xs = frameLons.map((lon) => (wrapLon(lon) * Math.PI) / 180); diff --git a/apps/postcards/src/features/travel/landGeometry.ts b/apps/postcards/src/features/travel/landGeometry.ts index c2d59b4..fc62183 100644 --- a/apps/postcards/src/features/travel/landGeometry.ts +++ b/apps/postcards/src/features/travel/landGeometry.ts @@ -1,5 +1,6 @@ -import type { FeatureCollection } from "geojson"; +import type { FeatureCollection, MultiPolygon, Polygon } from "geojson"; import { feature } from "topojson-client"; +import { unwrapAntimeridian } from "../map/antimeridian"; // Bundled, offline land geometry for the composer's route map — the SAME public // Natural Earth asset the main map uses (public domain, SW-precached), fetched @@ -19,7 +20,9 @@ export function getLand(): Promise { if (!res.ok) return null; // eslint-disable-next-line @typescript-eslint/no-explicit-any const topo: any = await res.json(); - return feature(topo, topo.objects.countries) as unknown as FeatureCollection; + return unwrapAntimeridian( + feature(topo, topo.objects.countries) as unknown as FeatureCollection, + ); } catch { return null; } diff --git a/apps/postcards/tests/unit/antimeridian.spec.tsx b/apps/postcards/tests/unit/antimeridian.spec.tsx index 269593d..b950780 100644 --- a/apps/postcards/tests/unit/antimeridian.spec.tsx +++ b/apps/postcards/tests/unit/antimeridian.spec.tsx @@ -1,14 +1,30 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, vi, beforeAll, afterAll } from "vitest"; +import { render, waitFor } from "@testing-library/react"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; import { feature } from "topojson-client"; import type { FeatureCollection, MultiPolygon, Polygon, Position } from "geojson"; import { unwrapAntimeridian } from "../../src/features/map/antimeridian"; +import { getLand } from "../../src/features/travel/landGeometry"; +import { CountryCoverageMap } from "../../src/features/stats/CountryCoverageMap"; +import type { Visit } from "../../src/lib/schema/models"; +import { useVisits } from "../../src/lib/store/useVisits"; const here = dirname(fileURLToPath(import.meta.url)); const GEOMETRY = join(here, "..", "..", "public", "basemap", "countries-50m.json"); +// The app fetches the bundled geometry; serve it from disk. +beforeAll(() => { + vi.stubGlobal( + "fetch", + vi.fn(async () => ({ ok: true, json: async () => JSON.parse(readFileSync(GEOMETRY, "utf8")) })), + ); +}); +afterAll(() => { + vi.unstubAllGlobals(); +}); + function countries(): FeatureCollection { // eslint-disable-next-line @typescript-eslint/no-explicit-any const topo: any = JSON.parse(readFileSync(GEOMETRY, "utf8")); @@ -96,3 +112,41 @@ describe("map: country shapes across the antimeridian", () => { expect(byName("France").geometry).toEqual(before.geometry); }); }); + +describe("route and coverage maps: the land they share", () => { + it("arrives with no edge spanning the map", async () => { + const land = (await getLand())!; + for (const f of land.features) { + const g = f.geometry as Polygon | MultiPolygon; + expect(widestEdge(rings(g)), f.properties?.name).toBeLessThan(180); + } + }); + + /** Every point of the country silhouette's path, in view-box pixels. */ + async function coverageLand(iso2: string, name: string): Promise<[number, number][][]> { + const { container } = render(); + await waitFor(() => expect(container.querySelector("path.ccov-land")).toBeTruthy()); + const d = container.querySelector("path.ccov-land")!.getAttribute("d")!; + return d + .split("Z") + .filter(Boolean) + .map((sub) => [...sub.matchAll(/[ML](-?[\d.]+) (-?[\d.]+)/g)].map((m) => [+m[1]!, +m[2]!])); + } + + it("draws Fiji with no stroke across the card", async () => { + for (const sub of await coverageLand("FJ", "Fiji")) + for (let k = 1; k < sub.length; k++) expect(Math.abs(sub[k]![0] - sub[k - 1]![0])).toBeLessThan(160); + }); + + it("keeps Russia's land and towns east of 180° on the card", async () => { + // Provideniya, Chukotka, at 173°W. + const place = { kind: "city", id: "4031574", countryId: "RU" }; + useVisits.setState({ visits: [{ status: "visited", place } as unknown as Visit] }); + const xs = (await coverageLand("RU", "Russia")).flat().map((p) => p[0]); + expect(Math.min(...xs)).toBeGreaterThanOrEqual(0); + expect(Math.max(...xs)).toBeLessThanOrEqual(320); + const dot = Number(document.querySelector("circle.ccov-visited")!.getAttribute("cx")); + expect(dot).toBeGreaterThan(Math.max(...xs) * 0.9); + useVisits.setState({ visits: [] }); + }); +}); From c279f1c2103115758edc0906a523cd217d208951 Mon Sep 17 00:00:00 2001 From: davd-gzl <60177543+davd-gzl@users.noreply.github.com> Date: Wed, 30 Sep 2026 23:53:46 +0900 Subject: [PATCH 4/5] Publish: stop the reader's route map streaking land across Asia MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reader's embedded land outline kept world-atlas's jump at the antimeridian. The reader starts a new sub-path there, but the fill closed Eurasia's ring back to its start, painting a wedge from Chukotka to South Africa across any journey map over Asia, and Antarctica stopped at 84°S. The generator now unwraps the land before simplifying it, and the outline is regenerated. --- apps/postcards/scripts/build-reader-land.mjs | 5 ++++- apps/postcards/src/lib/publish/landOutline.ts | 4 ++-- apps/postcards/tests/unit/antimeridian.spec.tsx | 9 +++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/postcards/scripts/build-reader-land.mjs b/apps/postcards/scripts/build-reader-land.mjs index d6ca828..bc13c6a 100644 --- a/apps/postcards/scripts/build-reader-land.mjs +++ b/apps/postcards/scripts/build-reader-land.mjs @@ -11,6 +11,7 @@ import { readFileSync, writeFileSync } from "node:fs"; import { feature } from "topojson-client"; +import { unwrapAntimeridian } from "../src/features/map/antimeridian.ts"; const SRC = new URL("../public/basemap/countries-50m.json", import.meta.url); const OUT = new URL("../src/lib/publish/landOutline.ts", import.meta.url); @@ -21,7 +22,9 @@ const MIN_AREA_DEG2 = 1.6; // drop rings whose lon×lat bbox area is below this const COORD_DECIMALS = 1; // 0.1° grid — plenty for a small context map const topo = JSON.parse(readFileSync(SRC, "utf8")); -const land = feature(topo, topo.objects.land); +// Unwrapped at the antimeridian first: the reader draws on a plane, where a +// ring jumping from +180 to -180 streaks across the map. +const land = unwrapAntimeridian(feature(topo, topo.objects.land)); /** Collect every polygon ring (outer + holes ignored — silhouette only). */ function collectRings(geo) { diff --git a/apps/postcards/src/lib/publish/landOutline.ts b/apps/postcards/src/lib/publish/landOutline.ts index f3bdac1..c96149f 100644 --- a/apps/postcards/src/lib/publish/landOutline.ts +++ b/apps/postcards/src/lib/publish/landOutline.ts @@ -3,7 +3,7 @@ // A compact world land silhouette (Natural Earth 50m, public domain), heavily // simplified (RDP 0.55°, 1-decimal grid, small islands dropped) so the // self-contained published reader can draw real coastlines behind the route -// without fetching anything. 104 rings, 2015 points. +// without fetching anything. 104 rings, 2018 points. // // Each ring is a closed outer boundary as [lon, lat] pairs in degrees. -export const LAND_OUTLINE: [number, number][][] = [[[16.4,-28.6],[15,-26.3],[14.5,-22.4],[11.8,-18],[11.8,-15.8],[13.8,-11.1],[12.3,-6.1],[13.1,-5.9],[12.2,-5.8],[8.7,-0.6],[10,0.2],[9.3,0.6],[9.7,4.1],[8.3,4.9],[6.1,4.3],[3.7,6.6],[-2,4.8],[-4.4,5.3],[-7.5,4.4],[-9.1,5.1],[-12.5,7.4],[-13.7,9.9],[-15.4,11.2],[-15.1,12],[-16.8,12.5],[-16.7,13.4],[-15.4,13.5],[-16.4,13.4],[-17.5,14.8],[-16,17.9],[-16.9,21.9],[-14.4,26.3],[-10.2,29.4],[-9.2,32.6],[-6.9,34],[-5.9,35.8],[-1.9,35.1],[1.3,36.5],[9.7,37.3],[11.1,36.9],[10.3,33.7],[15.2,32.4],[15.7,31.4],[18.9,30.3],[20,30.8],[20.1,32.2],[21.7,32.9],[29.1,30.8],[31.1,31.6],[33.7,31.1],[36,34.5],[36.2,36.8],[32.8,36],[31.2,36.8],[29.7,36.2],[27.5,36.7],[28.2,37],[27.3,37],[27.2,38],[26.3,38.3],[27.1,38.5],[26.2,40],[33.3,42],[38.4,40.9],[41.8,42],[36.6,45.2],[39.2,47.3],[35.2,46.4],[35,45.7],[36.4,45.1],[33.9,44.4],[32.5,45.4],[33.6,46.1],[31.8,46.3],[32.6,46.6],[31.8,47.2],[28.9,45],[27.5,42.5],[29,41],[26.2,40.1],[26.8,40.6],[24.5,40.9],[23.6,39.9],[22.6,40.5],[23.3,39.2],[22.6,38.9],[24.1,37.8],[23,37.9],[23.2,36.4],[22.5,36.4],[21.1,37.9],[23.2,38.1],[21.1,38.4],[19.3,40.4],[19.6,41.8],[13.2,45.8],[12.3,45.4],[12.4,44.2],[18.5,40.2],[16.9,40.5],[17.2,39],[16.1,37.9],[15.7,40],[8.8,44.4],[6.1,43.1],[3.3,43.2],[3.2,41.9],[0.7,40.8],[-0.3,39.5],[0.2,38.8],[-2.1,36.8],[-5.6,36],[-6.9,37.3],[-9,37],[-9.2,43.2],[-7.7,43.8],[-2,43.3],[-1.1,45.5],[-0.5,45],[-1.7,47.2],[-4.7,48.5],[-1.4,48.7],[-1.9,49.7],[0.4,49.4],[1.8,50.9],[4.2,51.4],[3.5,51.5],[4.3,51.5],[5.5,53.3],[9.8,53.6],[8.1,55.6],[8.2,56.6],[9.2,56.7],[8.6,57.1],[10.6,57.7],[10.3,56.6],[10.9,56.4],[9.6,55.5],[9.9,54.5],[14.6,53.6],[13.8,54.1],[19.4,54.4],[21.1,55.6],[20.6,55],[21.2,54.9],[21.7,57.6],[24.4,57.3],[24.5,58.4],[23.5,59.2],[30.2,60],[28.5,60.7],[23,59.8],[21.4,60.6],[21.5,63.2],[25.4,65],[24.6,65.9],[22.4,65.9],[20.8,63.9],[17.4,62.5],[17.2,60.7],[19,59.8],[16.2,58.6],[16.9,58.5],[16,56.2],[13.3,55.3],[10.6,59.8],[8.2,58.1],[5.6,58.6],[6.4,59.5],[5.2,59.6],[7,60.5],[5.1,59.6],[5.6,60.7],[5,61],[7.6,61.2],[5.1,61.2],[4.9,61.9],[6.7,61.9],[5.1,62.2],[8.6,62.8],[8.7,63.6],[10.8,63.5],[11.5,64],[9.6,63.7],[18.3,69.5],[23.4,70],[24.8,71],[25.8,70.8],[25,70.1],[28.1,71],[28.2,70.2],[28.8,70.9],[31,70.3],[28.8,70.1],[29.7,69.7],[32,70],[33.1,69.1],[35.9,69.2],[41,67.7],[41.2,66.8],[38.7,66.1],[31.9,67.2],[34.7,66],[35,64.4],[37.4,63.8],[38.1,64.1],[36.9,65.2],[39.8,64.6],[40.4,64.8],[39.8,65.6],[42.2,66.5],[44.1,66],[44.2,68.3],[43.3,68.7],[45.9,68.5],[46.7,67.8],[44.9,67.4],[46.5,66.8],[53.8,69],[54.5,69],[53.3,68.3],[59.1,69],[59.7,68.4],[60.9,69],[60.2,69.6],[60.9,69.8],[68.5,68.3],[69.1,69],[66.9,69.6],[67.3,70.7],[66.6,71.1],[69.4,73],[71.5,72.9],[72.8,72.7],[71.9,71.5],[72.7,71],[72.6,69],[73.6,68.5],[71.5,66.7],[69,66.8],[72.1,66.3],[74.8,67.8],[74.4,68.4],[75.1,68.9],[78.9,67.6],[77.6,67.8],[77.7,68.9],[73.8,69.2],[74.3,70.6],[73.1,71.4],[75,72.1],[74.8,72.8],[75.6,72.6],[75.7,71.3],[79,70.9],[76,71.9],[78.5,72.4],[83.1,71.7],[82.2,70.6],[83,70.9],[83.1,70.1],[83.5,71.7],[80.8,72.5],[80.6,73.6],[86.9,73.9],[85.8,73.4],[86.7,73.1],[85.9,73.4],[87.6,73.8],[85.8,74.6],[87,75.2],[94.2,76],[93.3,76.1],[99.5,75.8],[98.8,76.5],[101.6,76.4],[100.9,76.9],[104,77.7],[106.1,77.4],[104.2,77.1],[107.4,76.9],[106.4,76.5],[111.1,76.7],[113.9,75.9],[105.1,72.8],[110.9,73.7],[109.7,73.8],[110.3,74],[113,73.9],[113.7,72.6],[114.1,73.6],[122.5,72.9],[124.5,73.8],[129.1,73.1],[128.4,72.5],[129.3,72.1],[127.7,72.4],[131,70.7],[132.7,71.9],[140,71.5],[139.4,72],[140.2,72.2],[139.1,72.3],[140.8,72.9],[146.8,72.3],[145.2,71.7],[149.5,72.2],[150,71.9],[148.9,71.7],[152.5,70.8],[158.7,70.9],[160.9,69.6],[160.9,68.5],[163.2,69.7],[167.9,69.7],[170.5,68.8],[170.5,70.1],[179.9,69],[-180,69],[-175.3,67.7],[-174.1,66.2],[-174.5,67.1],[-169.7,66.1],[-172.8,65.7],[-172.2,65],[-173.2,64.3],[-178.4,65.5],[-178.5,66.4],[-179.7,66.2],[-180,65.1],[179.8,65],[174.5,64.7],[178.2,64.4],[179.6,62.8],[179.1,62.3],[177,62.8],[170.4,60],[168.5,60.6],[163.5,59.9],[162,58.1],[163.2,57.8],[163.3,56.2],[162.1,56.1],[162.1,54.8],[160.1,54.2],[160,53.1],[158.5,53],[156.7,51],[155.6,55.2],[156,56.8],[165.4,62.5],[163.3,62.6],[163.1,61.6],[160.2,60.6],[160.3,61.9],[157.1,61.7],[154.3,59.8],[155.2,59.2],[151.3,58.9],[152.3,59.2],[149.6,59.8],[142.6,59.2],[135.3,54.9],[136.8,54.6],[136.7,53.8],[137.7,54.3],[137.3,53.5],[139.8,54.3],[141.4,53.3],[140.8,53.1],[141.5,52.2],[140.1,48.4],[135.5,43.8],[133.2,42.7],[131.8,43.3],[127.6,39.8],[129.4,37.1],[129.2,35.2],[126.5,34.3],[126.6,37.8],[124.7,38.1],[125.6,38.7],[124.4,40],[121.2,38.7],[121.8,41],[117.8,39.1],[119.1,37.2],[122.7,37.4],[119.2,34.8],[121.9,31.8],[120,31.9],[121.9,30.9],[120.2,30.2],[122.1,29.9],[121.6,28.3],[119.1,26.1],[119.6,25.4],[116.5,22.9],[114.3,22.3],[113.5,23.1],[113.5,22.2],[110.4,21.3],[110.3,20.3],[109.9,21.5],[108.6,21.9],[106.7,21],[105.6,19],[108.8,15.4],[109.4,12.6],[109,11.3],[106.1,10.2],[106.6,9.6],[105.8,10],[106.2,9.4],[105.1,8.6],[105,10.1],[100.9,12.7],[101,13.4],[100,13.4],[99.3,9.3],[100.4,7.2],[103.2,5.3],[104.2,1.4],[101.3,2.9],[100.1,6.4],[98.3,8.2],[98.9,11.7],[97.7,16.6],[96.8,17.4],[95.4,15.7],[94.7,16.5],[94.2,16],[94,19.4],[90.6,23.6],[90.2,21.8],[90,22.5],[88.1,21.6],[87.9,22.4],[86.8,20.3],[80.3,15.7],[79.8,10.3],[77.5,8.1],[73.3,16.5],[72.8,22.2],[70.5,20.8],[69,22.3],[70.5,23.1],[68.8,23.1],[66.4,25.6],[57.8,25.7],[56.7,27.1],[54.6,26.5],[51.6,27.9],[50.1,30.2],[48,30],[50.8,24.8],[51.5,25.9],[51.3,24.3],[54.1,24.2],[56.4,26.4],[56.6,24.5],[59.8,22.3],[57.9,20.2],[57.8,19],[55.1,17],[43.9,12.6],[42.3,17.4],[39.3,21],[38.5,23.7],[34.6,28.1],[35,29.6],[34.2,27.8],[32.5,29.9],[39.5,15.5],[43.4,12.4],[42.5,11.5],[44.9,10.4],[50.8,12],[51.4,10.4],[48,4.5],[40.2,-2.7],[38.8,-6.1],[39.7,-10],[40.6,-10.7],[40.8,-14.8],[39.8,-16.4],[34.6,-19.7],[35.5,-24.1],[32.8,-25.6],[32.5,-28.2],[30.3,-31],[26.6,-33.7],[19.6,-34.8],[18.4,-34.2],[18.2,-31.7],[16.4,-28.6]],[[-57.2,5.5],[-54.4,5.9],[-51.2,4.1],[-49.9,1.2],[-52.7,-1.6],[-50.9,-0.9],[-50.4,-2],[-49.3,-1.7],[-49.6,-2.7],[-47.4,-0.6],[-44.7,-1.7],[-44.7,-3.2],[-43.4,-2.4],[-40,-2.9],[-35.4,-5.3],[-34.8,-8],[-38.9,-12.8],[-39.2,-17.7],[-41,-22],[-48.7,-25.4],[-48.8,-28.6],[-52,-32.1],[-50.6,-30.4],[-51.3,-30],[-54.2,-34.7],[-57.8,-34.5],[-58.2,-32.5],[-58.5,-34.3],[-56.7,-37],[-58.2,-38.4],[-62.3,-38.8],[-62.4,-40.9],[-65.1,-40.8],[-65,-42.1],[-63.6,-42.7],[-65,-42.7],[-64.3,-43],[-65.6,-45],[-67.6,-46.1],[-65.8,-47.9],[-67.9,-50],[-69,-50],[-68.4,-50.2],[-69.5,-51.6],[-68.4,-52.4],[-70.7,-52.7],[-71.7,-53.8],[-72.4,-53.4],[-71.2,-52.8],[-73.1,-53.2],[-71.5,-52.6],[-74,-52.6],[-74.3,-52.1],[-72.5,-52.3],[-75.1,-50.7],[-73.8,-50.9],[-74.6,-50.2],[-73.8,-49.6],[-74.6,-48],[-73.4,-48.1],[-74.7,-47.7],[-74.3,-46.8],[-75.7,-46.7],[-74,-45.4],[-73.8,-46.6],[-72.7,-44.6],[-73.3,-44.2],[-72.3,-41.5],[-73.5,-41.8],[-74,-41.1],[-73.2,-39.2],[-73.7,-37.3],[-71.5,-32.7],[-70.2,-19.7],[-71.4,-17.4],[-75.9,-14.6],[-78.8,-8.6],[-81.2,-5.9],[-81.3,-4.3],[-79.7,-2.6],[-80.9,-2.3],[-80.9,-1.1],[-77.1,3.9],[-78.4,8.1],[-77.8,8.1],[-79.4,9],[-80.8,7.2],[-85.6,9.9],[-87.5,13.4],[-91.4,14],[-94.4,16.3],[-96.5,15.7],[-103.4,18.3],[-105.5,20],[-105.8,22.6],[-109.3,25.6],[-113,31.2],[-114.9,31.9],[-114.6,30],[-109.4,23.5],[-110,22.9],[-112.1,24.8],[-112.4,26.2],[-115,27.7],[-114.1,27.7],[-114,28.4],[-115.7,29.8],[-117.5,33.3],[-120.6,34.6],[-122.5,37.5],[-121.5,38.1],[-123,38],[-123.7,38.9],[-124.5,42.8],[-124,46.2],[-123.2,46.2],[-124.1,46.3],[-124.7,48.4],[-122.8,48.1],[-122.6,47.1],[-122.9,49.4],[-124.7,50],[-124.9,50.9],[-127.7,51.2],[-126.7,51.7],[-127.8,51.7],[-126.7,52.1],[-127,52.8],[-128.1,51.8],[-128.1,52.9],[-129.2,53.5],[-127.9,53.3],[-128.5,53.9],[-130.3,53.7],[-129.6,55.5],[-130,55.1],[-130,55.9],[-130.5,54.7],[-131,56.1],[-132.1,55.6],[-131.6,56.2],[-133.5,57.2],[-133.9,58.5],[-135.4,59.4],[-135.1,58.2],[-137,59],[-136.1,58.5],[-136.6,58.2],[-139.8,59.5],[-139.4,60],[-144.1,60],[-147.8,61.2],[-148.5,60],[-151.9,59.3],[-151,59.8],[-151.9,59.8],[-151.3,60.7],[-149.1,60.9],[-149.6,61.5],[-153,60.3],[-154.2,59.2],[-153.4,58.8],[-158.5,56.1],[-163.3,54.8],[-157.5,57.5],[-156.8,59.1],[-162.1,58.6],[-162,60.7],[-163.7,59.8],[-165.4,60.5],[-163.4,60.8],[-166.1,61.8],[-164.4,63.2],[-161.1,63.6],[-161.5,64.4],[-160.9,64.8],[-166.1,64.6],[-166.9,65.2],[-166.2,65.3],[-168.1,65.7],[-164.5,66.6],[-161,66.2],[-162.4,66.9],[-160.2,66.5],[-166.8,68.4],[-156.5,71.4],[-135.3,68.7],[-135.9,69.1],[-129.5,70.1],[-133.4,68.8],[-132.7,68.8],[-128,70.6],[-125.7,69.4],[-124.6,70.2],[-124.3,69.4],[-121.7,69.8],[-114,68.4],[-115.1,67.8],[-110.1,68],[-107.3,66.4],[-108,67.8],[-105.7,68.6],[-108.7,68.3],[-106.2,68.9],[-102.3,67.7],[-96,68.2],[-96.4,67.5],[-95.4,67],[-96.4,67],[-95.8,66.6],[-95.5,68],[-93.4,68.6],[-94.6,68.8],[-93.5,69.5],[-96.6,70.2],[-95.9,70.5],[-96.4,71.2],[-94.9,72],[-91.6,70.2],[-92.9,69.7],[-90.4,69.5],[-91.2,69.3],[-90.2,68.3],[-89.3,69.3],[-88,68.8],[-88.3,67.9],[-87.4,67.2],[-84.9,68.8],[-85.5,69.8],[-82.4,69.6],[-81.3,69.1],[-82.6,68.4],[-81.5,67.1],[-83.4,66.4],[-85.1,66.9],[-83.9,66.2],[-86.7,66.5],[-86,66.1],[-87.3,65.4],[-91.4,65.9],[-87,65.2],[-88.7,64],[-93.7,64.1],[-90.7,63.1],[-93.2,62.4],[-95,59.1],[-94.3,58.3],[-93.2,58.7],[-92.8,56.9],[-90.6,57.2],[-85.4,55.1],[-82.4,55.1],[-81.8,52.2],[-79.3,50.8],[-79.7,51.3],[-78.9,51.2],[-78.4,52.3],[-79.7,54.7],[-76.5,56.4],[-76.9,57.8],[-78.5,58.7],[-77.3,60],[-78.2,60.8],[-77.5,61.6],[-78.1,62.4],[-73.7,62.5],[-69.5,61],[-69.7,60.1],[-70.7,60],[-69.3,59.3],[-70.2,58.8],[-68.4,58.7],[-69,57.9],[-66,58.4],[-64.5,60.3],[-62.9,58.7],[-63.5,58.3],[-62.6,58.5],[-63.3,58],[-61.3,57],[-62.5,56.8],[-60.3,55.8],[-60.6,55.1],[-57.4,54.6],[-60.3,53.3],[-57.4,54.2],[-57.3,53.5],[-56,53.5],[-55.7,52.1],[-60.1,50.3],[-66.5,50.2],[-69.7,48.2],[-71,48.5],[-69.8,48.1],[-74.7,45],[-68.8,48.4],[-65.5,49.3],[-64.3,48.5],[-66.7,48],[-64.7,47.7],[-65.3,47.1],[-64.5,46.2],[-61,45.3],[-65.5,43.5],[-66.1,44.5],[-63.4,45.4],[-64.9,45.4],[-64.6,45.9],[-68.8,44.6],[-71,42.3],[-69.9,41.7],[-74,41.2],[-74.9,38.9],[-75.5,39.8],[-75,38.4],[-75.9,37.2],[-75.9,39.5],[-76.6,39.3],[-76.3,38.1],[-77,38.9],[-76.3,37.1],[-77.3,37.3],[-75.5,35.8],[-75.9,36.7],[-76.7,36.2],[-75.8,35.8],[-77,35.5],[-76.4,34.8],[-80.8,32.4],[-81.4,31.2],[-80,26.8],[-80.5,25.2],[-81.1,25.1],[-82.7,27.5],[-82.7,28.9],[-84,30.1],[-90.2,30.4],[-89.4,29],[-93.8,30],[-97.8,27.5],[-97.1,26],[-97.8,22.5],[-95.9,18.8],[-94.5,18.2],[-91.3,18.6],[-89.9,21.3],[-86.8,21.4],[-88.9,15.9],[-85,16],[-83.4,15.2],[-83.9,11.3],[-82.1,8.9],[-79.1,9.5],[-76.8,7.9],[-75.2,10.8],[-71.5,12.4],[-71.7,9.1],[-71.1,9.3],[-71.5,11],[-69.8,11.5],[-70,12.2],[-68.1,10.5],[-61.9,10.7],[-62.9,10.5],[-60.8,9.4],[-61.6,8.6],[-59.2,8.1],[-58.7,6.4],[-58,6.8],[-57.2,5.5]],[[-152.9,57.8],[-152.2,57.6],[-154,56.8],[-154.7,57.3],[-152.9,57.8]],[[-155.6,19],[-155.8,20.3],[-154.8,19.5],[-155.6,19]],[[-37.1,-54.1],[-35.8,-54.8],[-38,-54],[-37.1,-54.1]],[[-58.9,-51.3],[-57.8,-51.6],[-59.5,-52.2],[-58.9,-51.3]],[[-2.7,51.6],[-5.3,51.9],[-4.1,52.3],[-4.3,53.1],[-2.7,53.3],[-3.6,54.6],[-3,55],[-5.1,54.9],[-4.8,56.2],[-5.8,55.4],[-5.2,56.8],[-6.1,56.7],[-5,58.6],[-3.1,58.6],[-4.1,57.6],[-1.9,57.6],[-3.3,56.4],[-2.7,56.3],[-3.8,56.1],[-2.1,55.9],[-0.1,54.1],[-0.7,53.7],[0,52.9],[1.7,52.6],[0.4,51.5],[1.4,51.4],[1,50.9],[-5.7,50.1],[-2.7,51.6]],[[-7.2,55.1],[-5.5,54.5],[-6.3,54],[-6.3,52.2],[-10.1,51.6],[-9.6,51.9],[-10.4,52.2],[-8.8,52.7],[-9.9,52.6],[-8.9,53.2],[-10.1,53.4],[-10.1,54.3],[-7.2,55.1]],[[124.4,-9.2],[127.3,-8.4],[123.6,-10.3],[124.4,-9.2]],[[121,22.6],[120.8,21.9],[120.1,23.7],[121.6,25.3],[121,22.6]],[[80,9.8],[81.9,7.3],[80.7,6],[79.9,6.8],[80,9.8]],[[50.3,80.9],[51.7,80.7],[46.6,80.3],[50.3,80.9]],[[67.8,76.2],[61.4,75.3],[56.6,73.3],[53.8,73.8],[58.9,75.9],[68.9,76.7],[67.8,76.2]],[[55.3,73.3],[56.4,73.2],[55.3,71.9],[57.6,70.7],[53.7,70.8],[51.4,71.8],[53.2,73.2],[55.3,73.3]],[[96.5,81.1],[97.9,80.7],[91.5,80.4],[96.5,81.1]],[[97.7,80.2],[99.9,79.8],[99,79.3],[99.9,79],[93.1,79.5],[97.7,80.2]],[[102.9,79.3],[102.4,78.8],[105.3,78.6],[99.3,78],[102.9,79.3]],[[140.1,75.8],[145.4,75.5],[144,75],[142.3,75.7],[143.1,75],[139.1,74.7],[136.9,75.3],[138.2,76.1],[140.1,75.8]],[[146.8,75.4],[150.8,75.2],[146.1,75.2],[146.8,75.4]],[[142.8,54.4],[144.7,48.6],[143.1,49.2],[142.6,47.7],[143.6,46.4],[142.6,46.7],[142.1,45.9],[141.7,52.3],[142.8,54.4]],[[-180,71.5],[-177.5,71.2],[-179.4,70.9],[-180,71],[179.9,71],[178.8,70.8],[179.9,71.5],[-180,71.5]],[[54.7,81.1],[57.7,80.8],[54.1,80.8],[54.7,81.1]],[[142.2,73.9],[143.5,73.2],[139.7,73.4],[142.2,73.9]],[[121.1,18.6],[122.3,18.5],[122.5,17.1],[121.4,15.3],[121.8,14.2],[123.8,13.8],[124.1,12.6],[122.6,13.9],[122.6,13.2],[120.6,13.8],[120.9,14.6],[120.1,14.9],[119.8,16.3],[120.4,16.1],[121.1,18.6]],[[117.3,8.4],[119.6,11.3],[119.7,10.5],[117.3,8.4]],[[122.5,11.6],[123.1,11.3],[122,10.4],[121.9,11.9],[122.5,11.6]],[[123.1,9.1],[122.4,9.8],[123.3,11],[123.1,9.1]],[[125.2,12.5],[125.7,11.1],[124.3,12.6],[125.2,12.5]],[[126,9.3],[126.6,7.2],[126.2,6.3],[125.7,7.3],[125.3,5.6],[123.7,7.8],[121.9,7.1],[123.4,8.7],[123.8,8],[125.5,9.8],[126,9.3]],[[151.9,-4.3],[152.1,-5.5],[150.5,-6.3],[148.3,-5.7],[150.7,-5.5],[151.9,-4.3]],[[141,-2.6],[147.6,-6.1],[147.2,-7.4],[150.6,-10.6],[147.8,-10.1],[144.9,-7.6],[142.2,-8.2],[143.4,-8.8],[142.6,-9.3],[141.1,-9.2],[140.1,-7.9],[138.9,-8.3],[138.3,-5.7],[134,-3.8],[133.8,-3.1],[133,-4.1],[132,-2.8],[133.9,-2.1],[131,-1.4],[132.4,-0.4],[134,-0.7],[135.3,-3.4],[137.8,-1.5],[141,-2.6]],[[15.8,68.6],[16.5,68.7],[14.3,68.2],[16.1,69.3],[15.8,68.6]],[[21.6,78.6],[24.9,77.8],[21.1,77.4],[21.7,77.9],[20.2,78.5],[21.6,78.6]],[[20.9,80.2],[27.2,79.9],[23.9,79.2],[17.9,80.1],[20.9,80.2]],[[16.8,79.9],[21.4,78.7],[19.2,78.4],[16.7,76.6],[14,77.5],[16.9,77.9],[13.7,78],[16.8,78.7],[13.1,78.2],[10.7,79.8],[14.6,79.8],[16.3,79],[15.8,79.7],[16.8,79.9]],[[173.1,-41.3],[174.4,-41.1],[172.5,-43.5],[173.1,-43.8],[171.2,-44.3],[170.8,-45.9],[169.1,-46.6],[166.5,-46],[171,-42.9],[172.3,-40.8],[172.9,-40.5],[173.1,-41.3]],[[173.3,-34.9],[176.1,-37.6],[178.5,-37.7],[175.3,-41.6],[174.6,-41.3],[175.2,-40.2],[173.8,-39.3],[174.9,-37.1],[172.7,-34.5],[173.3,-34.9]],[[109.6,2],[111.2,1.4],[111.5,2.7],[115.4,5],[116.8,7],[119.3,5.4],[117.1,3.6],[119,1],[117.9,1.1],[116.3,-3.1],[114.7,-4.2],[114.3,-3.2],[110.3,-3],[108.9,0.4],[109.6,2]],[[49.5,-12.4],[50.4,-15.6],[49.7,-15.5],[47.2,-24.8],[45.1,-25.5],[44,-25],[43.3,-22.4],[44.4,-19.9],[44,-17.4],[44.5,-16.2],[48,-14.7],[47.9,-13.7],[49.5,-12.4]],[[143.8,44.1],[145.4,44.3],[145.8,43.4],[143.2,42],[140.5,42.6],[141.2,41.8],[140,41.5],[139.9,42.6],[141.4,43.3],[141.7,45.4],[143.8,44.1]],[[131.2,33.6],[132,32.9],[130.7,31],[130.5,32.8],[129.6,33.2],[131.2,33.6]],[[134.4,34.3],[134.2,33.2],[132.8,32.8],[132,33.3],[134.4,34.3]],[[141.2,41.4],[142,39.4],[140.4,35.2],[136.8,35.1],[135.7,33.5],[134.7,34.8],[130.9,34],[132.9,35.5],[135.9,35.6],[136.8,37.4],[137.2,36.8],[138.3,37.2],[139.8,38.9],[140,40.7],[141.2,41.4]],[[-77.3,18.5],[-76.2,17.9],[-78.3,18.3],[-77.3,18.5]],[[15.6,38.2],[15.1,36.7],[12.4,37.8],[15.6,38.2]],[[9.6,40.9],[9.6,39.2],[8.6,38.9],[8.2,40.9],[9.6,40.9]],[[106,-1.7],[106.7,-3.1],[105.1,-2],[106,-1.7]],[[96.5,5.2],[97.5,5.2],[103,0.6],[102.5,0.2],[103.7,0.3],[104.7,-2.6],[106,-3.1],[105.7,-5.8],[104.6,-5.9],[101.6,-3.2],[95.2,5.6],[96.5,5.2]],[[120,-9.4],[120.8,-10.1],[119,-9.5],[120,-9.4]],[[118.2,-8.3],[119.1,-8.7],[116.8,-9],[118.2,-8.3]],[[124.9,1],[120.2,0.3],[120.7,-1.4],[123.4,-0.8],[121.4,-1.9],[122.9,-4.4],[121.6,-4.8],[120.7,-2.7],[120.4,-5.6],[119.6,-5.6],[118.8,-2.7],[120.3,1],[123.8,0.8],[125.1,1.7],[124.9,1]],[[107.4,-6],[112.5,-6.9],[114.4,-7.8],[114.6,-8.8],[105.3,-6.8],[106.1,-5.9],[107.4,-6]],[[127.7,0.8],[128.7,1.6],[128.3,0.7],[128.9,0.2],[127.9,0.3],[128.4,-0.9],[127.4,1.1],[128,2.2],[127.7,0.8]],[[129.8,-2.9],[130.8,-3.9],[127.9,-3.5],[129.8,-2.9]],[[-15.5,66.2],[-14.6,66.4],[-15.1,66.1],[-13.6,65.1],[-18.7,63.4],[-22.7,63.8],[-21.6,64.6],[-24,64.9],[-21.8,65.4],[-24.5,65.5],[-22.4,65.9],[-22.9,66.4],[-21.1,65.3],[-20.2,66.1],[-15.5,66.2]],[[-71.8,18],[-74.5,18.4],[-72.4,18.6],[-73.4,19.7],[-72.9,19.9],[-70,19.7],[-68.3,18.6],[-71.8,18]],[[23.4,39],[24.6,38],[22.9,38.9],[23.4,39]],[[9.5,42.8],[9.2,41.4],[8.6,42.4],[9.5,42.8]],[[69.2,-49.1],[70.6,-49.2],[68.8,-49.7],[69.2,-49.1]],[[-30,83.6],[-25.8,83.3],[-32,83],[-21.5,82.6],[-29.9,82.1],[-21.1,81.9],[-23.1,80.8],[-19.6,81.6],[-11.4,81.5],[-20.2,80],[-19,79.2],[-21.1,78.7],[-21.7,77.7],[-18.3,76.9],[-22.6,76.7],[-19.9,76.1],[-19.5,75.2],[-22.2,75.1],[-19.3,74.3],[-22.3,74.3],[-20.6,73.5],[-25.5,73.9],[-24.8,73.5],[-27.6,73.1],[-22,72.9],[-22.3,72.1],[-24.6,73],[-26.7,72.7],[-24.8,72.9],[-25.2,72.4],[-22,71.7],[-22.5,71.4],[-21.8,71.5],[-21.5,70.5],[-27.1,71.6],[-25.7,71.3],[-29.1,70.4],[-22.2,70.1],[-26.5,68.7],[-32.3,68.4],[-34.6,66.4],[-40.2,65.6],[-39.6,65.3],[-41.1,65],[-40.2,64.5],[-41.6,64.3],[-40.5,63.7],[-42.9,62.7],[-42.1,61.9],[-43.9,59.8],[-46,60.6],[-45.9,61.2],[-48.2,60.8],[-49.3,61.6],[-48.8,62.1],[-50.3,62.5],[-49.8,63],[-51.5,64],[-50.3,64.2],[-51.7,64.2],[-50.1,64.7],[-51,65.2],[-51.9,64.2],[-52.5,65.3],[-51.1,65.8],[-53.4,66],[-51.2,66.9],[-53.6,66.2],[-52.4,66.9],[-53.8,67.4],[-50.6,67.5],[-53.7,67.5],[-51.2,68.4],[-53,68.6],[-50.3,69.2],[-51.1,69.2],[-50.3,70],[-54.5,70.7],[-50.7,70.4],[-53.1,71.3],[-51.8,71.7],[-53.4,71.6],[-53.7,72.4],[-54,71.5],[-55.6,71.6],[-54.7,72.9],[-58.5,75.7],[-68.3,76.1],[-69.5,76.4],[-68.1,76.7],[-71.2,77.1],[-66.3,77.5],[-72.8,78.2],[-65.8,79.2],[-64.2,80.1],[-67.2,80.3],[-60.4,81.9],[-56.6,81.4],[-59.3,82],[-49.5,81.9],[-51,82.5],[-50,82.5],[-44.7,81.8],[-44.3,82.5],[-45.6,82.7],[-41.4,82.8],[-46.5,83],[-30,83.6]],[[-52.7,69.9],[-51.9,69.6],[-54.9,69.7],[-52.7,69.9]],[[12.6,55.8],[11.9,54.8],[11,55.7],[12.6,55.8]],[[32.7,35.2],[34.6,35.7],[33,34.6],[32.7,35.2]],[[-81.8,23.2],[-74.2,20.2],[-77.7,19.9],[-77.2,20.6],[-81.8,22.7],[-84.9,21.9],[-81.8,23.2]],[[110.9,20],[110,18.4],[108.7,18.5],[109.3,19.9],[110.9,20]],[[-68.7,-54.9],[-71.9,-54.5],[-70.3,-54.5],[-70.5,-53.6],[-69,-54.4],[-70.2,-53.9],[-69.4,-53.4],[-70.5,-53.2],[-69.4,-52.5],[-65.2,-54.7],[-68.7,-54.9]],[[-127.2,50.6],[-125.5,50.3],[-123.3,48.4],[-128.4,50.7],[-127.2,50.6]],[[-110.5,78.1],[-109.6,78.1],[-112,77.3],[-113.3,77.8],[-110.5,78.1]],[[-115.6,77.4],[-117.2,76.3],[-122.9,76.1],[-115.6,77.4]],[[-108.3,76.1],[-105.5,75.7],[-112.5,74.4],[-114.4,74.7],[-111,75.2],[-117.6,75.3],[-115.1,75.7],[-117.2,75.6],[-115,75.9],[-116.7,76],[-115,76.5],[-109.1,75.5],[-110.3,76.4],[-108.8,76.8],[-108.3,76.1]],[[-114.5,72.6],[-110,73],[-107.8,71.6],[-107.3,71.9],[-108.2,73.2],[-106.5,73.2],[-104.5,71.1],[-100.9,69.8],[-103.4,69.7],[-101.8,69.2],[-102.9,68.8],[-106.7,69.4],[-113,68.5],[-117.2,70.1],[-111.6,70.3],[-117.6,70.6],[-118.4,71],[-115.3,71.5],[-119,71.8],[-116.6,73.1],[-114.2,73.3],[-114.5,72.6]],[[-119.7,74.1],[-115.4,73.5],[-122.8,71.1],[-125.8,72],[-123.8,73.8],[-124.7,74.3],[-119.7,74.1]],[[-69.5,83],[-61.2,82.3],[-68.7,81.3],[-64.8,81.5],[-72.1,80.1],[-70.6,80.1],[-71.4,79.8],[-76.9,79.5],[-74.5,79.1],[-78.6,79.1],[-74.4,78.7],[-78.5,77.4],[-82.1,77.3],[-78,76.9],[-80.8,76.2],[-89.6,76.5],[-86.8,77.2],[-88,77.8],[-84.5,77.4],[-82.6,78],[-84.9,77.5],[-85.5,77.9],[-84.2,78.2],[-84.8,78.5],[-87.6,78.2],[-86.8,78.8],[-81.8,79],[-84.4,79],[-83.6,79.1],[-86.5,80.3],[-80.5,79.6],[-83,80.3],[-76.9,80.9],[-78.7,81],[-76.9,81.4],[-85.1,80.5],[-86.6,80.6],[-83.3,81.1],[-87.7,80.7],[-89.3,80.9],[-84.9,81.3],[-91.7,81.6],[-84.9,82.4],[-79.4,81.9],[-82.4,82.4],[-78.7,82.7],[-79.9,82.9],[-69.5,83]],[[-93.5,75],[-96.6,75.1],[-94.9,75.6],[-93.5,75]],[[-100,73.9],[-97.1,73.8],[-98.4,73],[-96.5,72.7],[-96.6,71.8],[-99.2,71.4],[-102.7,72.8],[-100.1,72.9],[-101.5,73.5],[-100,73.9]],[[-84.9,65.3],[-80.3,63.8],[-87.2,63.6],[-85.8,65.8],[-84.9,65.3]],[[-97.7,76.5],[-97.3,75.4],[-98,75],[-102.8,75.6],[-101,75.8],[-101.8,76.5],[-97.7,76.5]],[[-103.4,79.3],[-99,78],[-104.5,78.3],[-103.4,78.7],[-105.6,79.1],[-103.4,79.3]],[[-91.9,81.1],[-85,79.3],[-91.4,78.2],[-94.2,79],[-91.3,79.4],[-95.1,79.3],[-95.7,79.4],[-94.4,79.7],[-96.8,80.1],[-94.3,80.2],[-96.4,80.3],[-93.2,81.1],[-94.2,81.3],[-91.9,81.1]],[[-94.3,76.9],[-89.3,76.3],[-91.4,76.2],[-88.9,75.5],[-82.2,75.8],[-79.4,74.9],[-91.5,74.7],[-93.1,76.4],[-96.9,76.8],[-94.3,76.9]],[[-96.2,78.5],[-94.9,78.1],[-97,77.8],[-98.3,78.8],[-96.2,78.5]],[[-93.2,74.2],[-90.4,73.9],[-95.2,72],[-95.6,73.7],[-93.2,74.2]],[[-97.4,69.6],[-95.3,68.8],[-99.6,69],[-97.4,69.6]],[[-61.1,45.9],[-59.8,46],[-61.5,45.7],[-60.5,47],[-61.1,45.9]],[[-55.5,51.5],[-56.8,49.6],[-56.2,50.1],[-55.4,49.1],[-53.6,49.3],[-54.1,48.4],[-53,48.6],[-53.9,48],[-52.9,48.1],[-53.1,46.7],[-54.2,46.9],[-54.2,47.9],[-55.8,46.9],[-54.8,47.7],[-59.3,47.6],[-57,51],[-55.5,51.5]],[[-86.6,71],[-84.8,71],[-85.9,72],[-84.3,72],[-85.7,72.7],[-84.3,72.8],[-85.5,73.1],[-81.6,73.7],[-80.3,72.8],[-80.9,71.9],[-77.3,72.7],[-75.1,72.3],[-75.9,71.7],[-74.3,72],[-75,71.2],[-71.6,71.5],[-72.6,70.8],[-70.7,71.1],[-71.9,70.4],[-71.4,70.1],[-68.5,70.6],[-70.1,70],[-67.9,70.3],[-67.2,69.8],[-69.3,69.5],[-66.7,69.3],[-69.3,68.9],[-61.3,66.6],[-63.5,65.9],[-63.6,64.9],[-65.4,65.8],[-64.4,66.3],[-68.7,66.2],[-64.4,63.7],[-65.2,63.8],[-65.1,62.6],[-68.9,63.7],[-66.3,61.9],[-71.3,63.1],[-73.3,64.6],[-78.2,64.6],[-77.3,65.5],[-75.5,64.8],[-75.8,65.3],[-73.6,65.5],[-74.4,66.2],[-72.2,67.3],[-73.9,68.7],[-76.6,68.7],[-75.6,69.2],[-79.1,70.6],[-79.1,69.9],[-81.7,70.1],[-80.9,69.7],[-87.9,70.3],[-89.5,71.1],[-87.1,71],[-90,71.9],[-88.7,73.4],[-85,73.8],[-86.7,72.8],[-85,71.4],[-86.6,71]],[[-63.8,46.5],[-62,46.4],[-64.4,46.7],[-63.8,46.5]],[[-75.7,68.3],[-75.4,67.4],[-77.3,67.7],[-75.7,68.3]],[[-79.5,73.7],[-76.1,72.9],[-79.5,72.8],[-80.9,73.7],[-79.5,73.7]],[[-105.3,72.9],[-106.9,73.5],[-104.6,73.6],[-105.3,72.9]],[[-49.6,-0.2],[-48.4,-0.3],[-48.8,-1.4],[-50.5,-1.8],[-50.6,-0.3],[-49.6,-0.2]],[[143.2,-12],[143.8,-14.3],[145.3,-14.9],[146.4,-19],[148.8,-20.3],[149.7,-22.4],[150.7,-22.4],[153.2,-26],[153.6,-28.7],[152.9,-31.4],[149.9,-37.5],[146.4,-39.1],[144.9,-37.9],[143.5,-38.8],[141.4,-38.4],[139.8,-37.2],[139.3,-35.4],[138.2,-35.6],[138.1,-34.2],[136.9,-35.2],[137.8,-32.6],[135.6,-34.9],[134.2,-32.5],[131.1,-31.5],[125.9,-32.3],[123.5,-33.9],[119.9,-34],[117.9,-35.1],[115,-34.3],[115.7,-31.7],[113.2,-26.2],[113.8,-26.5],[113.4,-25.6],[114.2,-26.3],[113.4,-24.4],[114,-21.9],[114.1,-22.5],[116.7,-20.7],[121,-19.6],[122.3,-17.1],[123,-16.4],[123.6,-17.5],[123.6,-16.2],[124.8,-16.4],[124.4,-15.5],[126.1,-14],[127.5,-14],[128.1,-15.3],[129.6,-15.1],[130.6,-12.4],[132.7,-12.1],[132,-11.2],[136.5,-12],[135.5,-14.9],[140.5,-17.6],[142.5,-10.7],[143.2,-12]],[[145,-40.8],[148.2,-40.9],[148,-43.2],[146,-43.5],[145,-40.8]],[[-180,-84.4],[-156.5,-85.2],[-174.2,-82.8],[-159.4,-83.5],[-153.4,-82.6],[-154.7,-81.9],[-154,-81.7],[-157,-81.3],[-148.1,-80.9],[-150.6,-80.4],[-148.1,-79.9],[-156.1,-78.7],[-154.3,-78.3],[-157.8,-78.1],[-158.2,-77.2],[-145.7,-77.5],[-145.7,-76.7],[-149.7,-76.4],[-135.4,-74.7],[-121.5,-74.7],[-114.6,-73.9],[-113.5,-74.1],[-114.1,-75],[-111.2,-74.2],[-110.2,-74.5],[-111.4,-75.2],[-101,-75.4],[-98.6,-75.2],[-102.8,-73.6],[-98.9,-73.6],[-103.4,-72.8],[-90.9,-73.3],[-88.8,-72.7],[-82.2,-73.9],[-80.4,-72.9],[-77,-73.8],[-68.8,-73.1],[-66.8,-72.1],[-68.7,-69.4],[-67,-69.2],[-66.7,-67.6],[-67.5,-67.1],[-66.5,-67.3],[-63.8,-65],[-57.4,-63.2],[-56.8,-63.6],[-62.1,-65.3],[-62,-66.1],[-60.6,-66],[-61,-66.3],[-63.8,-66.3],[-65.5,-67.4],[-65.2,-68.6],[-62.9,-68.4],[-63.8,-68.6],[-61,-71.2],[-62.3,-72],[-60,-72.9],[-62,-73.1],[-60.8,-73.7],[-61.8,-74.3],[-60.7,-74.3],[-70.2,-76.7],[-77.3,-76.7],[-72.9,-77.6],[-74,-78.1],[-80.6,-77.8],[-81.6,-77.8],[-77.5,-78.6],[-83.8,-78.5],[-80.9,-79.5],[-76.2,-79.4],[-79.7,-80],[-75.1,-80.9],[-62.4,-81.6],[-66.1,-82],[-60.5,-82.2],[-62.7,-82.5],[-59.5,-83.5],[-54,-82.2],[-45.8,-82.5],[-38.8,-80.9],[-23.6,-80],[-36.3,-78.6],[-28.9,-76.4],[-18.3,-75.4],[-17.4,-74.4],[-14.6,-73.9],[-16.4,-73.4],[-11.5,-72.4],[-11,-71.8],[-12.3,-71.4],[-10.3,-70.9],[-8.6,-71.7],[-5.9,-70.7],[-6.1,-71.3],[-0.5,-71.7],[13.1,-70.1],[26.5,-71],[32.6,-70],[32.6,-68.9],[33.5,-68.7],[38.9,-70.2],[40.2,-68.8],[46.6,-67.3],[48.4,-68],[49.2,-67.2],[48.5,-67],[50.6,-67.2],[50.3,-66.4],[53.7,-65.9],[57.2,-66.6],[56.2,-67.3],[69.6,-67.8],[70,-68.5],[69.1,-69.9],[67.3,-70.3],[69.2,-70.4],[66.5,-73.1],[67.3,-73.3],[73.3,-69.8],[84.5,-67.1],[99.4,-66.6],[102.7,-65.9],[109.5,-66.9],[113.1,-65.8],[115.6,-66.8],[114,-67.4],[120.4,-67],[119.1,-67.4],[125.9,-66.4],[129,-67.1],[133.4,-66.1],[143.7,-66.9],[144.6,-67.1],[144,-67.9],[146,-67.6],[147.1,-68.4],[153.9,-68.3],[159.8,-69.5],[162.2,-71],[162.7,-70.3],[170.4,-71.4],[170.2,-72.6],[168.4,-72.4],[169.5,-73.1],[166.5,-72.9],[167.7,-73.4],[164.8,-73.4],[165.4,-74.6],[160.9,-75.3],[162.8,-75.8],[162.4,-77],[164.5,-78],[164,-78.2],[167.1,-78.7],[161.7,-78.5],[162,-79],[159.9,-79.8],[160.6,-80],[158.6,-80.4],[160.6,-80.4],[160.5,-81.3],[163.7,-82.2],[161.3,-82.5],[179.6,-84.3],[-180,-84.4]],[[-45.2,-78.8],[-43.6,-78.8],[-43,-79.8],[-54.4,-80.8],[-50.3,-79.5],[-49.1,-78],[-43.8,-78.3],[-45.2,-78.8]],[[-59.7,-80.3],[-60.6,-80.9],[-66.8,-80.3],[-60.6,-79.7],[-59.7,-80.3]],[[-70.1,-69.2],[-68.3,-70.9],[-69.2,-72.5],[-75.4,-71.8],[-69.9,-71.1],[-71.2,-70.7],[-69.6,-70.4],[-71.7,-70.1],[-72.1,-69.1],[-70.1,-69.2]],[[-98.1,-71.9],[-95.5,-72.2],[-102.3,-72.1],[-98.1,-71.9]],[[-120.6,-73.8],[-122.3,-74.4],[-123.3,-73.8],[-120.6,-73.8]],[[-126.3,-73.3],[-123.8,-74.2],[-127.4,-73.4],[-126.3,-73.3]],[[-159.1,-79.8],[-164.3,-79.2],[-162.4,-78.8],[-159.1,-79.8]],[[167.1,-77.3],[169.4,-77.5],[166.2,-77.5],[167.1,-77.3]],[[-70.3,-79.7],[-71.8,-79.4],[-66.7,-78.4],[-70.3,-79.7]],[[-68,-67.5],[-69.1,-67.5],[-67.7,-66.6],[-68,-67.5]]]; +export const LAND_OUTLINE: [number, number][][] = [[[16.4,-28.6],[15,-26.3],[14.5,-22.4],[11.8,-18],[11.8,-15.8],[13.8,-11.1],[12.3,-6.1],[13.1,-5.9],[12.2,-5.8],[8.7,-0.6],[10,0.2],[9.3,0.6],[9.7,4.1],[8.3,4.9],[6.1,4.3],[3.7,6.6],[-2,4.8],[-3.2,5.4],[-8.3,4.6],[-12.5,7.4],[-13.7,9.9],[-15.4,11.2],[-15.1,12],[-16.8,12.5],[-16.7,13.4],[-15.4,13.5],[-16.4,13.4],[-17.5,14.8],[-16.1,17.5],[-16.2,20.2],[-17.1,20.9],[-15.9,23.8],[-12.9,27.9],[-10.2,29.4],[-9.2,32.6],[-6.9,34],[-5.9,35.8],[-1.9,35.1],[1.3,36.5],[9.7,37.3],[11.1,36.9],[10.3,33.7],[15.2,32.4],[15.7,31.4],[18.9,30.3],[20,30.8],[20.1,32.2],[21.7,32.9],[29.1,30.8],[31.1,31.6],[33.7,31.1],[36,34.5],[36.2,36.8],[32.8,36],[31.2,36.8],[29.7,36.2],[27.5,36.7],[28.2,37],[27.3,37],[27.2,38],[26.3,38.3],[27.1,38.5],[26.2,40],[33.4,42],[38.4,40.9],[41.7,41.7],[41.4,42.7],[36.6,45.2],[39.2,47.3],[35.2,46.4],[35,45.7],[36.4,45.1],[33.9,44.4],[32.5,45.4],[33.6,46.1],[31.8,46.3],[32.6,46.6],[31.8,47.2],[28.9,45],[27.5,42.5],[29,41],[26.2,40.1],[26.8,40.6],[24.5,40.9],[23.6,39.9],[22.6,40.5],[23.3,39.2],[22.6,38.9],[24.1,37.8],[23,37.9],[23.2,36.4],[22.5,36.4],[21.1,37.9],[23.2,38.1],[21.1,38.4],[19.3,40.4],[19.6,41.8],[13.2,45.8],[12.3,45.4],[12.4,44.2],[18.5,40.2],[16.9,40.5],[17.2,39],[16.1,37.9],[15.7,40],[8.8,44.4],[6.1,43.1],[3.3,43.2],[3.2,41.9],[0.7,40.8],[-0.3,39.5],[0.2,38.8],[-2.1,36.8],[-5.6,36],[-6.9,37.3],[-9,37],[-9.2,43.2],[-7.7,43.8],[-2,43.3],[-1.1,45.5],[-0.5,45],[-1.7,47.2],[-4.7,48.5],[-1.4,48.7],[-1.9,49.7],[0.4,49.4],[1.8,50.9],[4.2,51.4],[3.5,51.5],[4.3,51.5],[5.5,53.3],[9.8,53.6],[8.1,55.6],[8.2,56.6],[9.2,56.7],[8.6,57.1],[10.6,57.7],[10.3,56.6],[10.9,56.4],[9.6,55.5],[9.9,54.5],[14.6,53.6],[13.8,54.1],[19.4,54.4],[21.1,55.6],[20.6,55],[21.2,54.9],[21.7,57.6],[24.4,57.3],[24.5,58.4],[23.5,59.2],[30.2,59.9],[28.5,60.7],[23,59.8],[21.4,60.6],[21.5,63.2],[25.4,65],[24.6,65.9],[22.4,65.9],[20.8,63.9],[17.4,62.5],[17.2,60.7],[19,59.8],[16.2,58.6],[16.9,58.5],[16.3,56.7],[14.2,55.4],[12.9,55.4],[12.9,56.6],[10.6,59.8],[8.2,58.1],[5.6,58.6],[6.4,59.5],[5.2,59.6],[7,60.5],[5.1,59.6],[5.6,60.7],[5,61],[7.6,61.2],[5.1,61.2],[4.9,61.9],[6.7,61.9],[5.1,62.2],[11.4,63.8],[9.6,63.7],[12.9,65.3],[12.1,65.4],[14,66.3],[13.1,66.2],[13.7,66.9],[15.4,67.2],[14.4,67.3],[15.6,67.3],[15,68],[16.3,67.9],[19.2,69.7],[23.4,70],[24.7,71],[25.8,70.9],[25,70.1],[27.6,71.1],[28.4,71],[28.2,70.2],[30.1,70.7],[30.9,70.3],[28.8,70.1],[41,67.7],[41.2,66.8],[38.7,66.1],[31.9,67.2],[34.7,66],[35,64.4],[37.4,63.8],[38.1,64.1],[36.9,65.2],[39.8,64.6],[40.4,64.8],[39.8,65.6],[42.2,66.5],[44.1,66],[44.2,68.3],[43.3,68.7],[45.9,68.5],[46.7,67.8],[44.9,67.4],[46.5,66.8],[53.8,69],[54.5,69],[53.3,68.3],[59.1,69],[59.7,68.4],[60.9,69],[60.2,69.6],[60.9,69.8],[68.5,68.3],[69.1,69],[66.9,69.6],[67.3,70.7],[66.6,71.1],[69.6,73],[72.8,72.7],[71.9,71.5],[72.7,71],[72.6,69],[73.6,68.5],[71.5,66.7],[69,66.8],[72.1,66.3],[74.8,67.8],[74.4,68.4],[75.1,68.9],[78.9,67.6],[77.6,67.8],[77.7,68.9],[73.8,69.2],[74.3,70.6],[73.1,71.4],[75,72.1],[74.8,72.8],[75.6,72.6],[75.7,71.3],[79,70.9],[76,71.9],[78.5,72.4],[83.1,71.7],[82.2,70.6],[83,70.9],[83.1,70.1],[83.5,71.7],[80.8,72.5],[80.6,73.6],[86.9,73.9],[85.8,73.4],[86.7,73.1],[85.9,73.4],[87.6,73.8],[85.8,74.6],[87,75.2],[94.2,76],[93.3,76.1],[99.5,75.8],[98.8,76.5],[101.6,76.4],[100.9,76.9],[104.2,77.7],[106.1,77.4],[104.2,77.1],[107.4,76.9],[106.4,76.5],[111.1,76.7],[113.9,75.9],[105.1,72.8],[110.9,73.7],[109.7,73.8],[110.3,74],[113,73.9],[113.7,72.6],[114.1,73.6],[122.5,72.9],[124.5,73.8],[129.1,73.1],[128.4,72.5],[129.3,72.1],[127.7,72.4],[131.2,70.7],[132.7,71.9],[137.9,71.1],[140,71.5],[139.4,72],[140.2,72.2],[139.1,72.3],[140.7,72.9],[144.3,72.6],[146.8,72.3],[145.1,71.7],[148.4,72.3],[152.5,70.8],[159.4,70.8],[160.9,69.6],[160.9,68.5],[162.4,69.6],[167.9,69.7],[169.6,68.8],[171,69],[170.5,70.1],[175.9,69.9],[184.7,67.7],[185.9,66.2],[185.5,67.1],[190.3,66.1],[187.2,65.7],[187.8,65],[186.8,64.3],[181.6,65.5],[180.9,66.4],[179.4,64.8],[174.5,64.7],[178.2,64.4],[179.6,62.8],[179.1,62.3],[177,62.8],[170.4,60],[169.2,60.6],[163.7,60],[162,58.1],[163.2,57.8],[163.3,56.2],[162.1,56.1],[162.1,54.8],[160.1,54.2],[160,53.1],[158.5,53],[156.7,51],[155.6,55.2],[156,56.8],[165.4,62.5],[163.3,62.6],[163.1,61.6],[160.2,60.6],[160.3,61.9],[157.1,61.7],[154.3,59.8],[155.2,59.2],[151.3,58.9],[152.3,59.2],[149.6,59.8],[142.6,59.2],[135.3,54.9],[136.8,54.6],[136.7,53.8],[137.7,54.3],[137.3,53.5],[139.8,54.3],[141.4,53.3],[140.8,53.1],[141.5,52.2],[140.1,48.4],[135.5,43.8],[133.2,42.7],[131.8,43.3],[127.6,39.8],[129.4,37.1],[129.2,35.2],[126.5,34.3],[126.6,37.8],[124.7,38.1],[125.6,38.7],[124.4,40],[121.2,38.7],[121.8,41],[117.8,39.1],[119.1,37.2],[122.7,37.4],[119.2,34.8],[121.9,31.8],[120,31.9],[121.9,30.9],[120.2,30.2],[122.1,29.9],[121.6,28.3],[119.1,26.1],[119.6,25.4],[116.5,22.9],[114.3,22.3],[113.5,23.1],[113.5,22.2],[110.4,21.3],[110.3,20.3],[109.9,21.5],[108.6,21.9],[106.7,21],[105.6,19],[108.8,15.4],[109.4,12.6],[109,11.3],[106.1,10.2],[106.6,9.6],[105.8,10],[106.2,9.4],[105.1,8.6],[105,10.1],[100.9,12.7],[101,13.4],[100,13.4],[99.3,9.3],[100.4,7.2],[103.2,5.3],[104.2,1.4],[101.3,2.9],[100.1,6.4],[98.3,8.2],[98.9,11.7],[97.7,16.6],[96.8,17.4],[95.4,15.7],[94.7,16.5],[94.2,16],[94,19.4],[90.6,23.6],[90.2,21.8],[90,22.5],[88.1,21.6],[87.9,22.4],[86.8,20.3],[80.3,15.7],[79.8,10.3],[77.5,8.1],[73.3,16.5],[72.8,22.2],[70.5,20.8],[69,22.3],[70.5,23.1],[68.8,23.1],[66.4,25.6],[57.8,25.7],[56.7,27.1],[54.6,26.5],[51.6,27.9],[50.1,30.2],[48,30],[50.8,24.8],[51.5,25.9],[51.3,24.3],[54.1,24.2],[56.4,26.4],[56.6,24.5],[59.8,22.3],[57.9,20.2],[57.8,19],[55.1,17],[43.9,12.6],[42.3,17.4],[39.3,21],[38.5,23.7],[34.6,28.1],[35,29.6],[34.2,27.8],[32.5,29.9],[39.5,15.5],[43.4,12.4],[42.5,11.5],[44.9,10.4],[50.8,12],[51.4,10.4],[48,4.5],[40.2,-2.7],[38.8,-6.1],[39.7,-10],[40.6,-10.7],[40.8,-14.8],[39.8,-16.4],[34.6,-19.7],[35.5,-24.1],[32.8,-25.6],[32.5,-28.2],[30.3,-31],[26.6,-33.7],[19.6,-34.8],[18.4,-34.2],[18.2,-31.7],[16.4,-28.6]],[[-57.2,5.5],[-54.4,5.9],[-51.2,4.1],[-49.9,1.2],[-52.7,-1.6],[-50.9,-0.9],[-50.4,-2],[-49.3,-1.7],[-49.6,-2.7],[-47.4,-0.6],[-44.7,-1.7],[-44.7,-3.2],[-43.4,-2.4],[-40,-2.9],[-35.4,-5.3],[-34.8,-8],[-38.9,-12.8],[-39.2,-17.7],[-41,-22],[-48.7,-25.4],[-48.8,-28.6],[-52,-32.1],[-50.6,-30.4],[-51.3,-30],[-54.2,-34.7],[-57.8,-34.5],[-58.2,-32.5],[-58.5,-34.3],[-56.7,-37],[-58.2,-38.4],[-62.3,-38.8],[-62.4,-40.9],[-65.1,-40.8],[-65,-42.1],[-63.6,-42.7],[-65,-42.7],[-64.3,-43],[-65.6,-45],[-67.6,-46.1],[-65.8,-47.9],[-67.9,-50],[-69,-50],[-68.4,-50.2],[-69.5,-51.6],[-68.4,-52.4],[-70.7,-52.7],[-71.7,-53.8],[-72.4,-53.4],[-71.2,-52.8],[-73.1,-53.2],[-71.5,-52.6],[-74,-52.6],[-74.3,-52.1],[-72.5,-52.3],[-75.1,-50.7],[-73.8,-50.9],[-74.6,-50.2],[-73.8,-49.6],[-74.6,-48],[-73.4,-48.1],[-74.7,-47.7],[-74.3,-46.8],[-75.7,-46.7],[-74,-45.4],[-73.8,-46.6],[-72.7,-44.6],[-73.3,-44.2],[-72.3,-41.5],[-73.5,-41.8],[-74,-41.1],[-73.2,-39.2],[-73.7,-37.3],[-71.5,-32.7],[-70.2,-19.7],[-71.4,-17.4],[-75.9,-14.6],[-78.8,-8.6],[-81.2,-5.9],[-81.3,-4.3],[-79.7,-2.6],[-80.9,-2.3],[-80.9,-1.1],[-77.1,3.9],[-78.4,8.1],[-77.8,8.1],[-79.4,9],[-80.8,7.2],[-85.6,9.9],[-87.5,13.4],[-91.4,14],[-94.4,16.3],[-96.5,15.7],[-103.4,18.3],[-105.5,20],[-105.8,22.6],[-109.3,25.6],[-113,31.2],[-114.9,31.9],[-114.6,30],[-109.4,23.5],[-110,22.9],[-112.1,24.8],[-112.4,26.2],[-115,27.7],[-114.1,27.7],[-114,28.4],[-115.7,29.8],[-117.5,33.3],[-120.6,34.6],[-122.5,37.5],[-121.5,38.1],[-123,38],[-123.7,38.9],[-124.5,42.8],[-124,46.2],[-123.2,46.2],[-124.1,46.3],[-124.7,48.4],[-122.8,48.1],[-122.6,47.1],[-122.9,49.4],[-124.7,50],[-124.9,50.9],[-127.7,51.2],[-126.7,51.7],[-127.8,51.7],[-126.7,52.1],[-127,52.8],[-128.1,51.8],[-128.1,52.9],[-129.2,53.5],[-127.9,53.3],[-128.5,53.9],[-130.3,53.7],[-129.6,55.5],[-130,55.1],[-130,55.9],[-130.5,54.7],[-131,56.1],[-132.1,55.6],[-131.6,56.2],[-133.5,57.2],[-133.9,58.5],[-135.4,59.4],[-135.1,58.2],[-137,59],[-136.1,58.5],[-136.6,58.2],[-139.8,59.5],[-139.4,60],[-144.1,60],[-147.8,61.2],[-148.5,60],[-151.9,59.3],[-151,59.8],[-151.9,59.8],[-151.3,60.7],[-149.1,60.9],[-149.6,61.5],[-153,60.3],[-154.2,59.2],[-153.4,58.8],[-158.5,56.1],[-163.3,54.8],[-157.5,57.5],[-156.8,59.1],[-162.1,58.6],[-162,60.7],[-163.7,59.8],[-165.4,60.5],[-163.4,60.8],[-166.1,61.8],[-164.4,63.2],[-161.1,63.6],[-161.5,64.4],[-160.9,64.8],[-166.1,64.6],[-166.9,65.2],[-166.2,65.3],[-168.1,65.7],[-164.5,66.6],[-161,66.2],[-162.4,66.9],[-160.2,66.5],[-166.8,68.4],[-156.5,71.4],[-135.3,68.7],[-135.9,69.1],[-129.5,70.1],[-133.4,68.8],[-132.7,68.8],[-128,70.6],[-125.7,69.4],[-124.6,70.2],[-124.3,69.4],[-121.7,69.8],[-114,68.4],[-115.1,67.8],[-110.1,68],[-107.3,66.4],[-108,67.8],[-105.7,68.6],[-108.7,68.3],[-106.2,68.9],[-102.3,67.7],[-96,68.2],[-96.4,67.5],[-95.4,67],[-96.4,67],[-95.8,66.6],[-95.5,68],[-93.4,68.6],[-94.6,68.8],[-93.5,69.5],[-96.6,70.2],[-95.9,70.5],[-96.4,71.2],[-94.9,72],[-91.6,70.2],[-92.9,69.7],[-90.4,69.5],[-91.2,69.3],[-90.2,68.3],[-89.3,69.3],[-88,68.8],[-88.3,67.9],[-87.4,67.2],[-84.9,68.8],[-85.5,69.8],[-82.4,69.6],[-81.3,69.1],[-82.6,68.4],[-81.5,67.1],[-83.4,66.4],[-85.1,66.9],[-83.9,66.2],[-86.7,66.5],[-86,66.1],[-87.3,65.4],[-91.4,65.9],[-87,65.2],[-88.7,64],[-93.7,64.1],[-90.7,63.1],[-93.2,62.4],[-95,59.1],[-94.3,58.3],[-93.2,58.7],[-92.8,56.9],[-90.6,57.2],[-85.4,55.1],[-82.4,55.1],[-81.8,52.2],[-79.3,50.8],[-79.7,51.3],[-78.9,51.2],[-78.4,52.3],[-79.7,54.7],[-76.5,56.4],[-76.9,57.8],[-78.5,58.7],[-77.3,60],[-78.2,60.8],[-77.5,61.6],[-78.1,62.4],[-73.7,62.5],[-69.5,61],[-69.7,60.1],[-70.7,60],[-69.3,59.3],[-70.2,58.8],[-68.4,58.7],[-69,57.9],[-66,58.4],[-64.5,60.3],[-62.9,58.7],[-63.5,58.3],[-62.6,58.5],[-63.3,58],[-61.3,57],[-62.5,56.8],[-60.3,55.8],[-60.6,55.1],[-57.4,54.6],[-60.3,53.3],[-57.4,54.2],[-57.3,53.5],[-56,53.5],[-55.7,52.1],[-60.1,50.3],[-66.5,50.2],[-69.7,48.2],[-71,48.5],[-69.8,48.1],[-74.7,45],[-68.8,48.4],[-65.5,49.3],[-64.3,48.5],[-66.7,48],[-64.7,47.7],[-65.3,47.1],[-64.5,46.2],[-61,45.3],[-65.5,43.5],[-66.1,44.5],[-63.4,45.4],[-64.9,45.4],[-64.6,45.9],[-68.8,44.6],[-71,42.3],[-69.9,41.7],[-74,41.2],[-74.9,38.9],[-75.5,39.8],[-75,38.4],[-75.9,37.2],[-75.9,39.5],[-76.6,39.3],[-76.3,38.1],[-77,38.9],[-76.3,37.1],[-77.3,37.3],[-75.5,35.8],[-75.9,36.7],[-76.7,36.2],[-75.8,35.8],[-77,35.5],[-76.4,34.8],[-80.8,32.4],[-81.4,31.2],[-80,26.8],[-80.5,25.2],[-81.1,25.1],[-82.7,27.5],[-82.7,28.9],[-84,30.1],[-90.2,30.4],[-89.4,29],[-93.8,30],[-97.8,27.5],[-97.1,26],[-97.8,22.5],[-95.9,18.8],[-94.5,18.2],[-91.3,18.6],[-89.9,21.3],[-86.8,21.4],[-88.9,15.9],[-85,16],[-83.4,15.2],[-83.9,11.3],[-82.1,8.9],[-79.1,9.5],[-76.8,7.9],[-75.2,10.8],[-71.5,12.4],[-71.7,9.1],[-71.1,9.3],[-71.5,11],[-69.8,11.5],[-70,12.2],[-68.1,10.5],[-61.9,10.7],[-62.9,10.5],[-60.8,9.4],[-61.6,8.6],[-59.2,8.1],[-58.7,6.4],[-58,6.8],[-57.2,5.5]],[[-152.9,57.8],[-152.2,57.6],[-154,56.8],[-154.7,57.3],[-152.9,57.8]],[[-155.6,19],[-155.8,20.3],[-154.8,19.5],[-155.6,19]],[[-37.1,-54.1],[-35.8,-54.8],[-38,-54],[-37.1,-54.1]],[[-58.9,-51.3],[-57.8,-51.6],[-59.5,-52.2],[-58.9,-51.3]],[[-2.7,51.6],[-5.3,51.9],[-4.1,52.3],[-4.3,53.1],[-2.7,53.3],[-3.6,54.6],[-3,55],[-5.1,54.9],[-4.8,56.2],[-5.8,55.4],[-5.2,56.8],[-6.1,56.7],[-5,58.6],[-3.1,58.6],[-4.1,57.6],[-1.9,57.6],[-3.3,56.4],[-2.7,56.3],[-3.8,56.1],[-2.1,55.9],[-0.1,54.1],[-0.7,53.7],[0,52.9],[1.7,52.6],[0.4,51.5],[1.4,51.4],[1,50.9],[-5.7,50.1],[-2.7,51.6]],[[-7.2,55.1],[-5.5,54.5],[-6.3,54],[-6.3,52.2],[-10.1,51.6],[-9.6,51.9],[-10.4,52.2],[-8.8,52.7],[-9.9,52.6],[-8.9,53.2],[-10.1,53.4],[-10.1,54.3],[-7.2,55.1]],[[124.4,-9.2],[127.3,-8.4],[123.6,-10.3],[124.4,-9.2]],[[121,22.6],[120.8,21.9],[120.1,23.7],[121.6,25.3],[121,22.6]],[[80,9.8],[81.9,7.3],[80.7,6],[79.9,6.8],[80,9.8]],[[50.3,80.9],[51.7,80.7],[46.6,80.3],[50.3,80.9]],[[67.8,76.2],[61.4,75.3],[56.6,73.3],[53.8,73.8],[58.9,75.9],[68.9,76.7],[67.8,76.2]],[[55.3,73.3],[56.4,73.2],[55.3,71.9],[57.6,70.7],[53.7,70.8],[51.4,71.8],[53.2,73.2],[55.3,73.3]],[[96.5,81.1],[97.9,80.7],[91.5,80.4],[96.5,81.1]],[[97.7,80.2],[99.9,79.8],[99,79.3],[99.9,79],[93.1,79.5],[97.7,80.2]],[[102.9,79.3],[102.4,78.8],[105.3,78.6],[99.3,78],[102.9,79.3]],[[140.1,75.8],[145.4,75.5],[144,75],[142.3,75.7],[143.1,75],[139.1,74.7],[136.9,75.3],[138.2,76.1],[140.1,75.8]],[[146.8,75.4],[150.8,75.2],[146.1,75.2],[146.8,75.4]],[[142.8,54.4],[144.7,48.6],[143.1,49.2],[142.6,47.7],[143.6,46.4],[142.6,46.7],[142.1,45.9],[141.7,52.3],[142.8,54.4]],[[180,71.5],[182.5,71.2],[178.6,71],[180,71.5]],[[54.7,81.1],[57.7,80.8],[54.1,80.8],[54.7,81.1]],[[142.2,73.9],[143.5,73.2],[139.7,73.4],[142.2,73.9]],[[121.1,18.6],[122.3,18.5],[122.5,17.1],[121.4,15.3],[121.8,14.2],[123.8,13.8],[124.1,12.6],[122.6,13.9],[122.6,13.2],[120.6,13.8],[120.9,14.6],[120.1,14.9],[119.8,16.3],[120.4,16.1],[121.1,18.6]],[[117.3,8.4],[119.6,11.3],[119.7,10.5],[117.3,8.4]],[[122.5,11.6],[123.1,11.3],[122,10.4],[121.9,11.9],[122.5,11.6]],[[123.1,9.1],[122.4,9.8],[123.3,11],[123.1,9.1]],[[125.2,12.5],[125.7,11.1],[124.3,12.6],[125.2,12.5]],[[126,9.3],[126.6,7.2],[126.2,6.3],[125.7,7.3],[125.3,5.6],[123.7,7.8],[121.9,7.1],[123.4,8.7],[123.8,8],[125.5,9.8],[126,9.3]],[[151.9,-4.3],[152.1,-5.5],[150.5,-6.3],[148.3,-5.7],[150.7,-5.5],[151.9,-4.3]],[[141,-2.6],[147.6,-6.1],[147.2,-7.4],[150.6,-10.6],[147.8,-10.1],[144.9,-7.6],[142.2,-8.2],[143.4,-8.8],[142.6,-9.3],[141.1,-9.2],[140.1,-7.9],[138.9,-8.3],[138.3,-5.7],[134,-3.8],[133.8,-3.1],[133,-4.1],[132,-2.8],[133.9,-2.1],[131,-1.4],[132.4,-0.4],[134,-0.7],[135.3,-3.4],[137.8,-1.5],[141,-2.6]],[[15.8,68.6],[16.5,68.7],[14.3,68.2],[16.1,69.3],[15.8,68.6]],[[21.6,78.6],[24.9,77.8],[21.1,77.4],[21.7,77.9],[20.2,78.5],[21.6,78.6]],[[20.9,80.2],[27.2,79.9],[23.9,79.2],[17.9,80.1],[20.9,80.2]],[[16.8,79.9],[21.4,78.7],[19.2,78.4],[16.7,76.6],[14,77.5],[16.9,77.9],[13.7,78],[16.8,78.7],[13.1,78.2],[10.7,79.8],[14.6,79.8],[16.3,79],[15.8,79.7],[16.8,79.9]],[[173.1,-41.3],[174.4,-41.1],[172.5,-43.5],[173.1,-43.8],[171.2,-44.3],[170.8,-45.9],[169.1,-46.6],[166.5,-46],[171,-42.9],[172.3,-40.8],[172.9,-40.5],[173.1,-41.3]],[[173.3,-34.9],[176.1,-37.6],[178.5,-37.7],[175.3,-41.6],[174.6,-41.3],[175.2,-40.2],[173.8,-39.3],[174.9,-37.1],[172.7,-34.5],[173.3,-34.9]],[[109.6,2],[111.2,1.4],[111.5,2.7],[115.4,5],[116.8,7],[119.3,5.4],[117.1,3.6],[119,1],[117.9,1.1],[116.3,-3.1],[114.7,-4.2],[114.3,-3.2],[110.3,-3],[108.9,0.4],[109.6,2]],[[49.5,-12.4],[50.4,-15.6],[49.7,-15.5],[47.2,-24.8],[45.1,-25.5],[44,-25],[43.3,-22.4],[44.4,-19.9],[44,-17.4],[44.5,-16.2],[48,-14.7],[47.9,-13.7],[49.5,-12.4]],[[143.8,44.1],[145.4,44.3],[145.8,43.4],[143.2,42],[140.5,42.6],[141.2,41.8],[140,41.5],[139.9,42.6],[141.4,43.3],[141.7,45.4],[143.8,44.1]],[[131.2,33.6],[132,32.9],[130.7,31],[130.5,32.8],[129.6,33.2],[131.2,33.6]],[[134.4,34.3],[134.2,33.2],[132.8,32.8],[132,33.3],[134.4,34.3]],[[141.2,41.4],[142,39.4],[140.4,35.2],[136.8,35.1],[135.7,33.5],[134.7,34.8],[130.9,34],[132.9,35.5],[135.9,35.6],[136.8,37.4],[137.2,36.8],[138.3,37.2],[139.8,38.9],[140,40.7],[141.2,41.4]],[[-77.3,18.5],[-76.2,17.9],[-78.3,18.3],[-77.3,18.5]],[[15.6,38.2],[15.1,36.7],[12.4,37.8],[15.6,38.2]],[[9.6,40.9],[9.6,39.2],[8.6,38.9],[8.2,40.9],[9.6,40.9]],[[106,-1.7],[106.7,-3.1],[105.1,-2],[106,-1.7]],[[96.5,5.2],[97.5,5.2],[103,0.6],[102.5,0.2],[103.7,0.3],[104.7,-2.6],[106,-3.1],[105.7,-5.8],[104.6,-5.9],[101.6,-3.2],[95.2,5.6],[96.5,5.2]],[[120,-9.4],[120.8,-10.1],[119,-9.5],[120,-9.4]],[[118.2,-8.3],[119.1,-8.7],[116.8,-9],[118.2,-8.3]],[[124.9,1],[120.2,0.3],[120.7,-1.4],[123.4,-0.8],[121.4,-1.9],[122.9,-4.4],[121.6,-4.8],[120.7,-2.7],[120.4,-5.6],[119.6,-5.6],[118.8,-2.7],[120.3,1],[123.8,0.8],[125.1,1.7],[124.9,1]],[[107.4,-6],[112.5,-6.9],[114.4,-7.8],[114.6,-8.8],[105.3,-6.8],[106.1,-5.9],[107.4,-6]],[[127.7,0.8],[128.7,1.6],[128.3,0.7],[128.9,0.2],[127.9,0.3],[128.4,-0.9],[127.4,1.1],[128,2.2],[127.7,0.8]],[[129.8,-2.9],[130.8,-3.9],[127.9,-3.5],[129.8,-2.9]],[[-15.5,66.2],[-14.6,66.4],[-15.1,66.1],[-13.6,65.1],[-18.7,63.4],[-22.7,63.8],[-21.6,64.6],[-24,64.9],[-21.8,65.4],[-24.5,65.5],[-22.4,65.9],[-22.9,66.4],[-21.1,65.3],[-20.2,66.1],[-15.5,66.2]],[[-71.8,18],[-74.5,18.4],[-72.4,18.6],[-73.4,19.7],[-72.9,19.9],[-70,19.7],[-68.3,18.6],[-71.8,18]],[[23.4,39],[24.6,38],[22.9,38.9],[23.4,39]],[[9.5,42.8],[9.2,41.4],[8.6,42.4],[9.5,42.8]],[[69.2,-49.1],[70.6,-49.2],[68.8,-49.7],[69.2,-49.1]],[[-30,83.6],[-25.8,83.3],[-32,83],[-21.5,82.6],[-29.9,82.1],[-21.1,81.9],[-23.1,80.8],[-19.6,81.6],[-11.4,81.5],[-20.2,80],[-19,79.2],[-21.1,78.7],[-21.7,77.7],[-18.3,76.9],[-22.6,76.7],[-19.9,76.1],[-19.5,75.2],[-22.2,75.1],[-19.3,74.3],[-22.3,74.3],[-20.6,73.5],[-25.5,73.9],[-24.8,73.5],[-27.6,73.1],[-22,72.9],[-22.3,72.1],[-24.6,73],[-26.7,72.7],[-24.8,72.9],[-25.2,72.4],[-22,71.7],[-22.5,71.4],[-21.8,71.5],[-21.5,70.5],[-27.1,71.6],[-25.7,71.3],[-29.1,70.4],[-22.2,70.1],[-26.5,68.7],[-32.3,68.4],[-34.6,66.4],[-40.2,65.6],[-39.6,65.3],[-41.1,65],[-40.2,64.5],[-41.6,64.3],[-40.5,63.7],[-42.9,62.7],[-42.1,61.9],[-43.9,59.8],[-46,60.6],[-45.9,61.2],[-48.2,60.8],[-49.3,61.6],[-48.8,62.1],[-50.3,62.5],[-49.8,63],[-51.5,64],[-50.3,64.2],[-51.7,64.2],[-50.1,64.7],[-51,65.2],[-51.9,64.2],[-52.5,65.3],[-51.1,65.8],[-53.4,66],[-51.2,66.9],[-53.6,66.2],[-52.4,66.9],[-53.8,67.4],[-50.6,67.5],[-53.7,67.5],[-51.2,68.4],[-53,68.6],[-50.3,69.2],[-51.1,69.2],[-50.3,70],[-54.5,70.7],[-50.7,70.4],[-53.1,71.3],[-51.8,71.7],[-53.4,71.6],[-53.7,72.4],[-54,71.5],[-55.6,71.6],[-54.7,72.9],[-58.5,75.7],[-68.3,76.1],[-69.5,76.4],[-68.1,76.7],[-71.2,77.1],[-66.3,77.5],[-72.8,78.2],[-65.8,79.2],[-64.2,80.1],[-67.2,80.3],[-60.4,81.9],[-56.6,81.4],[-59.3,82],[-49.5,81.9],[-51,82.5],[-50,82.5],[-44.7,81.8],[-44.3,82.5],[-45.6,82.7],[-41.4,82.8],[-46.5,83],[-30,83.6]],[[-52.7,69.9],[-51.9,69.6],[-54.9,69.7],[-52.7,69.9]],[[12.6,55.8],[11.9,54.8],[11,55.7],[12.6,55.8]],[[32.7,35.2],[34.6,35.7],[33,34.6],[32.7,35.2]],[[-81.8,23.2],[-74.2,20.2],[-77.7,19.9],[-77.2,20.6],[-81.8,22.7],[-84.9,21.9],[-81.8,23.2]],[[110.9,20],[110,18.4],[108.7,18.5],[109.3,19.9],[110.9,20]],[[-68.7,-54.9],[-71.9,-54.5],[-70.3,-54.5],[-70.5,-53.6],[-69,-54.4],[-70.2,-53.9],[-69.4,-53.4],[-70.5,-53.2],[-69.4,-52.5],[-65.2,-54.7],[-68.7,-54.9]],[[-127.2,50.6],[-125.5,50.3],[-123.3,48.4],[-128.4,50.7],[-127.2,50.6]],[[-110.5,78.1],[-109.6,78.1],[-112,77.3],[-113.3,77.8],[-110.5,78.1]],[[-115.6,77.4],[-117.2,76.3],[-122.9,76.1],[-115.6,77.4]],[[-108.3,76.1],[-105.5,75.7],[-112.5,74.4],[-114.4,74.7],[-111,75.2],[-117.6,75.3],[-115.1,75.7],[-117.2,75.6],[-115,75.9],[-116.7,76],[-115,76.5],[-109.1,75.5],[-110.3,76.4],[-108.8,76.8],[-108.3,76.1]],[[-114.5,72.6],[-110,73],[-107.8,71.6],[-107.3,71.9],[-108.2,73.2],[-106.5,73.2],[-104.5,71.1],[-100.9,69.8],[-103.4,69.7],[-101.8,69.2],[-102.9,68.8],[-106.7,69.4],[-113,68.5],[-117.2,70.1],[-111.6,70.3],[-117.6,70.6],[-118.4,71],[-115.3,71.5],[-119,71.8],[-116.6,73.1],[-114.2,73.3],[-114.5,72.6]],[[-119.7,74.1],[-115.4,73.5],[-122.8,71.1],[-125.8,72],[-123.8,73.8],[-124.7,74.3],[-119.7,74.1]],[[-69.5,83],[-61.2,82.3],[-68.7,81.3],[-64.8,81.5],[-72.1,80.1],[-70.6,80.1],[-71.4,79.8],[-76.9,79.5],[-74.5,79.1],[-78.6,79.1],[-74.4,78.7],[-78.5,77.4],[-82.1,77.3],[-78,76.9],[-80.8,76.2],[-89.6,76.5],[-86.8,77.2],[-88,77.8],[-84.5,77.4],[-82.6,78],[-84.9,77.5],[-85.5,77.9],[-84.2,78.2],[-84.8,78.5],[-87.6,78.2],[-86.8,78.8],[-81.8,79],[-84.4,79],[-83.6,79.1],[-86.5,80.3],[-80.5,79.6],[-83,80.3],[-76.9,80.9],[-78.7,81],[-76.9,81.4],[-85.1,80.5],[-86.6,80.6],[-83.3,81.1],[-87.7,80.7],[-89.3,80.9],[-84.9,81.3],[-91.7,81.6],[-84.9,82.4],[-79.4,81.9],[-82.4,82.4],[-78.7,82.7],[-79.9,82.9],[-69.5,83]],[[-93.5,75],[-96.6,75.1],[-94.9,75.6],[-93.5,75]],[[-100,73.9],[-97.1,73.8],[-98.4,73],[-96.5,72.7],[-96.6,71.8],[-99.2,71.4],[-102.7,72.8],[-100.1,72.9],[-101.5,73.5],[-100,73.9]],[[-84.9,65.3],[-80.3,63.8],[-87.2,63.6],[-85.8,65.8],[-84.9,65.3]],[[-97.7,76.5],[-97.3,75.4],[-98,75],[-102.8,75.6],[-101,75.8],[-101.8,76.5],[-97.7,76.5]],[[-103.4,79.3],[-99,78],[-104.5,78.3],[-103.4,78.7],[-105.6,79.1],[-103.4,79.3]],[[-91.9,81.1],[-85,79.3],[-91.4,78.2],[-94.2,79],[-91.3,79.4],[-95.1,79.3],[-95.7,79.4],[-94.4,79.7],[-96.8,80.1],[-94.3,80.2],[-96.4,80.3],[-93.2,81.1],[-94.2,81.3],[-91.9,81.1]],[[-94.3,76.9],[-89.3,76.3],[-91.4,76.2],[-88.9,75.5],[-82.2,75.8],[-79.4,74.9],[-91.5,74.7],[-93.1,76.4],[-96.9,76.8],[-94.3,76.9]],[[-96.2,78.5],[-94.9,78.1],[-97,77.8],[-98.3,78.8],[-96.2,78.5]],[[-93.2,74.2],[-90.4,73.9],[-95.2,72],[-95.6,73.7],[-93.2,74.2]],[[-97.4,69.6],[-95.3,68.8],[-99.6,69],[-97.4,69.6]],[[-61.1,45.9],[-59.8,46],[-61.5,45.7],[-60.5,47],[-61.1,45.9]],[[-55.5,51.5],[-56.8,49.6],[-56.2,50.1],[-55.4,49.1],[-53.6,49.3],[-54.1,48.4],[-53,48.6],[-53.9,48],[-52.9,48.1],[-53.1,46.7],[-54.2,46.9],[-54.2,47.9],[-55.8,46.9],[-54.8,47.7],[-59.3,47.6],[-57,51],[-55.5,51.5]],[[-86.6,71],[-84.8,71],[-85.9,72],[-84.3,72],[-85.7,72.7],[-84.3,72.8],[-85.5,73.1],[-81.6,73.7],[-80.3,72.8],[-80.9,71.9],[-77.3,72.7],[-75.1,72.3],[-75.9,71.7],[-74.3,72],[-75,71.2],[-71.6,71.5],[-72.6,70.8],[-70.7,71.1],[-71.9,70.4],[-71.4,70.1],[-68.5,70.6],[-70.1,70],[-67.9,70.3],[-67.2,69.8],[-69.3,69.5],[-66.7,69.3],[-69.3,68.9],[-61.3,66.6],[-63.5,65.9],[-63.6,64.9],[-65.4,65.8],[-64.4,66.3],[-68.7,66.2],[-64.4,63.7],[-65.2,63.8],[-65.1,62.6],[-68.9,63.7],[-66.3,61.9],[-71.3,63.1],[-73.3,64.6],[-78.2,64.6],[-77.3,65.5],[-75.5,64.8],[-75.8,65.3],[-73.6,65.5],[-74.4,66.2],[-72.2,67.3],[-73.9,68.7],[-76.6,68.7],[-75.6,69.2],[-79.1,70.6],[-79.1,69.9],[-81.7,70.1],[-80.9,69.7],[-87.9,70.3],[-89.5,71.1],[-87.1,71],[-90,71.9],[-88.7,73.4],[-85,73.8],[-86.7,72.8],[-85,71.4],[-86.6,71]],[[-63.8,46.5],[-62,46.4],[-64.4,46.7],[-63.8,46.5]],[[-75.7,68.3],[-75.4,67.4],[-77.3,67.7],[-75.7,68.3]],[[-79.5,73.7],[-76.1,72.9],[-79.5,72.8],[-80.9,73.7],[-79.5,73.7]],[[-105.3,72.9],[-106.9,73.5],[-104.6,73.6],[-105.3,72.9]],[[-49.6,-0.2],[-48.4,-0.3],[-48.8,-1.4],[-50.5,-1.8],[-50.6,-0.3],[-49.6,-0.2]],[[143.2,-12],[143.8,-14.3],[145.3,-14.9],[146.4,-19],[148.8,-20.3],[149.7,-22.4],[150.7,-22.4],[153.2,-26],[153.6,-28.7],[152.9,-31.4],[149.9,-37.5],[146.4,-39.1],[144.9,-37.9],[143.5,-38.8],[141.4,-38.4],[139.8,-37.2],[139.3,-35.4],[138.2,-35.6],[138.1,-34.2],[136.9,-35.2],[137.8,-32.6],[135.6,-34.9],[134.2,-32.5],[131.1,-31.5],[125.9,-32.3],[123.5,-33.9],[119.9,-34],[117.9,-35.1],[115,-34.3],[115.7,-31.7],[113.2,-26.2],[113.8,-26.5],[113.4,-25.6],[114.2,-26.3],[113.4,-24.4],[114,-21.9],[114.1,-22.5],[116.7,-20.7],[121,-19.6],[122.3,-17.1],[123,-16.4],[123.6,-17.5],[123.6,-16.2],[124.8,-16.4],[124.4,-15.5],[126.1,-14],[127.5,-14],[128.1,-15.3],[129.6,-15.1],[130.6,-12.4],[132.7,-12.1],[132,-11.2],[136.5,-12],[135.5,-14.9],[140.5,-17.6],[142.5,-10.7],[143.2,-12]],[[145,-40.8],[148.2,-40.9],[148,-43.2],[146,-43.5],[145,-40.8]],[[-180,-84.4],[-156.5,-85.2],[-174.2,-82.8],[-159.4,-83.5],[-153.4,-82.6],[-154.7,-81.9],[-154,-81.7],[-157,-81.3],[-148.1,-80.9],[-150.6,-80.4],[-148.1,-79.9],[-156.1,-78.7],[-154.3,-78.3],[-157.8,-78.1],[-158.2,-77.2],[-145.7,-77.5],[-145.7,-76.7],[-149.7,-76.4],[-135.4,-74.7],[-121.5,-74.7],[-114.6,-73.9],[-113.5,-74.1],[-114.1,-75],[-111.2,-74.2],[-110.2,-74.5],[-111.4,-75.2],[-101,-75.4],[-98.6,-75.2],[-102.8,-73.6],[-98.9,-73.6],[-103.4,-72.8],[-90.9,-73.3],[-88.8,-72.7],[-82.2,-73.9],[-80.4,-72.9],[-77,-73.8],[-68.8,-73.1],[-66.8,-72.1],[-68.7,-69.4],[-67,-69.2],[-66.7,-67.6],[-67.5,-67.1],[-66.5,-67.3],[-63.8,-65],[-57.4,-63.2],[-56.8,-63.6],[-62.1,-65.3],[-62,-66.1],[-60.6,-66],[-61,-66.3],[-63.8,-66.3],[-65.5,-67.4],[-65.2,-68.6],[-62.9,-68.4],[-63.8,-68.6],[-61,-71.2],[-62.3,-72],[-60,-72.9],[-62,-73.1],[-60.8,-73.7],[-61.8,-74.3],[-60.7,-74.3],[-70.2,-76.7],[-77.3,-76.7],[-72.9,-77.6],[-74,-78.1],[-80.6,-77.8],[-81.6,-77.8],[-77.5,-78.6],[-83.8,-78.5],[-80.9,-79.5],[-76.2,-79.4],[-79.7,-80],[-75.1,-80.9],[-62.4,-81.6],[-66.1,-82],[-60.5,-82.2],[-62.7,-82.5],[-59.5,-83.5],[-54,-82.2],[-45.8,-82.5],[-38.8,-80.9],[-23.6,-80],[-36.3,-78.6],[-28.9,-76.4],[-18.3,-75.4],[-17.4,-74.4],[-14.6,-73.9],[-16.4,-73.4],[-11.5,-72.4],[-11,-71.8],[-12.3,-71.4],[-10.3,-70.9],[-8.6,-71.7],[-5.9,-70.7],[-6.1,-71.3],[-0.5,-71.7],[13.1,-70.1],[26.5,-71],[32.6,-70],[32.6,-68.9],[33.5,-68.7],[38.9,-70.2],[40.2,-68.8],[46.6,-67.3],[48.4,-68],[49.2,-67.2],[48.5,-67],[50.6,-67.2],[50.3,-66.4],[53.7,-65.9],[57.2,-66.6],[56.2,-67.3],[69.6,-67.8],[70,-68.5],[69.1,-69.9],[67.3,-70.3],[69.2,-70.4],[66.5,-73.1],[67.3,-73.3],[73.3,-69.8],[84.5,-67.1],[99.4,-66.6],[102.7,-65.9],[109.5,-66.9],[113.1,-65.8],[115.6,-66.8],[114,-67.4],[120.4,-67],[119.1,-67.4],[125.9,-66.4],[129,-67.1],[133.4,-66.1],[143.7,-66.9],[144.6,-67.1],[144,-67.9],[146,-67.6],[147.1,-68.4],[153.9,-68.3],[159.8,-69.5],[162.2,-71],[162.7,-70.3],[170.4,-71.4],[170.2,-72.6],[168.4,-72.4],[169.5,-73.1],[166.5,-72.9],[167.7,-73.4],[164.8,-73.4],[165.4,-74.6],[160.9,-75.3],[162.8,-75.8],[162.4,-77],[164.5,-78],[164,-78.2],[167.1,-78.7],[161.7,-78.5],[162,-79],[159.9,-79.8],[160.6,-80],[158.6,-80.4],[160.6,-80.4],[160.5,-81.3],[163.7,-82.2],[161.3,-82.5],[180,-84.4],[180,-90],[-180,-90],[-180,-84.4]],[[-45.2,-78.8],[-43.6,-78.8],[-43,-79.8],[-54.4,-80.8],[-50.3,-79.5],[-49.1,-78],[-43.8,-78.3],[-45.2,-78.8]],[[-59.7,-80.3],[-60.6,-80.9],[-66.8,-80.3],[-60.6,-79.7],[-59.7,-80.3]],[[-70.1,-69.2],[-68.3,-70.9],[-69.2,-72.5],[-75.4,-71.8],[-69.9,-71.1],[-71.2,-70.7],[-69.6,-70.4],[-71.7,-70.1],[-72.1,-69.1],[-70.1,-69.2]],[[-98.1,-71.9],[-95.5,-72.2],[-102.3,-72.1],[-98.1,-71.9]],[[-120.6,-73.8],[-122.3,-74.4],[-123.3,-73.8],[-120.6,-73.8]],[[-126.3,-73.3],[-123.8,-74.2],[-127.4,-73.4],[-126.3,-73.3]],[[-159.1,-79.8],[-164.3,-79.2],[-162.4,-78.8],[-159.1,-79.8]],[[167.1,-77.3],[169.4,-77.5],[166.2,-77.5],[167.1,-77.3]],[[-70.3,-79.7],[-71.8,-79.4],[-66.7,-78.4],[-70.3,-79.7]],[[-68,-67.5],[-69.1,-67.5],[-67.7,-66.6],[-68,-67.5]]]; diff --git a/apps/postcards/tests/unit/antimeridian.spec.tsx b/apps/postcards/tests/unit/antimeridian.spec.tsx index b950780..fe705f4 100644 --- a/apps/postcards/tests/unit/antimeridian.spec.tsx +++ b/apps/postcards/tests/unit/antimeridian.spec.tsx @@ -8,6 +8,7 @@ import type { FeatureCollection, MultiPolygon, Polygon, Position } from "geojson import { unwrapAntimeridian } from "../../src/features/map/antimeridian"; import { getLand } from "../../src/features/travel/landGeometry"; import { CountryCoverageMap } from "../../src/features/stats/CountryCoverageMap"; +import { LAND_OUTLINE } from "../../src/lib/publish/landOutline"; import type { Visit } from "../../src/lib/schema/models"; import { useVisits } from "../../src/lib/store/useVisits"; @@ -150,3 +151,11 @@ describe("route and coverage maps: the land they share", () => { useVisits.setState({ visits: [] }); }); }); + +describe("published reader: the embedded land outline", () => { + it("has no edge spanning the map", () => { + const rs = LAND_OUTLINE as Position[][]; + expect(widestEdge(rs)).toBeLessThan(180); + expect(seams(rs)).toBe(0); + }); +}); From 72749c3152b32d36c4f9bd2898b9ae762bcad0ef Mon Sep 17 00:00:00 2001 From: davd-gzl <60177543+davd-gzl@users.noreply.github.com> Date: Wed, 30 Sep 2026 23:53:47 +0900 Subject: [PATCH 5/5] Passport: fill Antarctica to the pole on the world poster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The poster unwrapped each ring on its own, so Antarctica's coast closed straight across at 84°S and the bottom of the poster showed sea under the continent. It now draws the geometry unwrapped once at load, closed through the pole. --- .../postcards/src/features/passport/poster.ts | 35 ++++------------ .../tests/unit/antimeridian.spec.tsx | 42 +++++++++++++++++++ 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/apps/postcards/src/features/passport/poster.ts b/apps/postcards/src/features/passport/poster.ts index 92256f0..96818f5 100644 --- a/apps/postcards/src/features/passport/poster.ts +++ b/apps/postcards/src/features/passport/poster.ts @@ -7,6 +7,7 @@ import type { FeatureCollection, MultiPolygon, Polygon, Position } from "geojson import type { ReferenceData } from "../../lib/reference/types"; import { CONTINENT_COLORS } from "../../lib/reference/continents"; import { countryFlag } from "../../lib/format/format"; +import { unwrapAntimeridian } from "../map/antimeridian"; const GEOMETRY_URL = `${import.meta.env.BASE_URL}basemap/countries-50m.json`; @@ -18,25 +19,6 @@ function project([lon, lat]: Position): [number, number] { return [((lon + 180) / 360) * W, ((90 - lat) / 180) * MAP_H]; } -/** - * Unwrap a ring's longitudes into a continuous sequence (may run past ±180). - * Natural Earth stores Russia/Fiji with rings that jump across the antimeridian; - * projecting those jumps linearly smears a fill band across the whole map. - */ -function unwrapRing(ring: Position[]): Position[] { - let prev: number | null = null; - let off = 0; - return ring.map(([lon, lat]) => { - if (prev !== null) { - while (lon! + off - prev > 180) off -= 360; - while (lon! + off - prev < -180) off += 360; - } - const l = lon! + off; - prev = l; - return [l, lat!]; - }); -} - function drawRing(ctx: CanvasRenderingContext2D, ring: Position[], lonShift = 0): void { ring.forEach((pt, i) => { const [x, y] = project([pt[0]! + lonShift, pt[1]!]); @@ -46,10 +28,9 @@ function drawRing(ctx: CanvasRenderingContext2D, ring: Position[], lonShift = 0) ctx.closePath(); } -/** Draw one ring, duplicated ±360° when it runs past the map edge after - * unwrapping, so an antimeridian-crossing shape appears on both sides. */ -function drawWrappedRing(ctx: CanvasRenderingContext2D, rawRing: Position[]): void { - const ring = unwrapRing(rawRing); +/** Draw one ring, duplicated ±360° when it runs past the map edge (the rings + * arrive unwrapped), so an antimeridian-crossing shape appears on both sides. */ +function drawWrappedRing(ctx: CanvasRenderingContext2D, ring: Position[]): void { let minLon = Infinity; let maxLon = -Infinity; for (const [lon] of ring as [number, number][]) { @@ -67,7 +48,7 @@ function flagAnchor(geom: Polygon | MultiPolygon): [number, number] { let best: [number, number] | null = null; let bestArea = -1; for (const p of polys) { - const ring = unwrapRing(p[0]!); + const ring = p[0]!; let minX = Infinity, maxX = -Infinity, minY = 90, maxY = -90; for (const [x, y] of ring as [number, number][]) { minX = Math.min(minX, x); maxX = Math.max(maxX, x); @@ -103,9 +84,9 @@ export async function renderPoster( if (!res.ok) throw new Error("map geometry unavailable"); // eslint-disable-next-line @typescript-eslint/no-explicit-any const topo: any = await res.json(); - const fc = feature(topo, topo.objects.countries) as unknown as FeatureCollection< - Polygon | MultiPolygon - >; + const fc = unwrapAntimeridian( + feature(topo, topo.objects.countries) as unknown as FeatureCollection, + ); const canvas = document.createElement("canvas"); canvas.width = W; diff --git a/apps/postcards/tests/unit/antimeridian.spec.tsx b/apps/postcards/tests/unit/antimeridian.spec.tsx index fe705f4..7ba4bac 100644 --- a/apps/postcards/tests/unit/antimeridian.spec.tsx +++ b/apps/postcards/tests/unit/antimeridian.spec.tsx @@ -8,7 +8,9 @@ import type { FeatureCollection, MultiPolygon, Polygon, Position } from "geojson import { unwrapAntimeridian } from "../../src/features/map/antimeridian"; import { getLand } from "../../src/features/travel/landGeometry"; import { CountryCoverageMap } from "../../src/features/stats/CountryCoverageMap"; +import { renderPoster } from "../../src/features/passport/poster"; import { LAND_OUTLINE } from "../../src/lib/publish/landOutline"; +import type { ReferenceData } from "../../src/lib/reference/types"; import type { Visit } from "../../src/lib/schema/models"; import { useVisits } from "../../src/lib/store/useVisits"; @@ -159,3 +161,43 @@ describe("published reader: the embedded land outline", () => { expect(seams(rs)).toBe(0); }); }); + +describe("world poster", () => { + it("fills Antarctica down to the pole", async () => { + // A 2D context that records every filled path. + const filled: [number, number][][][] = []; + let path: [number, number][][] = []; + const ctx = new Proxy({} as Record, { + get(t, k) { + if (k in t) return t[k as string]; + if (k === "beginPath") return () => (path = []); + if (k === "moveTo") return (x: number, y: number) => path.push([[x, y]]); + if (k === "lineTo") return (x: number, y: number) => path[path.length - 1]!.push([x, y]); + if (k === "fill") return () => filled.push(path); + return () => {}; + }, + set(t, k, v) { + t[k as string] = v; + return true; + }, + }); + vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockReturnValue(ctx as never); + vi.spyOn(HTMLCanvasElement.prototype, "toBlob").mockImplementation((cb) => cb(new Blob())); + const ref = { countryByNumeric: () => undefined } as unknown as ReferenceData; + await renderPoster(new Set(), ref, { countries: 0, cities: 0 }); + + // Longitude 0, latitude -87 on the 2000 x 1000 equirectangular map. + const [px, py] = [1000, ((90 + 87) / 180) * 1000]; + const inside = (subs: [number, number][][]) => { + let odd = false; + for (const s of subs) + for (let i = 0, j = s.length - 1; i < s.length; j = i++) { + const [xi, yi] = s[i]!; + const [xj, yj] = s[j]!; + if (yi > py !== yj > py && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) odd = !odd; + } + return odd; + }; + expect(filled.some(inside)).toBe(true); + }); +});