From ed877b21d8569ff58803e27bf0b9072e21603b83 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Wed, 9 Sep 2026 00:07:19 +0000 Subject: [PATCH 1/2] A world map, and clicking a country to select it ui.worldMap({ onSelect: country => console.log(country?.name) }); The map is the canvas doing what it already does. Once it draws in the caller's own coordinates, longitude and latitude are just another pair of axes, so the world is 258 polylines in degrees and needs no projection of its own beyond choosing which window on the globe to show. The half worth building is the other direction. A click arrives as a terminal cell, and a country is a polygon, so answering "what did they click" means turning the cell back into degrees and ray-casting it against the outlines. Doing it with bounding boxes would be far cheaper and consistently wrong: Russia's box covers most of the northern hemisphere and Chile's covers Argentina. The lookup is exact, and open water comes back as nothing rather than as whichever box happened to be first. The inverse has to agree with the drawing or every click lands somewhere the user did not point at -- and nothing about the map would look wrong while it did. So `degreesAt` takes the cell centre through the same 2x4 Braille denominators the canvas projects with, and a test round-trips all 2600 cells of a 100x26 map back to themselves. The data is Natural Earth 1:110m Admin 0, which is public domain, simplified by Douglas-Peucker to one degree. A world map across eighty columns is about 160 Braille pixels wide, so a pixel is two degrees and one degree of tolerance is already finer than the terminal can show; it keeps 171 of the 177 countries in 1982 points. The six it drops are island states smaller than a pixel, which could be neither seen nor clicked. `scripts/generate-world.ts` fetches and regenerates it, so the 36KB of coordinates is derived rather than pasted. Rings are drawn closed, so a coastline has no seam where the ring began. TypeScript only. The shape and the lookup port cleanly, but the dataset would be a seventh copy in every port, and that is a call about repo weight rather than about the feature -- so it is not in the widget gallery either, which promises a runnable example in all eleven languages. `bun examples/world.ts` for the clickable version: hover, click, z to zoom to the selection, r to go back. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy --- examples/world.ts | 77 ++ packages/hqtui/scripts/generate-world.ts | 154 +++ packages/hqtui/src/graphics/index.ts | 1 + packages/hqtui/src/graphics/world-data.ts | 1307 +++++++++++++++++++++ packages/hqtui/src/graphics/world.ts | 171 +++ packages/hqtui/src/index.ts | 3 + packages/hqtui/src/ui.ts | 28 + packages/hqtui/src/widgets/index.ts | 1 + packages/hqtui/src/widgets/world.ts | 61 + packages/hqtui/test/world.test.ts | 182 +++ 10 files changed, 1985 insertions(+) create mode 100644 examples/world.ts create mode 100644 packages/hqtui/scripts/generate-world.ts create mode 100644 packages/hqtui/src/graphics/world-data.ts create mode 100644 packages/hqtui/src/graphics/world.ts create mode 100644 packages/hqtui/src/widgets/world.ts create mode 100644 packages/hqtui/test/world.test.ts diff --git a/examples/world.ts b/examples/world.ts new file mode 100644 index 0000000..9c23511 --- /dev/null +++ b/examples/world.ts @@ -0,0 +1,77 @@ +/** + * A clickable world map: `bun examples/world.ts` + * + * Click a country to select it, hover to see what is under the cursor, and + * press z to zoom to the selection or r to go back to the whole globe. + * + * The map is the canvas drawing polylines in degrees, which is all a map is + * once the canvas works in the caller's own coordinates. The clicking is the + * other direction: the cell under the cursor is turned back into a longitude + * and latitude and tested against the outlines, so the answer is the country + * actually there rather than whichever bounding box happened to be first. + */ +import { createApp, countryBounds, findCountry, WORLD_X, WORLD_Y } from "@profullstack/hqtui"; +import type { CountryOutline, Bounds } from "@profullstack/hqtui"; + +let selected: CountryOutline | undefined; +let hovered: CountryOutline | undefined; +let window: { x: Bounds; y: Bounds } = { x: WORLD_X, y: WORLD_Y }; + +const app = await createApp({ title: "hqtui · world" }); + +app.render(({ ui, theme }) => { + const shown = hovered ?? selected; + const zoomed = window.x !== WORLD_X; + + ui.panel( + { + title: "World", + subtitle: shown ? shown.name : "click a country", + footer: zoomed ? "z zoom · r reset · q quit" : "z zoom to selection · q quit", + }, + (p) => { + p.worldMap({ + ...window, + color: theme.border, + highlight: [selected?.iso || selected?.name || "", hovered?.iso || hovered?.name || ""], + highlightColor: theme.accent, + onSelect: (country) => { + selected = country; + app.invalidate(); + }, + onHover: (country) => { + if (country?.name !== hovered?.name) { + hovered = country; + app.invalidate(); + } + }, + }); + + p.row({ size: 1, gap: 2 }, (row) => { + row.keyValues( + [ + { label: "Selected", value: selected?.name ?? "—" }, + { label: "ISO", value: selected?.iso || "—" }, + ], + { spread: false }, + ); + }); + }, + ); +}); + +app.on("key", (key) => { + if (key.name === "z" && selected) { + window = countryBounds(selected); + app.invalidate(); + } + if (key.name === "r") { + window = { x: WORLD_X, y: WORLD_Y }; + app.invalidate(); + } +}); + +// Somewhere to start from, so the first frame is not an empty selection. +selected = findCountry("Japan"); + +await app.start(); diff --git a/packages/hqtui/scripts/generate-world.ts b/packages/hqtui/scripts/generate-world.ts new file mode 100644 index 0000000..d574c87 --- /dev/null +++ b/packages/hqtui/scripts/generate-world.ts @@ -0,0 +1,154 @@ +/** + * Generates `src/graphics/world-data.ts` from Natural Earth's country polygons. + * + * bun packages/hqtui/scripts/generate-world.ts + * + * The source is Natural Earth 1:110m Admin 0 countries, which is public domain + * . It is fetched rather + * than committed: the raw file is 800KB of GeoJSON and this only needs running + * when the borders change, which is roughly never. + * + * The output is committed. Regenerating it should be a no-op unless the + * tolerance below changes. + */ +import { writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const SOURCE = + "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_110m_admin_0_countries.geojson"; + +/** + * Douglas-Peucker tolerance, in degrees. + * + * A world map across an eighty-column terminal is about 160 Braille pixels + * wide, so one pixel is a bit over two degrees. At one degree the simplified + * outline is already finer than anything a terminal can show, and it keeps 171 + * of the 177 countries; the six it drops are island states smaller than a pixel, + * which could be neither seen nor clicked. + */ +const TOLERANCE = 1.0; + +/** A country whose bounding box is smaller than this cannot be drawn or hit. */ +const MIN_SPAN = 1.5; + +type Point = [number, number]; + +function perpendicular(p: Point, a: Point, b: Point): number { + const [px, py] = p; + const [ax, ay] = a; + const [bx, by] = b; + const dx = bx - ax; + const dy = by - ay; + if (dx === 0 && dy === 0) return Math.hypot(px - ax, py - ay); + const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy))); + return Math.hypot(px - (ax + t * dx), py - (ay + t * dy)); +} + +/** Douglas-Peucker, iterative so a long coastline cannot blow the stack. */ +function simplify(points: Point[], tolerance: number): Point[] { + if (points.length < 3) return points; + const keep = new Array(points.length).fill(false); + keep[0] = true; + keep[points.length - 1] = true; + const stack: [number, number][] = [[0, points.length - 1]]; + while (stack.length > 0) { + const [lo, hi] = stack.pop() as [number, number]; + let worst = 0; + let at = -1; + for (let i = lo + 1; i < hi; i++) { + const d = perpendicular(points[i], points[lo], points[hi]); + if (d > worst) { + worst = d; + at = i; + } + } + if (worst > tolerance && at !== -1) { + keep[at] = true; + stack.push([lo, at], [at, hi]); + } + } + return points.filter((_, i) => keep[i]); +} + +interface Feature { + properties: Record; + geometry: { type: string; coordinates: number[][][] | number[][][][] }; +} + +const response = await fetch(SOURCE); +if (!response.ok) throw new Error(`${SOURCE} responded ${response.status}`); +const collection = (await response.json()) as { features: Feature[] }; + +const countries: { name: string; iso: string; rings: Point[][] }[] = []; +for (const feature of collection.features) { + const props = feature.properties; + const name = String(props.NAME ?? props.NAME_LONG ?? ""); + const iso = String(props.ISO_A2_EH ?? props.ISO_A2 ?? ""); + if (!name) continue; + + const geometry = feature.geometry; + const polygons = (geometry.type === "MultiPolygon" + ? geometry.coordinates + : [geometry.coordinates]) as number[][][][]; + + const rings: Point[][] = []; + for (const polygon of polygons) { + // Only the outer ring. A lake inside a country is not a border at this + // resolution, and carrying the holes doubles the data for nothing. + const ring = polygon[0].map(([x, y]) => [x, y] as Point); + const xs = ring.map((p) => p[0]); + const ys = ring.map((p) => p[1]); + if (Math.max(...xs) - Math.min(...xs) < MIN_SPAN && + Math.max(...ys) - Math.min(...ys) < MIN_SPAN) continue; + const cut = simplify(ring, TOLERANCE); + if (cut.length >= 3) { + rings.push(cut.map(([x, y]) => [Math.round(x * 10) / 10, Math.round(y * 10) / 10] as Point)); + } + } + if (rings.length > 0) countries.push({ name, iso, rings }); +} + +countries.sort((a, b) => a.name.localeCompare(b.name)); + +const points = countries.reduce((n, c) => n + c.rings.reduce((m, r) => m + r.length, 0), 0); +const body = countries + .map((c) => { + const rings = c.rings + .map((ring) => `[${ring.map(([x, y]) => `${x},${y}`).join(",")}]`) + .join(",\n "); + return ` {\n name: ${JSON.stringify(c.name)},\n iso: ${JSON.stringify(c.iso)},\n` + + ` rings: [\n ${rings},\n ],\n },`; + }) + .join("\n"); + +const out = `/** + * Country outlines, flattened for a terminal. + * + * Generated by \`packages/hqtui/scripts/generate-world.ts\` from Natural Earth's + * 1:110m Admin 0 countries, which is public domain. Do not edit by hand. + * + * Each ring is a flat list of longitude and latitude pairs -- \`[lon, lat, + * lon, lat, ...]\` rather than an array of pairs, because at ${points} points the + * nested form costs an object per coordinate for no gain. A country has more + * than one ring when it is more than one landmass. + */ + +export interface CountryOutline { + name: string; + /** ISO 3166-1 alpha-2, where Natural Earth has one. */ + iso: string; + /** Longitude and latitude, interleaved. */ + rings: number[][]; +} + +/** ${countries.length} countries, ${points} points, simplified at ${TOLERANCE} degrees. */ +export const WORLD_COUNTRIES: readonly CountryOutline[] = [ +${body} +]; +`; + +const here = dirname(fileURLToPath(import.meta.url)); +const target = join(here, "..", "src", "graphics", "world-data.ts"); +writeFileSync(target, out); +console.log(`${countries.length} countries, ${points} points -> ${target}`); diff --git a/packages/hqtui/src/graphics/index.ts b/packages/hqtui/src/graphics/index.ts index 3feeadb..c0a8c29 100644 --- a/packages/hqtui/src/graphics/index.ts +++ b/packages/hqtui/src/graphics/index.ts @@ -2,4 +2,5 @@ export { BrailleCanvas } from "./braille.ts"; export * from "./blocks.ts"; export * from "./plot.ts"; export * from "./canvas.ts"; +export * from "./world.ts"; export * from "./chart.ts"; diff --git a/packages/hqtui/src/graphics/world-data.ts b/packages/hqtui/src/graphics/world-data.ts new file mode 100644 index 0000000..058f9ee --- /dev/null +++ b/packages/hqtui/src/graphics/world-data.ts @@ -0,0 +1,1307 @@ +/** + * Country outlines, flattened for a terminal. + * + * Generated by `packages/hqtui/scripts/generate-world.ts` from Natural Earth's + * 1:110m Admin 0 countries, which is public domain. Do not edit by hand. + * + * Each ring is a flat list of longitude and latitude pairs -- `[lon, lat, + * lon, lat, ...]` rather than an array of pairs, because at 1982 points the + * nested form costs an object per coordinate for no gain. A country has more + * than one ring when it is more than one landmass. + */ + +export interface CountryOutline { + name: string; + /** ISO 3166-1 alpha-2, where Natural Earth has one. */ + iso: string; + /** Longitude and latitude, interleaved. */ + rings: number[][]; +} + +/** 171 countries, 1982 points, simplified at 1 degrees. */ +export const WORLD_COUNTRIES: readonly CountryOutline[] = [ + { + name: "Afghanistan", + iso: "AF", + rings: [ + [66.5,37.4,70.8,38.5,71.8,36.7,75.2,37.1,71.3,36.1,69.3,31.9,66.3,29.9,60.9,29.8,61.2,35.7,66.5,37.4], + ], + }, + { + name: "Albania", + iso: "AL", + rings: [ + [21,40.8,19.4,40.3,19.7,42.7,21,40.8], + ], + }, + { + name: "Algeria", + iso: "DZ", + rings: [ + [-8.7,27.4,-8.7,28.8,-1.3,32.3,-1.2,35.7,8.4,36.9,7.5,34.1,9.8,29.4,9.3,26.1,12,23.5,3.2,19.1,-8.7,27.4], + ], + }, + { + name: "Angola", + iso: "AO", + rings: [ + [12.3,-6.1,16.3,-5.9,17.5,-8.1,21.7,-7.3,22.2,-11.1,24,-11.2,24,-12.9,21.9,-12.9,23.2,-17.5,11.7,-17.3,13.7,-11.3,12.3,-6.1], + ], + }, + { + name: "Antarctica", + iso: "AQ", + rings: [ + [-48.7,-78,-43.9,-78.5,-43.3,-80,-54.2,-80.6,-48.7,-78], + [-66.3,-80.3,-59.6,-80,-66.3,-80.3], + [-73.9,-71.3,-70.3,-68.9,-68.3,-71.4,-75,-72.1,-73.9,-71.3], + [-102.3,-71.9,-96.2,-72.5,-102.3,-71.9], + [-122.6,-73.7,-118.7,-73.5,-122.6,-73.7], + [-127.3,-73.5,-124,-73.9,-127.3,-73.5], + [-163.7,-78.6,-159.2,-79.5,-163.7,-78.6], + [180,-84.7,180,-90,-180,-90,-179.1,-84.1,-143.1,-85,-153.6,-83.7,-152.9,-82,-156.8,-81.1,-146.4,-80.3,-155.3,-79.1,-158.4,-76.9,-151.3,-77.4,-146.1,-76.5,-146.2,-75.4,-135.2,-74.3,-100.1,-74.9,-103.7,-72.6,-74.9,-73.9,-67.4,-72.5,-67.7,-67.3,-57.8,-63.3,-65.7,-68,-61.8,-70.7,-60.8,-73.7,-70.6,-76.6,-77.2,-76.7,-73.7,-77.9,-78,-79.2,-58.2,-83.2,-28.5,-80.3,-35.6,-79.5,-35.8,-78.3,-17.5,-75.1,-15.4,-73.1,-6.9,-70.9,27.1,-70.5,33.9,-68.5,38.6,-69.8,54.5,-65.8,61.4,-68,68.9,-67.9,69.7,-69.2,67.9,-71.9,69.9,-72.3,73.9,-69.9,88,-66.2,95.8,-67.4,102.8,-65.6,106.2,-66.9,113.6,-65.9,119.8,-67.3,135.1,-65.3,137.5,-67,145.5,-66.9,171.2,-71.7,163.6,-76.2,167,-78.8,161.8,-79.2,159.8,-80.9,169.4,-83.8,180,-84.7], + ], + }, + { + name: "Argentina", + iso: "AR", + rings: [ + [-68.6,-52.6,-65,-54.7,-68.6,-54.9,-68.6,-52.6], + [-57.6,-30.2,-58.5,-34.4,-56.8,-36.9,-62.3,-38.8,-62.7,-41,-65.1,-41.1,-63.5,-42.6,-67.3,-45.6,-65.6,-47.2,-69.1,-50.7,-68.1,-52.3,-71.9,-52,-73.4,-49.3,-71.2,-44.8,-72.1,-42.3,-68.4,-24.5,-66.3,-21.8,-62.8,-22,-57.8,-25.2,-58.6,-27.1,-55.7,-27.4,-54.1,-25.5,-53.6,-26.9,-57.6,-30.2], + ], + }, + { + name: "Armenia", + iso: "AM", + rings: [ + [46.5,38.8,43.6,41.1,45.6,40.8,46.5,38.8], + ], + }, + { + name: "Australia", + iso: "AU", + rings: [ + [147.7,-40.8,147.9,-43.2,146,-43.5,144.7,-40.7,147.7,-40.8], + [126.1,-32.2,118,-35.1,115,-34.2,113.7,-22.5,120.9,-19.7,125.7,-14.2,129.6,-15,132.4,-11.1,136.5,-11.9,135.5,-15,140.2,-17.7,142.5,-10.7,146.4,-19,150.7,-22.4,153.6,-28.1,150,-37.4,146.3,-39,140.6,-38,138.2,-34.4,136.8,-35.3,137.8,-32.9,136,-34.9,131.3,-31.5,126.1,-32.2], + ], + }, + { + name: "Austria", + iso: "AT", + rings: [ + [17,48.1,14.6,46.4,9.5,47.1,12.9,47.5,13.6,48.9,17,48.1], + ], + }, + { + name: "Azerbaijan", + iso: "AZ", + rings: [ + [46.4,41.9,50.4,40.3,48.9,38.3,45.6,39.9,45,41.2,46.4,41.9], + ], + }, + { + name: "Bahamas", + iso: "BS", + rings: [ + [-78.2,25.2,-77.5,23.8,-78.2,25.2], + ], + }, + { + name: "Bangladesh", + iso: "BD", + rings: [ + [92.7,22,92.4,20.7,91.4,22.8,89,22.1,88.6,26.4,92.4,25,91.2,23.5,92.7,22], + ], + }, + { + name: "Belarus", + iso: "BY", + rings: [ + [28.2,56.2,30.9,55.6,32.7,53.4,31.8,52.1,23.5,51.6,23.5,53.9,28.2,56.2], + ], + }, + { + name: "Belgium", + iso: "BE", + rings: [ + [6.2,50.8,5.7,49.5,2.5,51.1,6.2,50.8], + ], + }, + { + name: "Belize", + iso: "BZ", + rings: [ + [-89.1,17.8,-88.1,18.3,-88.9,15.9,-89.1,17.8], + ], + }, + { + name: "Benin", + iso: "BJ", + rings: [ + [2.7,6.3,0.8,10.5,2.8,12.2,2.7,6.3], + ], + }, + { + name: "Bhutan", + iso: "BT", + rings: [ + [91.7,27.8,88.8,27.1,91.7,27.8], + ], + }, + { + name: "Bolivia", + iso: "BO", + rings: [ + [-69.5,-11,-65.3,-9.8,-65.4,-11.6,-60.5,-13.8,-60.2,-16.3,-58.2,-16.3,-57.9,-20,-61.8,-19.6,-62.7,-22.2,-67.8,-22.9,-69.5,-11], + ], + }, + { + name: "Bosnia and Herz.", + iso: "BA", + rings: [ + [18.6,42.7,16,45.2,19.4,44.9,18.6,42.7], + ], + }, + { + name: "Botswana", + iso: "BW", + rings: [ + [29.4,-22.1,25.7,-25.5,21.6,-26.7,19.9,-24.8,20.9,-18.3,25.3,-17.7,29.4,-22.1], + ], + }, + { + name: "Brazil", + iso: "BR", + rings: [ + [-53.4,-33.8,-53.8,-32,-57.6,-30.2,-53.6,-26.1,-55.8,-22.4,-57.9,-22.1,-58.2,-16.3,-60.2,-16.3,-60.5,-13.8,-65.4,-11.6,-65.3,-9.8,-70.5,-11,-70.5,-9.5,-72.2,-10.1,-74,-7.5,-72.9,-5.3,-69.9,-4.3,-69.8,1.7,-65.5,0.8,-63.4,2.2,-64.8,4.1,-60.7,5.2,-59,1.3,-52.9,2.1,-51.3,4.2,-50.4,-0.1,-44.6,-2.7,-40,-2.9,-35.6,-5.1,-34.7,-7.3,-38.7,-13.1,-40.9,-21.9,-47.6,-24.9,-48.9,-28.7,-53.4,-33.8], + ], + }, + { + name: "Bulgaria", + iso: "BG", + rings: [ + [22.7,44.2,28.6,43.7,28,42,23,41.3,22.7,44.2], + ], + }, + { + name: "Burkina Faso", + iso: "BF", + rings: [ + [-5.4,10.4,-4.3,13.2,-1.1,15,2.2,12.6,0.9,11,-2.9,11,-2.8,9.6,-5.4,10.4], + ], + }, + { + name: "Burundi", + iso: "BI", + rings: [ + [30.5,-2.4,29.3,-4.5,29,-2.8,30.5,-2.4], + ], + }, + { + name: "Cambodia", + iso: "KH", + rings: [ + [102.6,12.2,103,14.2,107.6,13.5,106.2,11,103.5,10.6,102.6,12.2], + ], + }, + { + name: "Cameroon", + iso: "CM", + rings: [ + [14.5,12.9,14.5,4.7,15.9,1.7,9.6,2.3,8.8,5.5,11.7,7,14.5,12.9], + ], + }, + { + name: "Canada", + iso: "CA", + rings: [ + [-122.8,49,-127.4,50.8,-130.5,54.3,-130,55.9,-135.5,59.8,-137.5,58.9,-141,60.3,-141,69.7,-136.5,68.9,-128.1,70.5,-113.5,67.7,-106.1,68.8,-101.5,67.6,-97.7,68.6,-96.1,67.3,-94.2,69.1,-96.5,70.1,-95.2,71.9,-87.4,67.2,-85.5,69.9,-82.6,69.7,-81.4,67.1,-85.8,66.6,-90.7,63.6,-94.7,58.9,-92.3,57.1,-82.3,55.1,-79.9,51.2,-78.6,52.6,-79.8,54.7,-76.5,56.5,-78.5,58.8,-77.3,59.9,-78.1,62.3,-73.8,62.4,-69.6,61.1,-67.6,58.2,-64.6,60.3,-61.8,56.3,-57.3,54.6,-55.7,52.1,-60,50.2,-66.4,50.2,-71.1,46.8,-65.1,49.2,-64.5,46.2,-60.5,47,-59.8,45.9,-65.4,43.5,-66.2,44.5,-64.4,45.3,-67.1,45.1,-69.2,47.4,-71.5,45,-82.4,41.7,-82.6,45.3,-88.4,48.3,-122.8,49], + [-84,62.5,-81.9,62.9,-84,62.5], + [-79.8,72.8,-80.8,73.7,-76.3,72.8,-79.8,72.8], + [-93.6,75,-96.8,74.9,-93.6,75], + [-93.8,77.5,-96.4,77.8,-93.8,77.5], + [-96.8,78.8,-95.6,78.4,-98.6,78.9,-96.8,78.8], + [-88.2,74.4,-97.1,76.8,-79.8,74.9,-88.2,74.4], + [-111.3,78.2,-109.9,78,-113.5,77.7,-111.3,78.2], + [-111,78.8,-109.7,78.6,-112.5,78.4,-111,78.8], + [-55.6,51.3,-56.8,49.8,-53.5,49.2,-53.1,46.7,-59.3,47.6,-55.6,51.3], + [-83.9,65.1,-80.1,63.7,-87.2,63.5,-85.9,65.7,-83.9,65.1], + [-78.8,72.4,-68.8,70.5,-67,69.2,-68.8,68.7,-61.9,66.9,-63.9,65,-68,66.3,-64.7,63.4,-68.8,63.7,-66.2,61.9,-68.9,62.3,-78.6,64.6,-74,65.5,-73.3,68.1,-79,70.2,-88.7,70.4,-90.2,72.2,-85.8,73.8,-85.8,72.5,-82.3,73.8,-78.8,72.4], + [-94.5,74.1,-90.5,73.9,-95.4,72.1,-96,73.4,-94.5,74.1], + [-122.9,76.1,-116.2,77.6,-122.9,76.1], + [-132.7,54,-131.2,52.2,-132.7,54], + [-105.5,79.3,-99.7,77.9,-105.5,79.3], + [-123.5,48.5,-128.4,50.8,-123.5,48.5], + [-121.5,74.4,-115.5,73.5,-123.1,70.9,-125.9,71.9,-123.9,73.7,-124.9,74.3,-121.5,74.4], + [-107.8,75.8,-105.7,75.5,-117.7,75.2,-115.4,76.5,-107.8,75.8], + [-106.5,73.1,-101.1,69.6,-113.3,68.5,-117.3,70,-112.4,70.4,-119.4,71.6,-115.2,73.3,-108.2,71.7,-108.4,73.1,-106.5,73.1], + [-100.4,72.7,-101.5,73.4,-97.4,73.8,-96.5,72.6,-98.4,71.3,-102.5,72.5,-100.4,72.7], + [-106.6,73.6,-104.5,73.4,-106.6,73.6], + [-98.5,76.7,-98.2,75,-102.5,75.6,-98.5,76.7], + [-96,80.6,-92.4,81.3,-85.8,79.3,-92.9,78.3,-96,80.6], + [-91.6,81.9,-61.8,82.6,-76.9,79.3,-75.4,78.5,-80.6,76.2,-89.5,76.5,-88.3,77.9,-85,77.5,-88,78.4,-85.1,79.3,-86.9,80.3,-81.8,80.5,-91.6,81.9], + [-75.2,67.4,-77.2,67.6,-75.2,67.4], + [-96.3,69.5,-99.8,69.4,-96.3,69.5], + [-64.5,49.9,-61.8,49.1,-64.5,49.9], + [-64,47,-62,46.4,-64,47], + ], + }, + { + name: "Central African Rep.", + iso: "CF", + rings: [ + [27.4,5.2,22.4,4,19.5,5,16,2.3,14.5,5.5,15.3,7.4,22.9,11.1,27.4,5.2], + ], + }, + { + name: "Chad", + iso: "TD", + rings: [ + [23.8,19.6,23.9,15.6,21.9,12.6,22.9,11.1,15.3,7.4,13.5,14.4,15.9,20.4,14.9,22.9,23.8,19.6], + ], + }, + { + name: "Chile", + iso: "CL", + rings: [ + [-68.6,-52.6,-68.6,-54.9,-67,-54.9,-68.1,-55.6,-74.7,-52.8,-71.1,-54.1,-68.6,-52.6], + [-69.6,-17.6,-67,-23,-70.5,-31.4,-69.8,-34.2,-72.1,-42.3,-71.2,-44.8,-73.4,-49.3,-71.9,-52,-68.6,-52.3,-71.4,-53.9,-74.9,-52.3,-75.6,-48.7,-74.1,-46.9,-75.6,-46.6,-72.7,-42.4,-74.3,-43.2,-69.6,-17.6], + ], + }, + { + name: "China", + iso: "CN", + rings: [ + [109.5,18.2,108.6,19.4,110.8,20.1,109.5,18.2], + [80.3,42.3,80,44.9,87.8,49.3,91,46.9,90.9,45.3,96.3,42.7,109.2,42.5,111.9,45.1,119.7,46.7,115.5,48.1,122.2,53.4,125.9,52.8,131,47.8,135,48.5,133.1,45.1,131,45,130.6,42.4,121.1,38.9,121.6,40.9,117.5,38.7,122.4,37.5,119.2,34.9,121.9,31.7,121.7,28.2,118.7,24.5,110.4,20.3,105.3,23.4,101.7,22.3,101.8,21.2,99.2,22.1,97.6,23.9,98.7,27.5,96.1,29.5,88.8,27.3,78.7,31.5,78.9,34.3,73.7,39.4,80.3,42.3], + ], + }, + { + name: "Colombia", + iso: "CO", + rings: [ + [-66.9,1.3,-69.8,1.7,-69.9,-4.3,-70,-2.7,-77.4,0.4,-79,1.7,-77.1,3.8,-77.5,8.5,-71.4,12.4,-73.3,9.2,-72,7,-67.3,6.1,-66.9,1.3], + ], + }, + { + name: "Congo", + iso: "CG", + rings: [ + [18.5,3.5,16,-3.5,11.9,-5,11.5,-2.8,14.4,-1.3,13.1,2.3,15.9,1.7,18.5,3.5], + ], + }, + { + name: "Costa Rica", + iso: "CR", + rings: [ + [-82.5,9.6,-83,8.2,-85.9,10.9,-82.5,9.6], + ], + }, + { + name: "Côte d'Ivoire", + iso: "CI", + rings: [ + [-8,10.2,-2.8,9.6,-2.9,5,-7.7,4.4,-8,10.2], + ], + }, + { + name: "Croatia", + iso: "HR", + rings: [ + [16.6,46.5,19.4,45.2,15.8,44.8,18.5,42.5,13.7,45.1,16.6,46.5], + ], + }, + { + name: "Cuba", + iso: "CU", + rings: [ + [-82.3,23.2,-74.2,20.3,-77.8,19.9,-81.8,22.6,-85,21.9,-82.3,23.2], + ], + }, + { + name: "Cyprus", + iso: "CY", + rings: [ + [32.7,35.1,34,35,32.7,35.1], + ], + }, + { + name: "Czechia", + iso: "CZ", + rings: [ + [15,51.1,18.9,49.5,12.5,49.5,15,51.1], + ], + }, + { + name: "Dem. Rep. Congo", + iso: "CD", + rings: [ + [29.3,-4.5,30.7,-8.3,28.7,-8.5,28.4,-11.8,29.7,-13.3,22.2,-11.1,21.7,-7.3,17.5,-8.1,16.3,-5.9,12.2,-5.8,16,-3.5,19.5,5,29.7,4.6,31.2,2.2,29.3,-4.5], + ], + }, + { + name: "Denmark", + iso: "DK", + rings: [ + [9.9,55,8.1,56.5,10.6,57.7,9.9,55], + [12.4,56.1,12.1,54.8,11,55.4,12.4,56.1], + ], + }, + { + name: "Djibouti", + iso: "DJ", + rings: [ + [42.4,12.5,42.8,10.9,42.4,12.5], + ], + }, + { + name: "Dominican Rep.", + iso: "DO", + rings: [ + [-71.7,18,-71.6,19.9,-68.3,18.6,-71.7,18], + ], + }, + { + name: "Ecuador", + iso: "EC", + rings: [ + [-75.4,-0.2,-78.6,-4.5,-80.4,-4.4,-80.1,0.8,-75.4,-0.2], + ], + }, + { + name: "Egypt", + iso: "EG", + rings: [ + [36.9,22,25,22,25.2,31.6,34.3,31.2,34.2,27.8,32.3,29.8,36.9,22], + ], + }, + { + name: "El Salvador", + iso: "SV", + rings: [ + [-89.4,14.4,-87.9,13.1,-90.1,13.7,-89.4,14.4], + ], + }, + { + name: "Eq. Guinea", + iso: "GQ", + rings: [ + [9.6,2.3,11.3,1.1,9.5,1,9.6,2.3], + ], + }, + { + name: "Eritrea", + iso: "ER", + rings: [ + [36.4,14.4,38.4,18,43.1,12.7,36.4,14.4], + ], + }, + { + name: "Estonia", + iso: "EE", + rings: [ + [28,59.5,27.3,57.5,23.3,59.2,28,59.5], + ], + }, + { + name: "eSwatini", + iso: "SZ", + rings: [ + [32.1,-26.7,31,-25.7,32.1,-26.7], + ], + }, + { + name: "Ethiopia", + iso: "ET", + rings: [ + [47.8,8,45,5,39.6,3.4,36.2,4.4,33,7.8,37.9,15,41.6,13.5,43.7,9.2,47.8,8], + ], + }, + { + name: "Falkland Is.", + iso: "FK", + rings: [ + [-61.2,-51.8,-57.7,-51.5,-61.2,-51.8], + ], + }, + { + name: "Finland", + iso: "FI", + rings: [ + [28.6,69.1,31.1,62.4,28.1,60.5,21.3,60.7,21.5,63.2,25.4,65.1,20.6,69.1,24.7,68.6,27.7,70.2,28.6,69.1], + ], + }, + { + name: "Fr. S. Antarctic Lands", + iso: "TF", + rings: [ + [68.9,-48.6,70.6,-49.3,68.7,-49.8,68.9,-48.6], + ], + }, + { + name: "France", + iso: "FR", + rings: [ + [-51.7,4.2,-52.9,2.1,-54.5,2.3,-54,5.8,-51.7,4.2], + [6.2,49.5,8.1,49,6,46.7,7.4,43.7,1.8,42.3,-1.9,43.4,-1.2,46,-4.6,48.7,-1.6,48.6,-1.9,49.8,2.5,51.1,6.2,49.5], + [8.7,42.6,9.2,41.4,8.7,42.6], + ], + }, + { + name: "Gabon", + iso: "GA", + rings: [ + [11.3,2.3,14.3,1.2,14.4,-1.3,11.1,-4,8.8,-1.1,11.3,2.3], + ], + }, + { + name: "Gambia", + iso: "GM", + rings: [ + [-16.7,13.6,-13.8,13.5,-16.7,13.6], + ], + }, + { + name: "Georgia", + iso: "GE", + rings: [ + [40,43.4,46.6,41.2,41.6,41.5,40,43.4], + ], + }, + { + name: "Germany", + iso: "DE", + rings: [ + [14.1,53.8,15,51.1,12.2,50.3,12.9,47.5,7.5,47.6,8.1,49,6,50.1,7.1,53.7,9.9,55,14.1,53.8], + ], + }, + { + name: "Ghana", + iso: "GH", + rings: [ + [0,11,1.1,5.9,-2.9,5,-2.9,11,0,11], + ], + }, + { + name: "Greece", + iso: "GR", + rings: [ + [26.3,35.3,23.5,35.3,26.3,35.3], + [23,41.3,26.6,41.6,22.6,40.3,24,37.7,22.5,36.4,20.2,39.6,23,41.3], + ], + }, + { + name: "Greenland", + iso: "GL", + rings: [ + [-46.8,82.6,-27.1,83.5,-20.8,82.7,-31.9,82.2,-12.2,81.3,-20,80.2,-17.7,80.1,-19.7,78.8,-18.5,77,-21.7,76.6,-19.4,74.3,-24.8,72.3,-21.8,70.7,-25.5,71.4,-26.4,70.2,-22.3,70.1,-39.8,65.5,-43.4,60.1,-48.3,60.9,-51.6,63.6,-54,67.2,-50.9,69.9,-54.7,69.6,-54.4,70.8,-51.4,70.6,-55.8,71.7,-54.7,72.6,-58.6,75.5,-68.5,76.1,-71.4,77,-66.8,77.4,-73.3,78,-65.7,79.4,-68,80.1,-62.7,81.8,-44.5,81.7,-46.8,82.6], + ], + }, + { + name: "Guatemala", + iso: "GT", + rings: [ + [-92.2,14.5,-90.5,16.1,-91,17.8,-89.1,17.8,-88.2,15.7,-89.4,14.4,-92.2,14.5], + ], + }, + { + name: "Guinea", + iso: "GN", + rings: [ + [-13.7,12.6,-9.1,12.3,-8.3,7.7,-11.1,10,-13.2,8.9,-15.1,11,-13.7,12.6], + ], + }, + { + name: "Guinea-Bissau", + iso: "GW", + rings: [ + [-16.7,12.4,-13.7,11.8,-15.1,11,-16.7,12.4], + ], + }, + { + name: "Guyana", + iso: "GY", + rings: [ + [-56.5,1.9,-59.6,1.8,-61.4,6,-59.8,8.4,-57.1,6,-58,4.1,-56.5,1.9], + ], + }, + { + name: "Haiti", + iso: "HT", + rings: [ + [-71.7,19.7,-71.7,18,-74.5,18.3,-71.7,19.7], + ], + }, + { + name: "Honduras", + iso: "HN", + rings: [ + [-83.1,15,-87.3,13,-89.4,14.4,-87.9,15.9,-83.1,15], + ], + }, + { + name: "Hungary", + iso: "HU", + rings: [ + [22.1,48.4,21,46.3,16.2,46.9,17,48.1,22.1,48.4], + ], + }, + { + name: "Iceland", + iso: "IS", + rings: [ + [-14.5,66.5,-13.6,65.1,-18.7,63.5,-22.8,64,-21.8,64.4,-24,64.9,-22.2,65.4,-24.3,65.6,-14.5,66.5], + ], + }, + { + name: "India", + iso: "IN", + rings: [ + [97.3,28.3,92.7,22,91.2,23.5,92.4,25,88.6,26.4,88.9,21.7,80.3,15.9,79.9,10.4,77.5,8,72.6,21.4,70.5,20.9,68.2,23.7,71,24.4,69.5,26.9,75.3,32.3,73.7,34.3,77.8,35.5,78.7,31.5,81.1,30.2,80.1,28.8,83.3,27.4,88.1,26.4,88.7,28.1,92,26.8,96.1,29.5,97.3,28.3], + ], + }, + { + name: "Indonesia", + iso: "ID", + rings: [ + [141,-2.6,141,-9.1,137.6,-8.4,137.9,-5.4,133,-4.1,132,-2.8,133.7,-2.2,130.5,-0.9,134,-0.8,135.5,-3.4,137.4,-1.7,141,-2.6], + [125,-8.9,123.5,-10.2,125,-8.9], + [117.9,4.1,119,0.9,116.1,-4,110.2,-2.9,109.7,2,110.5,0.8,113.8,1.2,115.9,4.3,117.9,4.1], + [129.4,-2.8,130.8,-3.9,127.9,-3.4,129.4,-2.8], + [127.9,2.2,128.1,-0.9,127.9,2.2], + [122.9,0.9,125.2,1.4,120,-0.5,123.3,-0.6,121.5,-1.9,123.2,-5.3,121,-2.6,119.8,-5.7,118.8,-2.8,119.8,0.2,122.9,0.9], + [120.3,-10.3,119,-9.6,120.3,-10.3], + [121.3,-8.5,122.9,-8.1,119.9,-8.8,121.3,-8.5], + [118.3,-8.4,116.7,-9,118.3,-8.4], + [108.5,-6.4,115.7,-8.4,105.4,-6.9,108.5,-6.4], + [104.4,-1.1,106.1,-3.1,105.8,-5.9,102.6,-4.2,95.3,5.5,97.5,5.2,104.4,-1.1], + ], + }, + { + name: "Iran", + iso: "IR", + rings: [ + [48.6,29.9,45.4,34,46.1,35.7,44.1,39.4,48.1,39.6,50.8,36.9,56.6,38.1,61.1,36.5,60.9,29.8,63.3,26.8,61.5,25.1,57.4,25.7,48.6,29.9], + ], + }, + { + name: "Iraq", + iso: "IQ", + rings: [ + [39.2,32.2,41.3,36.4,44.8,37.2,48.6,29.9,44.7,29.2,39.2,32.2], + ], + }, + { + name: "Ireland", + iso: "IE", + rings: [ + [-6.2,53.9,-6.8,52.3,-10,51.8,-7.6,55.1,-6.2,53.9], + ], + }, + { + name: "Israel", + iso: "IL", + rings: [ + [35.7,32.7,34.9,29.5,34.3,31.2,35.7,32.7], + ], + }, + { + name: "Italy", + iso: "IT", + rings: [ + [10.4,46.9,13.8,46.5,12.6,44.1,18.3,39.8,16.9,40.4,15.7,37.9,15.4,40,10.2,43.9,7.4,43.7,6.8,46,10.4,46.9], + [14.8,38.1,15.1,36.6,12.4,37.6,14.8,38.1], + [8.7,40.9,9.8,40.5,8.8,38.9,8.7,40.9], + ], + }, + { + name: "Jamaica", + iso: "JM", + rings: [ + [-77.6,18.5,-76.2,17.9,-77.6,18.5], + ], + }, + { + name: "Japan", + iso: "JP", + rings: [ + [141.9,39.2,140.3,35.1,135.8,33.5,135.1,34.6,131,33.9,132,33.1,130.2,31.4,129.4,33.3,139.4,38.2,140.3,41.2,141.9,39.2], + [144.6,44,145.5,43.3,140,41.6,142,45.6,144.6,44], + [132.4,33.5,134.8,33.8,132.4,33.5], + ], + }, + { + name: "Jordan", + iso: "JO", + rings: [ + [35.5,32.4,38.8,33.4,39.2,32.2,37,31.5,38,30.5,36.1,29.2,34.9,29.5,35.5,32.4], + ], + }, + { + name: "Kazakhstan", + iso: "KZ", + rings: [ + [87.4,49.2,80,44.9,80.3,42.3,74.2,43.3,68.6,40.7,64.9,43.7,62,43.5,58.5,45.6,55.9,45,56,41.3,52.5,41.8,50.3,44.6,53,45.3,53,46.9,49.1,46.4,46.5,48.4,50.8,51.7,61.3,50.8,60,52,61.4,54,69.1,55.4,73.4,53.5,76.9,54.5,80,50.9,87.4,49.2], + ], + }, + { + name: "Kenya", + iso: "KE", + rings: [ + [39.2,-4.7,33.9,-0.9,35.3,5.5,38.1,3.6,41.9,3.9,41.6,-1.7,39.2,-4.7], + ], + }, + { + name: "Kosovo", + iso: "XK", + rings: [ + [20.6,41.9,20.6,43.2,21.8,42.7,20.6,41.9], + ], + }, + { + name: "Kuwait", + iso: "KW", + rings: [ + [48,30,48.4,28.6,46.6,29.1,48,30], + ], + }, + { + name: "Kyrgyzstan", + iso: "KG", + rings: [ + [71,42.3,74.2,43.3,80.3,42.3,73.7,39.4,69.5,39.5,73.1,40.9,71,42.3], + ], + }, + { + name: "Laos", + iso: "LA", + rings: [ + [107.4,14.2,105.2,14.3,104,18.2,101.1,17.5,100.1,20.4,101.7,22.3,104.4,20.8,103.9,19.3,107.4,14.2], + ], + }, + { + name: "Latvia", + iso: "LV", + rings: [ + [27.3,57.5,28.2,56.2,26.5,55.6,21.1,56,22.5,57.8,27.3,57.5], + ], + }, + { + name: "Lebanon", + iso: "LB", + rings: [ + [35.8,33.3,36.4,34.6,35.8,33.3], + ], + }, + { + name: "Lesotho", + iso: "LS", + rings: [ + [29,-29,28.1,-30.5,27,-29.9,29,-29], + ], + }, + { + name: "Liberia", + iso: "LR", + rings: [ + [-8.4,7.7,-7.7,4.4,-11.4,6.8,-10.2,8.4,-8.4,7.7], + ], + }, + { + name: "Libya", + iso: "LY", + rings: [ + [25,22,23.8,19.6,10.3,24.4,10,31.4,11.5,33.1,19.1,30.3,20.9,32.7,24.9,31.9,25,22], + ], + }, + { + name: "Lithuania", + iso: "LT", + rings: [ + [26.5,55.6,23.5,53.9,21.1,56,26.5,55.6], + ], + }, + { + name: "Madagascar", + iso: "MG", + rings: [ + [49.5,-12.5,50.4,-15.7,47.1,-24.9,45.4,-25.6,43.3,-22.8,44,-17.4,49.5,-12.5], + ], + }, + { + name: "Malawi", + iso: "MW", + rings: [ + [32.8,-9.2,35.7,-14.6,35,-16.8,32.7,-13.7,32.8,-9.2], + ], + }, + { + name: "Malaysia", + iso: "MY", + rings: [ + [100.1,6.5,103,5.5,104.2,1.3,101.4,2.8,100.1,6.5], + [117.9,4.1,115.9,4.3,114.6,1.4,109.8,1.3,115.3,4.3,116.7,6.9,119.2,5.4,117.9,4.1], + ], + }, + { + name: "Mali", + iso: "ML", + rings: [ + [-11.5,12.4,-11.7,15.4,-5.5,15.5,-6.5,25,4.3,19.2,3.6,15.6,-4,13.5,-5.4,10.4,-11.5,12.4], + ], + }, + { + name: "Mauritania", + iso: "MR", + rings: [ + [-17.1,21,-12.9,21.3,-12,25.9,-8.7,25.9,-8.7,27.4,-4.9,25,-6.5,25,-5.5,15.5,-12.2,14.6,-14.6,16.6,-16.5,16.1,-17.1,21], + ], + }, + { + name: "Mexico", + iso: "MX", + rings: [ + [-117.1,32.5,-106.5,31.8,-103.9,29.3,-101.7,29.8,-97.1,25.9,-97.9,22.4,-95.9,18.8,-91.4,18.9,-90.3,21,-87.1,21.5,-87.8,18.3,-91,17.8,-90.5,16.1,-92.2,14.5,-103.5,18.3,-113.1,31.2,-114.9,31.4,-109.9,22.8,-115.1,27.7,-114.2,28.6,-117.1,32.5], + ], + }, + { + name: "Moldova", + iso: "MD", + rings: [ + [26.6,48.2,30,46.4,28.2,45.5,26.6,48.2], + ], + }, + { + name: "Mongolia", + iso: "MN", + rings: [ + [87.8,49.3,92.2,50.8,97.3,49.7,98.9,52,108.5,49.3,116.7,49.9,115.7,47.7,119.8,47,105,41.6,96.3,42.7,90.9,45.3,91,46.9,87.8,49.3], + ], + }, + { + name: "Montenegro", + iso: "ME", + rings: [ + [20.1,42.6,18.5,42.5,20.1,42.6], + ], + }, + { + name: "Morocco", + iso: "MA", + rings: [ + [-2.2,35.2,-1.3,32.3,-8.7,28.8,-8.8,27.1,-11.4,26.9,-14.8,21.5,-17,21.4,-14.4,26.3,-9.6,29.9,-8.7,33.2,-5.9,35.8,-2.2,35.2], + ], + }, + { + name: "Mozambique", + iso: "MZ", + rings: [ + [34.6,-11.5,40.3,-10.3,40.8,-14.7,34.8,-19.8,35.5,-24.1,32.1,-26.7,31.2,-22.3,32.8,-16.7,30.2,-14.8,33.2,-14,35,-16.8,34.6,-11.5], + ], + }, + { + name: "Myanmar", + iso: "MM", + rings: [ + [100.1,20.4,97.4,18.4,99.6,11.9,98.6,9.9,97.2,16.9,94.2,16,92.3,21.5,97.3,28.3,98.7,27.5,97.6,23.9,101.2,21.8,100.1,20.4], + ], + }, + { + name: "N. Cyprus", + iso: "-99", + rings: [ + [32.7,35.1,34.6,35.7,32.7,35.1], + ], + }, + { + name: "Namibia", + iso: "NA", + rings: [ + [19.9,-24.8,19.9,-28.5,16.3,-28.6,11.7,-17.3,25.1,-17.6,20.9,-18.3,19.9,-24.8], + ], + }, + { + name: "Nepal", + iso: "NP", + rings: [ + [88.1,27.9,87.2,26.4,80.1,28.8,81.5,30.4,88.1,27.9], + ], + }, + { + name: "Netherlands", + iso: "NL", + rings: [ + [6.9,53.5,6.2,50.8,3.3,51.3,6.9,53.5], + ], + }, + { + name: "New Caledonia", + iso: "NC", + rings: [ + [165.8,-21.1,167.1,-22.2,164,-20.1,165.8,-21.1], + ], + }, + { + name: "New Zealand", + iso: "NZ", + rings: [ + [176.9,-40.1,174.7,-41.3,174.7,-37.4,172.6,-34.5,176,-37.6,178.5,-37.7,176.9,-40.1], + [169.7,-43.6,172.8,-40.5,174.2,-41.3,170.6,-45.9,166.7,-46.2,169.7,-43.6], + ], + }, + { + name: "Nicaragua", + iso: "NI", + rings: [ + [-83.7,10.9,-87.7,12.9,-83.1,15,-83.7,10.9], + ], + }, + { + name: "Niger", + iso: "NE", + rings: [ + [14.9,22.9,15.9,20.4,13.5,14.4,14.2,12.5,5.4,13.9,3.6,11.7,1,12.9,0.4,14.9,3.6,15.6,4.3,19.2,12,23.5,14.9,22.9], + ], + }, + { + name: "Nigeria", + iso: "NG", + rings: [ + [2.7,6.3,4.4,13.7,13.1,13.6,14.6,12.1,11.7,7,8.5,4.8,5.9,4.3,2.7,6.3], + ], + }, + { + name: "North Korea", + iso: "KP", + rings: [ + [130.6,42.4,127.5,39.8,128.2,38.4,124.7,38.1,125.1,40.6,130.6,42.4], + ], + }, + { + name: "North Macedonia", + iso: "MK", + rings: [ + [22.4,42.3,23,41.3,20.6,41.1,22.4,42.3], + ], + }, + { + name: "Norway", + iso: "NO", + rings: [ + [15.1,79.7,21.5,79,15.9,76.8,10.4,79.7,15.1,79.7], + [31.1,69.6,18,68.6,12.6,64.1,11,58.9,5.7,58.6,5,62,19.2,69.8,28.2,71.2,31.1,69.6], + [27.4,80.1,17.4,80.3,27.4,80.1], + [24.7,77.9,20.7,77.7,24.7,77.9], + ], + }, + { + name: "Oman", + iso: "OM", + rings: [ + [55.2,22.7,56.4,24.9,59.8,22.3,57.7,18.9,53.1,16.7,52,19,55,20,55.2,22.7], + ], + }, + { + name: "Pakistan", + iso: "PK", + rings: [ + [77.8,35.5,73.7,34.3,75.3,32.3,69.5,26.9,71,24.4,61.5,25.1,63.3,26.8,60.9,29.8,66.3,29.9,71.8,36.5,75.2,37.1,77.8,35.5], + ], + }, + { + name: "Panama", + iso: "PA", + rings: [ + [-77.4,8.7,-77.9,7.2,-79.1,9,-80.9,7.2,-82.9,9.5,-77.4,8.7], + ], + }, + { + name: "Papua New Guinea", + iso: "PG", + rings: [ + [141,-2.6,147.6,-6.1,147.2,-7.4,150.7,-10.6,144.7,-7.6,141,-9.1,141,-2.6], + [152.6,-3.7,152.8,-4.8,150.7,-2.7,152.6,-3.7], + [151.3,-5.8,148.3,-5.7,152.1,-4.1,151.3,-5.8], + [154.8,-5.3,155.9,-6.8,154.8,-5.3], + ], + }, + { + name: "Paraguay", + iso: "PY", + rings: [ + [-58.2,-20.2,-57.9,-22.1,-54.3,-24,-55.7,-27.4,-58.6,-27.1,-57.8,-25.2,-62.7,-22.2,-61.8,-19.6,-58.2,-20.2], + ], + }, + { + name: "Peru", + iso: "PE", + rings: [ + [-69.9,-4.3,-72.9,-5.3,-74,-7.5,-68.7,-12.6,-70.4,-18.3,-76,-14.6,-81.4,-4.7,-80.3,-3.4,-78.6,-4.5,-75.1,-0.1,-73.1,-2.3,-70,-2.7,-69.9,-4.3], + ], + }, + { + name: "Philippines", + iso: "PH", + rings: [ + [122.6,10,124.1,11.2,123,9,122.6,10], + [126.4,8.4,125.4,5.6,123.6,7.8,121.9,7.2,125.4,9.8,126.4,8.4], + [118.5,9.3,117.2,8.4,119.5,11.4,118.5,9.3], + [122.3,18.2,121.7,14.3,124.1,12.5,119.9,15.4,120.7,18.5,122.3,18.2], + [125.5,12.2,124.8,10.1,124.3,12.6,125.5,12.2], + ], + }, + { + name: "Poland", + iso: "PL", + rings: [ + [23.5,53.9,24,50.7,22.8,49,16.2,50.4,14.1,53,17.6,54.9,23.5,53.9], + ], + }, + { + name: "Portugal", + iso: "PT", + rings: [ + [-9,41.9,-6.4,41.4,-7.9,36.8,-9.5,38.7,-9,41.9], + ], + }, + { + name: "Puerto Rico", + iso: "PR", + rings: [ + [-66.3,18.5,-67.2,17.9,-66.3,18.5], + ], + }, + { + name: "Qatar", + iso: "QA", + rings: [ + [50.8,24.8,51.3,26.1,50.8,24.8], + ], + }, + { + name: "Romania", + iso: "RO", + rings: [ + [28.2,45.5,29.6,45.3,28.6,43.7,22.9,43.8,20.2,46.1,26.6,48.2,28.2,45.5], + ], + }, + { + name: "Russia", + iso: "RU", + rings: [ + [49.1,46.4,46.7,44.6,47.8,41.2,36.7,45.2,40.1,49.6,31.8,52.1,32.7,53.4,30.9,55.6,27.3,57.5,29.1,60,28.1,60.5,31.5,62.9,30,63.6,28.6,69.1,32.1,69.9,41.1,67.5,38.4,66,33.2,66.6,37,63.8,37.2,65.1,43.9,66.1,43.5,68.6,46.3,68.3,46.3,66.7,53.7,68.9,59.9,68.3,60.6,69.9,68.5,68.1,66.7,71,69.9,73,72.8,72.2,71.8,71.4,73.7,68.4,71.3,66.3,72.4,66.2,75.1,67.8,73.1,71.4,74.7,72.8,76.4,71.2,81.5,71.8,80.5,73.6,104.4,77.7,114.1,75.8,109.4,74.2,127,73.6,131.3,70.8,139.9,71.5,139.1,72.4,140.5,72.8,159,70.9,160.9,69.4,180,69,180,65,177.4,64.6,179.2,62.3,170.3,59.9,163.5,59.9,162,58.2,163.2,57.6,162.1,54.9,156.8,51,155.9,56.8,164.5,62.6,160.1,60.5,156.7,61.4,154.2,59.8,155,59.1,142.2,59,135.1,54.7,141.3,53.1,140.1,48.4,134.9,43.4,130.8,42.2,131,45,133.1,45.1,135,48.5,131,47.8,123.6,53.5,120.2,52.8,117.9,49.5,108.5,49.3,98.9,52,97.3,49.7,92.2,50.8,87.4,49.2,80,50.9,76.9,54.5,73.4,53.5,69.1,55.4,61.4,54,60,52,61.3,50.8,50.8,51.7,47.5,50.5,46.5,48.4,49.1,46.4], + [93.8,81,100.2,79.8,97.8,78.8,91.2,80.3,93.8,81], + [102.8,79.3,105.4,78.7,99.4,77.9,102.8,79.3], + [138.8,76.1,145.1,75.6,137,75.3,138.8,76.1], + [148.2,75.3,150.7,75.1,146.1,75.2,148.2,75.3], + [139.9,73.4,143.6,73.2,139.9,73.4], + [44.8,80.6,51.5,80.7,44.8,80.6], + [22.7,54.3,19.7,54.4,22.7,54.3], + [53.5,73.7,61.2,76.3,68.9,76.5,58.5,74.3,55.4,72.4,57.5,70.7,51.6,71.5,53.5,73.7], + [142.9,53.7,144.7,49,143.2,49.3,143.5,46.1,142.1,46,141.7,53.3,142.9,53.7], + [-174.9,67.2,-169.9,66,-173,64.3,-178.7,66.1,-180,65,-180,69,-174.9,67.2], + [-178.7,70.9,-180,71.5,-177.6,71.3,-178.7,70.9], + [33.4,46,36.5,45.5,33.9,44.4,32.5,45.3,33.4,46], + ], + }, + { + name: "Rwanda", + iso: "RW", + rings: [ + [30.4,-1.1,29,-2.8,30.4,-1.1], + ], + }, + { + name: "S. Sudan", + iso: "SS", + rings: [ + [30.8,3.5,23.9,8.6,25.8,10.4,31.4,9.8,33.2,12.2,33,7.8,35.3,5.5,30.8,3.5], + ], + }, + { + name: "Saudi Arabia", + iso: "SA", + rings: [ + [35,29.4,39.2,32.2,47.5,29,52,23,55.2,22.7,55,20,47,16.9,43.4,17.6,42.8,16.3,35,29.4], + ], + }, + { + name: "Senegal", + iso: "SN", + rings: [ + [-16.7,13.6,-17.6,14.7,-14.6,16.6,-11.5,12.4,-16.7,12.4,-13.8,13.5,-16.7,13.6], + ], + }, + { + name: "Serbia", + iso: "RS", + rings: [ + [18.8,45.9,22.7,44.6,22.5,42.5,19.2,43.5,18.8,45.9], + ], + }, + { + name: "Sierra Leone", + iso: "SL", + rings: [ + [-13.2,8.9,-11.1,10,-10.2,8.4,-11.4,6.8,-13.2,8.9], + ], + }, + { + name: "Slovakia", + iso: "SK", + rings: [ + [22.6,49.1,16.9,48.5,22.6,49.1], + ], + }, + { + name: "Slovenia", + iso: "SI", + rings: [ + [13.8,46.5,16.6,46.5,15.3,45.5,13.8,46.5], + ], + }, + { + name: "Solomon Is.", + iso: "SB", + rings: [ + [159.6,-8,158.2,-7.4,159.6,-8], + ], + }, + { + name: "Somalia", + iso: "SO", + rings: [ + [41.6,-1.7,41,2.8,45,5,48.9,9.5,48.9,11.4,51.1,12,48.6,5.3,41.6,-1.7], + ], + }, + { + name: "Somaliland", + iso: "-99", + rings: [ + [48.9,11.4,47.8,8,42.6,10.6,48.9,11.4], + ], + }, + { + name: "South Africa", + iso: "ZA", + rings: [ + [16.3,-28.6,19.9,-28.5,19.9,-24.8,21.6,-26.7,25.7,-25.5,29.4,-22.1,31.2,-22.3,31.9,-24.4,30.7,-26.7,32.8,-26.7,28.2,-32.8,20.1,-34.8,18.4,-34.1,16.3,-28.6], + ], + }, + { + name: "South Korea", + iso: "KR", + rings: [ + [126.2,37.7,128.3,38.6,129.1,35.1,126.5,34.4,126.2,37.7], + ], + }, + { + name: "Spain", + iso: "ES", + rings: [ + [-7.5,37.1,-6.4,41.4,-9.4,43,3,42.5,-2.1,36.7,-7.5,37.1], + ], + }, + { + name: "Sri Lanka", + iso: "LK", + rings: [ + [81.8,7.5,80.3,6,80.1,9.8,81.8,7.5], + ], + }, + { + name: "Sudan", + iso: "SD", + rings: [ + [24.6,8.2,21.9,12.6,25,22,36.9,22,38.4,18,34,8.7,32.7,12.2,31.4,9.8,25.1,10.3,24.6,8.2], + ], + }, + { + name: "Suriname", + iso: "SR", + rings: [ + [-54.5,2.3,-56.5,1.9,-57.6,3.3,-57.1,6,-54,5.8,-54.5,2.3], + ], + }, + { + name: "Sweden", + iso: "SE", + rings: [ + [11,58.9,12.6,61.3,11.9,63.1,16.8,68,20.6,69.1,23.5,67.9,23.9,66,17.8,62.7,17.1,61.3,18.8,60.1,15.9,56.1,12.9,55.4,11,58.9], + ], + }, + { + name: "Switzerland", + iso: "CH", + rings: [ + [9.6,47.5,10.4,46.5,6,46.3,9.6,47.5], + ], + }, + { + name: "Syria", + iso: "SY", + rings: [ + [35.7,32.7,36.7,36.8,42.3,37.2,41,34.4,35.7,32.7], + ], + }, + { + name: "Taiwan", + iso: "TW", + rings: [ + [121.8,24.4,120.7,22,120.1,23.6,121.8,24.4], + ], + }, + { + name: "Tajikistan", + iso: "TJ", + rings: [ + [67.8,37.1,67.7,39.6,70.7,41,69.5,39.5,73.7,39.4,75,37.4,71.8,36.7,70.8,38.5,67.8,37.1], + ], + }, + { + name: "Tanzania", + iso: "TZ", + rings: [ + [33.9,-0.9,39.2,-4.7,39.5,-10.9,34.6,-11.5,29.6,-6.5,30.4,-1.1,33.9,-0.9], + ], + }, + { + name: "Thailand", + iso: "TH", + rings: [ + [105.2,14.3,103,14.2,102.6,12.2,100.1,13.4,99.2,9.2,102.1,6.2,101.2,5.7,98.2,8.4,99.6,11.9,97.4,18.4,100.1,20.4,101.1,17.5,104.7,17.4,105.2,14.3], + ], + }, + { + name: "Timor-Leste", + iso: "TL", + rings: [ + [125,-8.9,127.3,-8.4,125,-8.9], + ], + }, + { + name: "Togo", + iso: "TG", + rings: [ + [0.9,11,1.1,5.9,0.9,11], + ], + }, + { + name: "Tunisia", + iso: "TN", + rings: [ + [9.5,30.3,7.5,34.1,9.5,37.3,11,37.1,10.1,34.3,11.5,33.1,9.5,30.3], + ], + }, + { + name: "Turkey", + iso: "TR", + rings: [ + [44.8,37.2,29.7,36.1,27.6,36.7,26.2,39.5,33.5,42,42.6,41.6,44.8,39.7,44.8,37.2], + [26.1,41.8,29,41.3,26.4,40.2,26.1,41.8], + ], + }, + { + name: "Turkmenistan", + iso: "TM", + rings: [ + [52.5,41.8,57.1,41.3,58.6,42.8,66.5,37.4,62.2,35.3,57.3,38,53.9,37.2,52.7,40,54.7,41,52.5,41.8], + ], + }, + { + name: "Uganda", + iso: "UG", + rings: [ + [33.9,-0.9,29.6,-1.3,31.2,3.8,34.5,3.6,33.9,-0.9], + ], + }, + { + name: "Ukraine", + iso: "UA", + rings: [ + [31.8,52.1,40.1,49.6,39.7,47.9,35,45.7,31.7,46.7,28.7,45.3,30,46.4,28.7,48.1,22.1,48.4,23.5,51.6,31.8,52.1], + ], + }, + { + name: "United Arab Emirates", + iso: "AE", + rings: [ + [51.6,24.2,56.3,25.7,55,22.5,51.6,24.2], + ], + }, + { + name: "United Kingdom", + iso: "GB", + rings: [ + [-6.2,53.9,-7.6,55.1,-6.2,53.9], + [-3.1,53.4,-6.1,56.8,-5,58.6,-2,57.7,-3.1,56,1.7,52.7,1.4,51.3,-5.8,50.2,-3.4,51.4,-5.3,52,-4.6,53.5,-3.1,53.4], + ], + }, + { + name: "United States of America", + iso: "US", + rings: [ + [-122.8,49,-88.4,48.3,-82.6,45.3,-82.7,41.7,-71.5,45,-69.2,47.4,-67,44.8,-70.1,43.7,-70,41.6,-75.5,39.5,-75.9,37.2,-76.3,39.2,-77,38.2,-75.7,35.6,-81.3,31.4,-80.4,25.2,-83.7,29.9,-86.4,30.4,-94.7,29.5,-97.5,25.8,-101,29.4,-103.9,29.3,-106.5,31.8,-117.1,32.5,-120.6,34.6,-124.4,40.3,-124.7,48.2,-122.6,47.1,-122.8,49], + [-166.5,60.4,-165.6,59.9,-167.5,60.2,-166.5,60.4], + [-153.2,58,-152.1,57.6,-154.5,57,-153.2,58], + [-141,69.7,-141,60.3,-137.5,58.9,-135.5,59.8,-130,55.9,-130.5,54.8,-134.1,58.1,-139.9,59.5,-147.1,60.9,-151.7,59.2,-150.6,61.3,-158.4,56,-164.9,54.6,-157,58.9,-162,58.7,-165.3,60.5,-165.7,62.1,-160.8,64.8,-168.1,65.7,-161.7,66.1,-166.2,68.9,-156.6,71.4,-141,69.7], + [-171.7,63.8,-168.7,63.3,-171.7,63.8], + ], + }, + { + name: "Uruguay", + iso: "UY", + rings: [ + [-57.6,-30.2,-53.8,-32,-53.8,-34.4,-58.4,-33.9,-57.6,-30.2], + ], + }, + { + name: "Uzbekistan", + iso: "UZ", + rings: [ + [56,41.3,55.9,45,58.5,45.6,62,43.5,64.9,43.7,68.3,40.7,71,42.3,73.1,40.9,67.7,39.6,67.8,37.1,58.6,42.8,56,41.3], + ], + }, + { + name: "Venezuela", + iso: "VE", + rings: [ + [-60.7,5.2,-64.8,4.1,-63.4,2.2,-66.3,0.7,-67.8,2.8,-67.3,6.1,-72,7,-72.9,10.5,-71.3,11.8,-71.3,9.1,-69.9,12.2,-68.2,10.6,-61.9,10.7,-59.8,8.4,-60.7,5.2], + ], + }, + { + name: "Vietnam", + iso: "VN", + rings: [ + [104.3,10.5,107.5,12.3,107.6,15.2,102.2,22.5,105.3,23.4,108.1,21.6,105.7,19.1,108.9,15.3,109.2,11.7,105.2,8.6,104.3,10.5], + ], + }, + { + name: "W. Sahara", + iso: "EH", + rings: [ + [-8.7,27.7,-8.7,25.9,-12,25.9,-12.9,21.3,-17.1,21,-14.8,21.5,-11.4,26.9,-8.7,27.7], + ], + }, + { + name: "Yemen", + iso: "YE", + rings: [ + [52,19,52.2,15.6,43.5,12.6,43.4,17.6,47,16.9,52,19], + ], + }, + { + name: "Zambia", + iso: "ZM", + rings: [ + [30.7,-8.3,33.2,-9.7,33.2,-14,27,-17.9,23.2,-17.5,21.9,-12.9,24,-12.9,23.9,-10.9,29.7,-13.3,28.4,-9.2,30.7,-8.3], + ], + }, + { + name: "Zimbabwe", + iso: "ZW", + rings: [ + [31.2,-22.3,28,-21.5,25.3,-17.7,30.3,-15.5,32.8,-16.7,31.2,-22.3], + ], + }, +]; diff --git a/packages/hqtui/src/graphics/world.ts b/packages/hqtui/src/graphics/world.ts new file mode 100644 index 0000000..ec1faca --- /dev/null +++ b/packages/hqtui/src/graphics/world.ts @@ -0,0 +1,171 @@ +/** + * The world, as shapes for the canvas, and the lookup that makes it clickable. + * + * The canvas already draws in the caller's own coordinates, and longitude and + * latitude are just another pair of axes -- so a map is a list of polylines in + * degrees, and nothing here needs a projection of its own beyond deciding which + * window on the globe to show. + * + * The interesting half is the other direction. A click arrives as a terminal + * cell, and a country is a polygon, so answering "what did they click" means + * turning the cell back into degrees and testing it against the outlines. Doing + * it that way rather than with bounding boxes is what makes the answer right: + * Russia's bounding box covers most of the northern hemisphere, and Chile's + * covers Argentina. + */ +import type { Color } from "../color.ts"; +import type { Bounds, Shape } from "./canvas.ts"; +import { type CountryOutline, WORLD_COUNTRIES } from "./world-data.ts"; + +export { type CountryOutline, WORLD_COUNTRIES }; + +/** The whole globe, which is what a map shows unless told otherwise. */ +export const WORLD_X: Bounds = { min: -180, max: 180 }; +export const WORLD_Y: Bounds = { min: -90, max: 90 }; + +export interface WorldShapeOptions { + /** Colour for countries with nothing special about them. */ + color?: Color; + /** Countries to pick out, by name or ISO code. */ + highlight?: readonly string[]; + highlightColor?: Color; +} + +/** Match on either the name or the ISO code, case-insensitively. */ +function matches(country: CountryOutline, keys: readonly string[]): boolean { + for (const key of keys) { + if (key.length === 0) continue; + if (country.name.toLowerCase() === key.toLowerCase()) return true; + if (country.iso.length > 0 && country.iso.toLowerCase() === key.toLowerCase()) return true; + } + return false; +} + +/** + * The world as canvas shapes, one polyline per landmass. + * + * Polylines rather than scattered points: the outlines are closed rings, so + * joining them draws a coastline instead of a dotted suggestion of one, and it + * reads at a fraction of the resolution dots would need. + */ +export function worldShapes(options: WorldShapeOptions = {}): Shape[] { + const shapes: Shape[] = []; + const highlight = options.highlight ?? []; + for (const country of WORLD_COUNTRIES) { + const picked = highlight.length > 0 && matches(country, highlight); + const color = picked ? options.highlightColor ?? options.color : options.color; + for (const ring of country.rings) { + const points: [number, number][] = []; + for (let i = 0; i + 1 < ring.length; i += 2) points.push([ring[i], ring[i + 1]]); + // Closed: the last point joins the first, or every country has a gap in + // its coastline where the ring started. + if (points.length > 0) points.push(points[0]); + shapes.push({ type: "polyline", points, color }); + } + } + return shapes; +} + +/** + * Whether a point is inside a ring, by ray casting. + * + * The ring is a flat list of interleaved coordinates, so this walks it two at a + * time rather than allocating a pair per vertex -- it runs once per country per + * click, and there are a couple of thousand vertices. + */ +function insideRing(ring: number[], lon: number, lat: number): boolean { + let inside = false; + const n = ring.length / 2; + for (let i = 0, j = n - 1; i < n; j = i++) { + const xi = ring[i * 2]; + const yi = ring[i * 2 + 1]; + const xj = ring[j * 2]; + const yj = ring[j * 2 + 1]; + if ((yi > lat) !== (yj > lat) && lon < ((xj - xi) * (lat - yi)) / (yj - yi) + xi) { + inside = !inside; + } + } + return inside; +} + +/** + * The country containing a point, or undefined for open water. + * + * Where outlines overlap -- and at this resolution simplified borders do + * overlap -- the first match wins, which is stable because the data is sorted + * by name. + */ +export function countryAt(lon: number, lat: number): CountryOutline | undefined { + if (!Number.isFinite(lon) || !Number.isFinite(lat)) return undefined; + for (const country of WORLD_COUNTRIES) { + for (const ring of country.rings) { + if (insideRing(ring, lon, lat)) return country; + } + } + return undefined; +} + +/** Look a country up by name or ISO code. */ +export function findCountry(key: string): CountryOutline | undefined { + return WORLD_COUNTRIES.find((c) => matches(c, [key])); +} + +/** + * The window a country fills, with a little room around it. + * + * For zooming a map to a country: the bounding box alone puts the coastline + * flat against the edge of the panel, which reads as though the country has + * been cut off rather than framed. + */ +export function countryBounds( + country: CountryOutline, + margin = 0.08, +): { x: Bounds; y: Bounds } { + let minLon = Infinity; + let maxLon = -Infinity; + let minLat = Infinity; + let maxLat = -Infinity; + for (const ring of country.rings) { + for (let i = 0; i + 1 < ring.length; i += 2) { + minLon = Math.min(minLon, ring[i]); + maxLon = Math.max(maxLon, ring[i]); + minLat = Math.min(minLat, ring[i + 1]); + maxLat = Math.max(maxLat, ring[i + 1]); + } + } + if (!Number.isFinite(minLon)) return { x: WORLD_X, y: WORLD_Y }; + // A single-point country would give a zero-width window, which cannot be + // mapped onto anything. + const padX = Math.max((maxLon - minLon) * margin, 1); + const padY = Math.max((maxLat - minLat) * margin, 1); + return { + x: { min: minLon - padX, max: maxLon + padX }, + y: { min: minLat - padY, max: maxLat + padY }, + }; +} + +/** + * The degrees under a terminal cell, given the window the map was drawn with. + * + * The inverse of what the canvas does on the way in, taken at the centre of the + * cell: a click lands on a whole cell, and the centre is the only point in it + * that is not arbitrarily nearer one neighbour than the other. + */ +export function degreesAt( + column: number, + row: number, + width: number, + height: number, + x: Bounds = WORLD_X, + y: Bounds = WORLD_Y, +): { lon: number; lat: number } | undefined { + if (width <= 0 || height <= 0) return undefined; + // The canvas is 2x4 Braille pixels per cell, and it spans its bounds across + // `pixels - 1`, so the inverse has to use the same denominators or a click + // drifts from what was drawn. + const px = Math.max(1, width * 2 - 1); + const py = Math.max(1, height * 4 - 1); + const lon = x.min + ((column * 2 + 1) / px) * (x.max - x.min); + const lat = y.min + (1 - (row * 4 + 2) / py) * (y.max - y.min); + return { lon, lat }; +} diff --git a/packages/hqtui/src/index.ts b/packages/hqtui/src/index.ts index 7de353c..bb8c556 100644 --- a/packages/hqtui/src/index.ts +++ b/packages/hqtui/src/index.ts @@ -71,9 +71,12 @@ export { plot, blit, sparkline, bar, gauge, donut, histogram, verticalGlyph, horizontalGlyph, shadeGlyph, bestMode, plotPoints, domainOf, drawCanvas, projection, + worldShapes, countryAt, findCountry, countryBounds, degreesAt, + WORLD_COUNTRIES, WORLD_X, WORLD_Y, type Series, type PlotOptions, type FillMode, type Point, type MarkType, type ChartSeries, type AxisOptions, type ChartPlotOptions, type Domain, type Shape, type Bounds, type CanvasOptions, type Projection, + type CountryOutline, type WorldShapeOptions, } from "./graphics/index.ts"; // Widgets (for drawing straight onto a Surface) diff --git a/packages/hqtui/src/ui.ts b/packages/hqtui/src/ui.ts index f35d4eb..3565a18 100644 --- a/packages/hqtui/src/ui.ts +++ b/packages/hqtui/src/ui.ts @@ -10,6 +10,7 @@ import { stringWidth, wrap } from "./unicode.ts"; import { isRich, toSpanLines, wrapRich, type RichText } from "./richtext.ts"; import { BrailleCanvas } from "./graphics/braille.ts"; import { drawCanvas, type CanvasOptions } from "./graphics/canvas.ts"; +import type { CountryOutline } from "./graphics/world.ts"; import * as W from "./widgets/index.ts"; export interface HitRegion { @@ -631,6 +632,33 @@ export class Container { return this.add((s) => drawCanvas(s, options), this.sizeOf(options, "fill")); } + /** + * A world map, and the country under whatever gets clicked. + * + * The click is answered by turning the cell back into degrees and testing it + * against the outlines, so the answer is the country actually under the + * cursor. Bounding boxes would be cheaper and wrong: Russia's covers most of + * the northern hemisphere and Chile's covers Argentina. + */ + worldMap( + options: W.WorldMapOptions & ContainerOptions & { + onSelect?: (country: CountryOutline | undefined) => void; + onHover?: (country: CountryOutline | undefined) => void; + } = {}, + ): this { + return this.add((s) => { + W.drawWorldMap(s, options); + if (!options.onSelect && !options.onHover) return; + const at = (x: number, y: number) => + W.countryAtCell(x, y, s.width, s.height, options); + this.ctx.hit({ + rect: s.hitRect(), + onClick: options.onSelect ? (x, y) => options.onSelect?.(at(x, y)) : undefined, + onHover: options.onHover ? (x, y) => options.onHover?.(at(x, y)) : undefined, + }); + }, this.sizeOf(options, "fill")); + } + /** A Braille pixel canvas sized to the region, blitted when you are done. */ canvas( fn: (canvas: BrailleCanvas, surface: Surface) => void, diff --git a/packages/hqtui/src/widgets/index.ts b/packages/hqtui/src/widgets/index.ts index b0f075f..49a88f2 100644 --- a/packages/hqtui/src/widgets/index.ts +++ b/packages/hqtui/src/widgets/index.ts @@ -3,6 +3,7 @@ export * from "./calendar.ts"; export * from "./chart.ts"; export * from "./shadow.ts"; export * from "./surface.ts"; +export * from "./world.ts"; export * from "./scrollbar.ts"; export * from "./table.ts"; export * from "./meters.ts"; diff --git a/packages/hqtui/src/widgets/world.ts b/packages/hqtui/src/widgets/world.ts new file mode 100644 index 0000000..e951265 --- /dev/null +++ b/packages/hqtui/src/widgets/world.ts @@ -0,0 +1,61 @@ +/** + * A world map you can click. + * + * The drawing is the canvas doing what it already does -- polylines in the + * caller's own coordinates, which for a map are degrees. What this adds is the + * other direction: turning a click back into a country. + */ +import type { Surface } from "../surface.ts"; +import type { Color } from "../color.ts"; +import type { Bounds } from "../graphics/canvas.ts"; +import { drawCanvas } from "../graphics/canvas.ts"; +import { + type CountryOutline, WORLD_X, WORLD_Y, countryAt, degreesAt, worldShapes, +} from "../graphics/world.ts"; + +export interface WorldMapOptions { + /** The window on the globe. Defaults to all of it. */ + x?: Bounds; + y?: Bounds; + /** Coastline colour. */ + color?: Color; + /** Countries to pick out, by name or ISO code. */ + highlight?: readonly string[]; + highlightColor?: Color; + background?: Color; + grid?: boolean; +} + +export function drawWorldMap(surface: Surface, options: WorldMapOptions = {}): void { + if (surface.empty) return; + const theme = surface.theme; + drawCanvas(surface, { + shapes: worldShapes({ + color: options.color ?? theme.border, + highlight: options.highlight, + highlightColor: options.highlightColor ?? theme.accent, + }), + x: options.x ?? WORLD_X, + y: options.y ?? WORLD_Y, + background: options.background, + grid: options.grid, + }); +} + +/** + * The country under a cell of a map drawn with these bounds. + * + * Exposed so a caller can answer a hover as well as a click, and so the + * arithmetic that has to agree with the drawing lives in one place. + */ +export function countryAtCell( + column: number, + row: number, + width: number, + height: number, + options: WorldMapOptions = {}, +): CountryOutline | undefined { + const at = degreesAt(column, row, width, height, options.x ?? WORLD_X, options.y ?? WORLD_Y); + if (!at) return undefined; + return countryAt(at.lon, at.lat); +} diff --git a/packages/hqtui/test/world.test.ts b/packages/hqtui/test/world.test.ts new file mode 100644 index 0000000..022eae8 --- /dev/null +++ b/packages/hqtui/test/world.test.ts @@ -0,0 +1,182 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { renderToScreen } from "../src/index.ts"; +import { + WORLD_COUNTRIES, WORLD_X, WORLD_Y, countryAt, countryBounds, degreesAt, findCountry, + worldShapes, +} from "../src/graphics/world.ts"; +import { countryAtCell } from "../src/widgets/world.ts"; + +test("world: the data covers the countries a map is expected to have", () => { + assert.ok(WORLD_COUNTRIES.length > 160, `only ${WORLD_COUNTRIES.length} countries`); + // Sorted by name, which is what makes the overlap tie-break below stable. + const names = WORLD_COUNTRIES.map((c) => c.name); + assert.deepEqual(names, [...names].sort((a, b) => a.localeCompare(b))); + for (const key of ["France", "Japan", "Brazil", "Kenya", "Australia"]) { + assert.ok(findCountry(key), `${key} is missing`); + } + // ISO codes work as keys too, which is what an app is likely to have. + assert.equal(findCountry("JP")?.name, "Japan"); + assert.equal(findCountry("jp")?.name, "Japan", "the lookup is case-insensitive"); +}); + +test("world: every ring is a closed list of finite coordinate pairs", () => { + for (const country of WORLD_COUNTRIES) { + for (const ring of country.rings) { + assert.equal(ring.length % 2, 0, `${country.name}: odd coordinate count`); + assert.ok(ring.length >= 6, `${country.name}: a ring needs three points`); + for (let i = 0; i + 1 < ring.length; i += 2) { + assert.ok(Number.isFinite(ring[i]) && Number.isFinite(ring[i + 1])); + assert.ok(ring[i] >= -180.5 && ring[i] <= 180.5, `${country.name}: longitude ${ring[i]}`); + assert.ok(ring[i + 1] >= -90.5 && ring[i + 1] <= 90.5, `${country.name}: latitude ${ring[i + 1]}`); + } + } + } +}); + +test("world: a point lands in the country that is actually there", () => { + // Capitals, which is the least arguable way to check a polygon lookup. + const cities: [string, number, number, string][] = [ + ["Paris", 2.35, 48.86, "France"], + ["Tokyo", 139.7, 35.7, "Japan"], + ["Cairo", 31.2, 30.0, "Egypt"], + ["Brasilia", -47.9, -15.8, "Brazil"], + ["Canberra", 149.1, -35.3, "Australia"], + ["Denver", -105.0, 39.7, "United States of America"], + ["Moscow", 37.6, 55.75, "Russia"], + ["Delhi", 77.2, 28.6, "India"], + ]; + for (const [city, lon, lat, expected] of cities) { + assert.equal(countryAt(lon, lat)?.name, expected, `${city} (${lon}, ${lat})`); + } +}); + +test("world: the open ocean is not a country", () => { + // A bounding-box lookup would answer Russia or Chile for half of these. + for (const [lon, lat] of [[-140, 0], [-30, 0], [-25, -40], [80, -40], [-150, 40]]) { + assert.equal(countryAt(lon, lat), undefined, `(${lon}, ${lat}) claimed land`); + } +}); + +test("world: nonsense coordinates are water, not a crash", () => { + assert.equal(countryAt(Number.NaN, 0), undefined); + assert.equal(countryAt(0, Number.POSITIVE_INFINITY), undefined); +}); + +test("world: a cell maps back to the degrees it was drawn from", () => { + // If this is off by even half a cell, every click lands somewhere the user + // did not point at, and nothing about the map would look wrong. + const w = 100; + const h = 26; + for (let row = 0; row < h; row++) { + for (let column = 0; column < w; column++) { + const at = degreesAt(column, row, w, h); + assert.ok(at, `no degrees for (${column}, ${row})`); + const px = ((at.lon - WORLD_X.min) / 360) * (w * 2 - 1); + const py = (1 - (at.lat - WORLD_Y.min) / 180) * (h * 4 - 1); + assert.equal(Math.floor(px / 2), column, `column ${column} came back as ${px / 2}`); + assert.equal(Math.floor(py / 4), row, `row ${row} came back as ${py / 4}`); + } + } +}); + +test("world: an empty map has no cells to click", () => { + assert.equal(degreesAt(0, 0, 0, 10), undefined); + assert.equal(degreesAt(0, 0, 10, 0), undefined); +}); + +test("world: clicking a cell over a country reports that country", () => { + // Big enough that a country is more than one cell wide, which is the case + // where the answer is unambiguous. + const w = 200; + const h = 50; + const cellOf = (lon: number, lat: number): [number, number] => [ + Math.round((((lon + 180) / 360) * (w * 2 - 1) - 1) / 2), + Math.round(((1 - (lat + 90) / 180) * (h * 4 - 1) - 2) / 4), + ]; + const cases: [string, number, number][] = [ + ["Japan", 138.0, 36.5], + ["Brazil", -50.0, -10.0], + ["Australia", 134.0, -25.0], + ["Egypt", 29.0, 26.0], + ["India", 78.0, 22.0], + ]; + for (const [expected, lon, lat] of cases) { + const [column, row] = cellOf(lon, lat); + assert.equal(countryAtCell(column, row, w, h)?.name, expected, `${expected} at (${lon}, ${lat})`); + } +}); + +test("world: zooming changes which country a given cell holds", () => { + // The same cell over two different windows is two different places, which is + // the whole reason the lookup takes the bounds. + const japan = countryBounds(findCountry("Japan")!); + // A cell that is Honshu once the map is zoomed to Japan, and Russia when the + // same map shows the whole globe. + const cell = { column: 74, row: 2 }; + const whole = countryAtCell(cell.column, cell.row, 100, 26); + const zoomed = countryAtCell(cell.column, cell.row, 100, 26, japan); + assert.equal(zoomed?.name, "Japan"); + assert.equal(whole?.name, "Russia"); +}); + +test("world: a country's window contains it, with room around it", () => { + const japan = findCountry("Japan")!; + const bounds = countryBounds(japan); + let minLon = Infinity; + let maxLon = -Infinity; + for (const ring of japan.rings) { + for (let i = 0; i + 1 < ring.length; i += 2) { + minLon = Math.min(minLon, ring[i]); + maxLon = Math.max(maxLon, ring[i]); + } + } + assert.ok(bounds.x.min < minLon, "no margin on the left"); + assert.ok(bounds.x.max > maxLon, "no margin on the right"); +}); + +test("world: shapes are closed rings, so a coastline has no seam", () => { + const shapes = worldShapes(); + assert.ok(shapes.length > 200, `only ${shapes.length} shapes`); + for (const shape of shapes) { + assert.equal(shape.type, "polyline"); + if (shape.type !== "polyline") continue; + const first = shape.points[0]; + const last = shape.points[shape.points.length - 1]; + assert.deepEqual(first, last, "a ring that does not close leaves a gap"); + } +}); + +test("world: highlighted countries are drawn in their own colour", () => { + const shapes = worldShapes({ color: 0x111111, highlight: ["JP"], highlightColor: 0x222222 }); + const colours = new Set(shapes.map((s) => s.color)); + assert.deepEqual([...colours].sort(), [0x111111, 0x222222]); +}); + +test("world: the map draws, and draws differently when zoomed", () => { + const whole = renderToScreen(({ ui }) => ui.worldMap({}), { width: 60, height: 16 }).text(); + assert.ok(whole.trim().length > 0, "nothing was drawn"); + const japan = countryBounds(findCountry("Japan")!); + const zoomed = renderToScreen(({ ui }) => ui.worldMap(japan), { width: 60, height: 16 }).text(); + assert.notEqual(whole, zoomed); +}); + +test("world: a map with no handlers registers nothing to click", () => { + const plain = renderToScreen(({ ui }) => ui.worldMap({}), { width: 40, height: 12 }); + assert.equal(plain.regions.length, 0); + + let picked: string | undefined; + const live = renderToScreen( + ({ ui }) => ui.worldMap({ onSelect: (c) => { picked = c?.name; } }), + { width: 200, height: 50 }, + ); + assert.equal(live.regions.length, 1); + // A click over Australia comes back as Australia. + live.regions[0].onClick?.(173, 28, "left"); + assert.equal(picked, "Australia"); + + // And a click on open water comes back as nothing, rather than the nearest + // bounding box. + live.regions[0].onClick?.(20, 25, "left"); + assert.equal(picked, undefined); +}); From 2f67ec91c6ff757ab10c7a17a655700cc562d2ec Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Wed, 9 Sep 2026 00:18:40 +0000 Subject: [PATCH 2/2] Port the world map to all six languages The generator now emits the dataset for every port from the one Natural Earth source, so the six copies cannot drift: regenerating rewrites all of them or none of them. Roughly 230KB in total, 32-44KB per language, all derived. C++ gets the coordinates as a flat pool with index tables rather than nested initialiser lists. The nested form is the same data and minutes of compile time, and C++ is the one language where that bites; the pool is turned into the shape the rest of the code wants once, on first use. Agreement is checked rather than assumed. Every port ships a `world-probe` that prints what the lookup answers for thirteen places -- nine capitals, four patches of open ocean -- plus four cell clicks and three raw projections, and all five ports diff clean against the TypeScript reference. That catches what the render fixtures cannot: a country lookup can be wrong in a way that still draws a perfectly good-looking map. Three fixtures pin the drawing itself: the whole globe, a window zoomed on Japan, and two highlighted countries. 93 widget scenes now, up from 90. Verified: 353 TS tests under bun and node; 93 widget scenes matching in Rust, Go, Python, Zig and C++; five world probes diffing clean against TypeScript; 11/11 ctest; every gallery; the site and the benchmark. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy --- packages/hqtui/scripts/generate-world.ts | 198 +- packages/hqtui/scripts/world-probe.ts | 42 + packages/hqtui/src/graphics/world-data.ts | 529 +- ports/conformance/fixtures/widgets.json | 5700 +++++++++++++++++ ports/conformance/generate.ts | Bin 49961 -> 50380 bytes ports/cpp/CMakeLists.txt | 4 +- ports/cpp/examples/world-probe.cpp | 45 + ports/cpp/include/hqtui/widgets.hpp | 49 + ports/cpp/src/world.cpp | 169 + ports/cpp/src/world_data.cpp | 505 ++ ports/cpp/tests/conformance_widgets.cpp | 17 + ports/go/conformance_widgets_test.go | 7 + ports/go/examples/world-probe/main.go | 56 + ports/go/ui.go | 13 + ports/go/widgets_world.go | 71 + ports/go/world.go | 178 + ports/go/world_data.go | 1308 ++++ ports/python/examples/world_probe.py | 41 + ports/python/hqtui/graphics/__init__.py | 22 + ports/python/hqtui/graphics/world.py | 183 + ports/python/hqtui/graphics/world_data.py | 1314 ++++ ports/python/hqtui/ui.py | 13 + ports/python/hqtui/widgets/__init__.py | 2 + ports/python/hqtui/widgets/world.py | 90 + .../python/tests/test_conformance_widgets.py | 8 + ports/rust/examples/world-probe.rs | 42 + ports/rust/src/graphics/mod.rs | 7 + ports/rust/src/graphics/world.rs | 169 + ports/rust/src/graphics/world_data.rs | 1306 ++++ ports/rust/src/ui.rs | 18 + ports/rust/src/widgets/mod.rs | 2 + ports/rust/src/widgets/world.rs | 71 + ports/rust/tests/conformance_widgets.rs | 16 + ports/zig/build.zig | 2 +- ports/zig/examples/world-probe.zig | 66 + ports/zig/src/conformance_widgets.zig | 9 + ports/zig/src/graphics.zig | 9 + ports/zig/src/graphics/world.zig | 202 + ports/zig/src/graphics/world_data.zig | 1306 ++++ ports/zig/src/ui.zig | 12 + ports/zig/src/widgets.zig | 4 + ports/zig/src/widgets/world.zig | 74 + 42 files changed, 13577 insertions(+), 302 deletions(-) create mode 100644 packages/hqtui/scripts/world-probe.ts create mode 100644 ports/cpp/examples/world-probe.cpp create mode 100644 ports/cpp/src/world.cpp create mode 100644 ports/cpp/src/world_data.cpp create mode 100644 ports/go/examples/world-probe/main.go create mode 100644 ports/go/widgets_world.go create mode 100644 ports/go/world.go create mode 100644 ports/go/world_data.go create mode 100644 ports/python/examples/world_probe.py create mode 100644 ports/python/hqtui/graphics/world.py create mode 100644 ports/python/hqtui/graphics/world_data.py create mode 100644 ports/python/hqtui/widgets/world.py create mode 100644 ports/rust/examples/world-probe.rs create mode 100644 ports/rust/src/graphics/world.rs create mode 100644 ports/rust/src/graphics/world_data.rs create mode 100644 ports/rust/src/widgets/world.rs create mode 100644 ports/zig/examples/world-probe.zig create mode 100644 ports/zig/src/graphics/world.zig create mode 100644 ports/zig/src/graphics/world_data.zig create mode 100644 ports/zig/src/widgets/world.zig diff --git a/packages/hqtui/scripts/generate-world.ts b/packages/hqtui/scripts/generate-world.ts index d574c87..cbceef2 100644 --- a/packages/hqtui/scripts/generate-world.ts +++ b/packages/hqtui/scripts/generate-world.ts @@ -112,43 +112,169 @@ for (const feature of collection.features) { countries.sort((a, b) => a.name.localeCompare(b.name)); const points = countries.reduce((n, c) => n + c.rings.reduce((m, r) => m + r.length, 0), 0); -const body = countries - .map((c) => { - const rings = c.rings - .map((ring) => `[${ring.map(([x, y]) => `${x},${y}`).join(",")}]`) - .join(",\n "); - return ` {\n name: ${JSON.stringify(c.name)},\n iso: ${JSON.stringify(c.iso)},\n` + - ` rings: [\n ${rings},\n ],\n },`; - }) - .join("\n"); - -const out = `/** - * Country outlines, flattened for a terminal. - * - * Generated by \`packages/hqtui/scripts/generate-world.ts\` from Natural Earth's - * 1:110m Admin 0 countries, which is public domain. Do not edit by hand. - * - * Each ring is a flat list of longitude and latitude pairs -- \`[lon, lat, - * lon, lat, ...]\` rather than an array of pairs, because at ${points} points the - * nested form costs an object per coordinate for no gain. A country has more - * than one ring when it is more than one landmass. - */ +const here = dirname(fileURLToPath(import.meta.url)); +const repo = join(here, "..", "..", ".."); -export interface CountryOutline { - name: string; - /** ISO 3166-1 alpha-2, where Natural Earth has one. */ - iso: string; - /** Longitude and latitude, interleaved. */ - rings: number[][]; +/** The same provenance note at the top of every generated file. */ +function banner(comment: string): string { + const line = (text: string) => (text ? `${comment} ${text}` : comment.trimEnd()); + return [ + line("Country outlines, flattened for a terminal."), + line(""), + line("Generated by packages/hqtui/scripts/generate-world.ts from Natural Earth's"), + line("1:110m Admin 0 countries, which is public domain. Do not edit by hand."), + line(""), + line("Each ring is longitude and latitude interleaved -- lon, lat, lon, lat --"), + line(`rather than a list of pairs, because at ${points} points the nested form`), + line("costs a container per coordinate for no gain. A country has more than one"), + line("ring when it is more than one landmass."), + line(""), + line(`${countries.length} countries, ${points} points, simplified at ${TOLERANCE} degrees.`), + ].join("\n"); } -/** ${countries.length} countries, ${points} points, simplified at ${TOLERANCE} degrees. */ -export const WORLD_COUNTRIES: readonly CountryOutline[] = [ -${body} -]; -`; +const flat = (ring: Point[]) => ring.flatMap(([x, y]) => [x, y]); +/** A float literal every language reads the same way, including whole numbers. */ +const real = (v: number) => (Number.isInteger(v) ? `${v}.0` : `${v}`); +const quoted = (v: string) => JSON.stringify(v); -const here = dirname(fileURLToPath(import.meta.url)); -const target = join(here, "..", "src", "graphics", "world-data.ts"); -writeFileSync(target, out); -console.log(`${countries.length} countries, ${points} points -> ${target}`); +const write = (relative: string, text: string) => { + const target = join(repo, relative); + writeFileSync(target, text); + console.log(` ${relative}`); +}; + +// ------------------------------------------------------------------ TypeScript +write( + "packages/hqtui/src/graphics/world-data.ts", + `/**\n${banner(" *")}\n */\n\n` + + `export interface CountryOutline {\n name: string;\n` + + ` /** ISO 3166-1 alpha-2, where Natural Earth has one. */\n iso: string;\n` + + ` /** Longitude and latitude, interleaved. */\n rings: number[][];\n}\n\n` + + `export const WORLD_COUNTRIES: readonly CountryOutline[] = [\n` + + countries + .map((c) => + ` {\n name: ${quoted(c.name)},\n iso: ${quoted(c.iso)},\n rings: [\n` + + c.rings.map((r) => ` [${flat(r).join(",")}],`).join("\n") + + `\n ],\n },`) + .join("\n") + + `\n];\n`, +); + +// ------------------------------------------------------------------------ Rust +write( + "ports/rust/src/graphics/world_data.rs", + `${banner("//!")}\n\npub struct CountryOutline {\n pub name: &'static str,\n` + + ` /// ISO 3166-1 alpha-2, where Natural Earth has one.\n pub iso: &'static str,\n` + + ` /// Longitude and latitude, interleaved.\n pub rings: &'static [&'static [f64]],\n}\n\n` + + `pub static WORLD_COUNTRIES: &[CountryOutline] = &[\n` + + countries + .map((c) => + ` CountryOutline {\n name: ${quoted(c.name)},\n iso: ${quoted(c.iso)},\n` + + ` rings: &[\n` + + c.rings.map((r) => ` &[${flat(r).map(real).join(",")}],`).join("\n") + + `\n ],\n },`) + .join("\n") + + `\n];\n`, +); + +// -------------------------------------------------------------------------- Go +write( + "ports/go/world_data.go", + `package hqtui\n\n${banner("//")}\n\ntype CountryOutline struct {\n\tName string\n` + + `\t// ISO 3166-1 alpha-2, where Natural Earth has one.\n\tISO string\n` + + `\t// Rings are longitude and latitude, interleaved.\n\tRings [][]float64\n}\n\n` + + `var WorldCountries = []CountryOutline{\n` + + countries + .map((c) => + `\t{\n\t\tName: ${quoted(c.name)},\n\t\tISO: ${quoted(c.iso)},\n\t\tRings: [][]float64{\n` + + c.rings.map((r) => `\t\t\t{${flat(r).join(",")}},`).join("\n") + + `\n\t\t},\n\t},`) + .join("\n") + + `\n}\n`, +); + +// ---------------------------------------------------------------------- Python +write( + "ports/python/hqtui/graphics/world_data.py", + `"""\n${banner("")}\n"""\n\nfrom __future__ import annotations\n\n` + + `from dataclasses import dataclass\n\n\n` + + `@dataclass(frozen=True, slots=True)\nclass CountryOutline:\n name: str\n` + + ` #: ISO 3166-1 alpha-2, where Natural Earth has one.\n iso: str\n` + + ` #: Longitude and latitude, interleaved.\n rings: tuple[tuple[float, ...], ...]\n\n\n` + + `WORLD_COUNTRIES: tuple[CountryOutline, ...] = (\n` + + countries + .map((c) => + ` CountryOutline(\n ${quoted(c.name)},\n ${quoted(c.iso)},\n (\n` + + c.rings.map((r) => ` (${flat(r).join(",")}),`).join("\n") + + `\n ),\n ),`) + .join("\n") + + `\n)\n`, +); + +// ------------------------------------------------------------------------- Zig +write( + "ports/zig/src/graphics/world_data.zig", + `${banner("//!")}\n\npub const CountryOutline = struct {\n name: []const u8,\n` + + ` /// ISO 3166-1 alpha-2, where Natural Earth has one.\n iso: []const u8,\n` + + ` /// Longitude and latitude, interleaved.\n rings: []const []const f64,\n};\n\n` + + `pub const WORLD_COUNTRIES = [_]CountryOutline{\n` + + countries + .map((c) => + ` .{\n .name = ${quoted(c.name)},\n .iso = ${quoted(c.iso)},\n` + + ` .rings = &.{\n` + + c.rings.map((r) => ` &.{${flat(r).map(real).join(",")}},`).join("\n") + + `\n },\n },`) + .join("\n") + + `\n};\n`, +); + +// ------------------------------------------------------------------------- C++ +// A flat coordinate pool with index tables, rather than nested initialiser +// lists: the nested form is the same data but minutes of compile time, and this +// is the one language where that bites. +const pool: number[] = []; +const ringSpans: [number, number][] = []; +const countrySpans: [number, number][] = []; +for (const c of countries) { + const first = ringSpans.length; + for (const ring of c.rings) { + const at = pool.length; + pool.push(...flat(ring)); + ringSpans.push([at, pool.length - at]); + } + countrySpans.push([first, ringSpans.length - first]); +} +const chunk = (values: string[], per: number) => { + const lines: string[] = []; + for (let i = 0; i < values.length; i += per) lines.push(` ${values.slice(i, i + per).join(",")},`); + return lines.join("\n"); +}; +write( + "ports/cpp/src/world_data.cpp", + `${banner("///")}\n#include \n\nnamespace hqtui {\nnamespace {\n\n` + + `const double POOL[] = {\n${chunk(pool.map(real), 12)}\n};\n\n` + + `struct Span {\n int at, length;\n};\n\n` + + `const Span RINGS[] = {\n${chunk(ringSpans.map(([a, n]) => `{${a},${n}}`), 8)}\n};\n\n` + + `const Span COUNTRIES[] = {\n${chunk(countrySpans.map(([a, n]) => `{${a},${n}}`), 8)}\n};\n\n` + + `const char *const NAMES[] = {\n${chunk(countries.map((c) => quoted(c.name)), 4)}\n};\n\n` + + `const char *const ISO[] = {\n${chunk(countries.map((c) => quoted(c.iso)), 12)}\n};\n\n` + + `} // namespace\n\n` + + `/// Built once, on first use: the pool above is plain static data, and this\n` + + `/// turns it into the shape the rest of the code wants without a static\n` + + `/// initialiser that has to run before main.\n` + + `const std::vector &world_countries() {\n` + + ` static const std::vector countries = [] {\n` + + ` std::vector out;\n` + + ` out.reserve(${countries.length});\n` + + ` for (std::size_t i = 0; i < ${countries.length}; i++) {\n` + + ` CountryOutline c;\n c.name = NAMES[i];\n c.iso = ISO[i];\n` + + ` const Span &cs = COUNTRIES[i];\n` + + ` for (int r = 0; r < cs.length; r++) {\n` + + ` const Span &rs = RINGS[cs.at + r];\n` + + ` c.rings.emplace_back(POOL + rs.at, POOL + rs.at + rs.length);\n` + + ` }\n out.push_back(std::move(c));\n }\n return out;\n }();\n` + + ` return countries;\n}\n\n} // namespace hqtui\n`, +); + +console.log(`${countries.length} countries, ${points} points`); diff --git a/packages/hqtui/scripts/world-probe.ts b/packages/hqtui/scripts/world-probe.ts new file mode 100644 index 0000000..3cb0d4d --- /dev/null +++ b/packages/hqtui/scripts/world-probe.ts @@ -0,0 +1,42 @@ +/** + * Prints what the country lookup answers for a fixed set of points. + * + * The same probe exists for every port, so "the ports agree about the world" is + * a diff rather than a hope. The reference output is this one, because the + * TypeScript implementation is the reference for everything else too. + * + * bun packages/hqtui/scripts/world-probe.ts + */ +import { WORLD_X, WORLD_Y, countryAt, degreesAt } from "../src/graphics/world.ts"; +import { countryAtCell } from "../src/widgets/world.ts"; + +const places: [string, number, number][] = [ + ["Paris", 2.35, 48.86], + ["Tokyo", 139.7, 35.7], + ["Cairo", 31.2, 30.0], + ["Brasilia", -47.9, -15.8], + ["Canberra", 149.1, -35.3], + ["Denver", -105.0, 39.7], + ["Moscow", 37.6, 55.75], + ["Delhi", 77.2, 28.6], + ["Nairobi", 36.8, -1.3], + ["Pacific", -140.0, 0.0], + ["Atlantic", -30.0, 0.0], + ["SouthernOcean", 80.0, -40.0], + ["NorthPacific", -150.0, 40.0], +]; +for (const [name, lon, lat] of places) { + console.log(`${name} ${countryAt(lon, lat)?.name ?? "-"}`); +} + +// The cell path, which has to agree with what the canvas drew. +for (const [column, row] of [[173, 28], [74, 2], [20, 25], [88, 7]]) { + console.log(`cell:${column},${row} ${countryAtCell(column, row, 200, 50)?.name ?? "-"}`); +} + +// And the projection itself, so a drift shows up as a number rather than as a +// country that happens to still be right. +for (const [column, row] of [[0, 0], [99, 25], [50, 13]]) { + const at = degreesAt(column, row, 100, 26, WORLD_X, WORLD_Y); + console.log(`degrees:${column},${row} ${at!.lon.toFixed(4)} ${at!.lat.toFixed(4)}`); +} diff --git a/packages/hqtui/src/graphics/world-data.ts b/packages/hqtui/src/graphics/world-data.ts index 058f9ee..d794667 100644 --- a/packages/hqtui/src/graphics/world-data.ts +++ b/packages/hqtui/src/graphics/world-data.ts @@ -1,13 +1,15 @@ /** * Country outlines, flattened for a terminal. * - * Generated by `packages/hqtui/scripts/generate-world.ts` from Natural Earth's + * Generated by packages/hqtui/scripts/generate-world.ts from Natural Earth's * 1:110m Admin 0 countries, which is public domain. Do not edit by hand. * - * Each ring is a flat list of longitude and latitude pairs -- `[lon, lat, - * lon, lat, ...]` rather than an array of pairs, because at 1982 points the - * nested form costs an object per coordinate for no gain. A country has more - * than one ring when it is more than one landmass. + * Each ring is longitude and latitude interleaved -- lon, lat, lon, lat -- + * rather than a list of pairs, because at 1982 points the nested form + * costs a container per coordinate for no gain. A country has more than one + * ring when it is more than one landmass. + * + * 171 countries, 1982 points, simplified at 1 degrees. */ export interface CountryOutline { @@ -18,1290 +20,1289 @@ export interface CountryOutline { rings: number[][]; } -/** 171 countries, 1982 points, simplified at 1 degrees. */ export const WORLD_COUNTRIES: readonly CountryOutline[] = [ { name: "Afghanistan", iso: "AF", rings: [ - [66.5,37.4,70.8,38.5,71.8,36.7,75.2,37.1,71.3,36.1,69.3,31.9,66.3,29.9,60.9,29.8,61.2,35.7,66.5,37.4], + [66.5,37.4,70.8,38.5,71.8,36.7,75.2,37.1,71.3,36.1,69.3,31.9,66.3,29.9,60.9,29.8,61.2,35.7,66.5,37.4], ], }, { name: "Albania", iso: "AL", rings: [ - [21,40.8,19.4,40.3,19.7,42.7,21,40.8], + [21,40.8,19.4,40.3,19.7,42.7,21,40.8], ], }, { name: "Algeria", iso: "DZ", rings: [ - [-8.7,27.4,-8.7,28.8,-1.3,32.3,-1.2,35.7,8.4,36.9,7.5,34.1,9.8,29.4,9.3,26.1,12,23.5,3.2,19.1,-8.7,27.4], + [-8.7,27.4,-8.7,28.8,-1.3,32.3,-1.2,35.7,8.4,36.9,7.5,34.1,9.8,29.4,9.3,26.1,12,23.5,3.2,19.1,-8.7,27.4], ], }, { name: "Angola", iso: "AO", rings: [ - [12.3,-6.1,16.3,-5.9,17.5,-8.1,21.7,-7.3,22.2,-11.1,24,-11.2,24,-12.9,21.9,-12.9,23.2,-17.5,11.7,-17.3,13.7,-11.3,12.3,-6.1], + [12.3,-6.1,16.3,-5.9,17.5,-8.1,21.7,-7.3,22.2,-11.1,24,-11.2,24,-12.9,21.9,-12.9,23.2,-17.5,11.7,-17.3,13.7,-11.3,12.3,-6.1], ], }, { name: "Antarctica", iso: "AQ", rings: [ - [-48.7,-78,-43.9,-78.5,-43.3,-80,-54.2,-80.6,-48.7,-78], - [-66.3,-80.3,-59.6,-80,-66.3,-80.3], - [-73.9,-71.3,-70.3,-68.9,-68.3,-71.4,-75,-72.1,-73.9,-71.3], - [-102.3,-71.9,-96.2,-72.5,-102.3,-71.9], - [-122.6,-73.7,-118.7,-73.5,-122.6,-73.7], - [-127.3,-73.5,-124,-73.9,-127.3,-73.5], - [-163.7,-78.6,-159.2,-79.5,-163.7,-78.6], - [180,-84.7,180,-90,-180,-90,-179.1,-84.1,-143.1,-85,-153.6,-83.7,-152.9,-82,-156.8,-81.1,-146.4,-80.3,-155.3,-79.1,-158.4,-76.9,-151.3,-77.4,-146.1,-76.5,-146.2,-75.4,-135.2,-74.3,-100.1,-74.9,-103.7,-72.6,-74.9,-73.9,-67.4,-72.5,-67.7,-67.3,-57.8,-63.3,-65.7,-68,-61.8,-70.7,-60.8,-73.7,-70.6,-76.6,-77.2,-76.7,-73.7,-77.9,-78,-79.2,-58.2,-83.2,-28.5,-80.3,-35.6,-79.5,-35.8,-78.3,-17.5,-75.1,-15.4,-73.1,-6.9,-70.9,27.1,-70.5,33.9,-68.5,38.6,-69.8,54.5,-65.8,61.4,-68,68.9,-67.9,69.7,-69.2,67.9,-71.9,69.9,-72.3,73.9,-69.9,88,-66.2,95.8,-67.4,102.8,-65.6,106.2,-66.9,113.6,-65.9,119.8,-67.3,135.1,-65.3,137.5,-67,145.5,-66.9,171.2,-71.7,163.6,-76.2,167,-78.8,161.8,-79.2,159.8,-80.9,169.4,-83.8,180,-84.7], + [-48.7,-78,-43.9,-78.5,-43.3,-80,-54.2,-80.6,-48.7,-78], + [-66.3,-80.3,-59.6,-80,-66.3,-80.3], + [-73.9,-71.3,-70.3,-68.9,-68.3,-71.4,-75,-72.1,-73.9,-71.3], + [-102.3,-71.9,-96.2,-72.5,-102.3,-71.9], + [-122.6,-73.7,-118.7,-73.5,-122.6,-73.7], + [-127.3,-73.5,-124,-73.9,-127.3,-73.5], + [-163.7,-78.6,-159.2,-79.5,-163.7,-78.6], + [180,-84.7,180,-90,-180,-90,-179.1,-84.1,-143.1,-85,-153.6,-83.7,-152.9,-82,-156.8,-81.1,-146.4,-80.3,-155.3,-79.1,-158.4,-76.9,-151.3,-77.4,-146.1,-76.5,-146.2,-75.4,-135.2,-74.3,-100.1,-74.9,-103.7,-72.6,-74.9,-73.9,-67.4,-72.5,-67.7,-67.3,-57.8,-63.3,-65.7,-68,-61.8,-70.7,-60.8,-73.7,-70.6,-76.6,-77.2,-76.7,-73.7,-77.9,-78,-79.2,-58.2,-83.2,-28.5,-80.3,-35.6,-79.5,-35.8,-78.3,-17.5,-75.1,-15.4,-73.1,-6.9,-70.9,27.1,-70.5,33.9,-68.5,38.6,-69.8,54.5,-65.8,61.4,-68,68.9,-67.9,69.7,-69.2,67.9,-71.9,69.9,-72.3,73.9,-69.9,88,-66.2,95.8,-67.4,102.8,-65.6,106.2,-66.9,113.6,-65.9,119.8,-67.3,135.1,-65.3,137.5,-67,145.5,-66.9,171.2,-71.7,163.6,-76.2,167,-78.8,161.8,-79.2,159.8,-80.9,169.4,-83.8,180,-84.7], ], }, { name: "Argentina", iso: "AR", rings: [ - [-68.6,-52.6,-65,-54.7,-68.6,-54.9,-68.6,-52.6], - [-57.6,-30.2,-58.5,-34.4,-56.8,-36.9,-62.3,-38.8,-62.7,-41,-65.1,-41.1,-63.5,-42.6,-67.3,-45.6,-65.6,-47.2,-69.1,-50.7,-68.1,-52.3,-71.9,-52,-73.4,-49.3,-71.2,-44.8,-72.1,-42.3,-68.4,-24.5,-66.3,-21.8,-62.8,-22,-57.8,-25.2,-58.6,-27.1,-55.7,-27.4,-54.1,-25.5,-53.6,-26.9,-57.6,-30.2], + [-68.6,-52.6,-65,-54.7,-68.6,-54.9,-68.6,-52.6], + [-57.6,-30.2,-58.5,-34.4,-56.8,-36.9,-62.3,-38.8,-62.7,-41,-65.1,-41.1,-63.5,-42.6,-67.3,-45.6,-65.6,-47.2,-69.1,-50.7,-68.1,-52.3,-71.9,-52,-73.4,-49.3,-71.2,-44.8,-72.1,-42.3,-68.4,-24.5,-66.3,-21.8,-62.8,-22,-57.8,-25.2,-58.6,-27.1,-55.7,-27.4,-54.1,-25.5,-53.6,-26.9,-57.6,-30.2], ], }, { name: "Armenia", iso: "AM", rings: [ - [46.5,38.8,43.6,41.1,45.6,40.8,46.5,38.8], + [46.5,38.8,43.6,41.1,45.6,40.8,46.5,38.8], ], }, { name: "Australia", iso: "AU", rings: [ - [147.7,-40.8,147.9,-43.2,146,-43.5,144.7,-40.7,147.7,-40.8], - [126.1,-32.2,118,-35.1,115,-34.2,113.7,-22.5,120.9,-19.7,125.7,-14.2,129.6,-15,132.4,-11.1,136.5,-11.9,135.5,-15,140.2,-17.7,142.5,-10.7,146.4,-19,150.7,-22.4,153.6,-28.1,150,-37.4,146.3,-39,140.6,-38,138.2,-34.4,136.8,-35.3,137.8,-32.9,136,-34.9,131.3,-31.5,126.1,-32.2], + [147.7,-40.8,147.9,-43.2,146,-43.5,144.7,-40.7,147.7,-40.8], + [126.1,-32.2,118,-35.1,115,-34.2,113.7,-22.5,120.9,-19.7,125.7,-14.2,129.6,-15,132.4,-11.1,136.5,-11.9,135.5,-15,140.2,-17.7,142.5,-10.7,146.4,-19,150.7,-22.4,153.6,-28.1,150,-37.4,146.3,-39,140.6,-38,138.2,-34.4,136.8,-35.3,137.8,-32.9,136,-34.9,131.3,-31.5,126.1,-32.2], ], }, { name: "Austria", iso: "AT", rings: [ - [17,48.1,14.6,46.4,9.5,47.1,12.9,47.5,13.6,48.9,17,48.1], + [17,48.1,14.6,46.4,9.5,47.1,12.9,47.5,13.6,48.9,17,48.1], ], }, { name: "Azerbaijan", iso: "AZ", rings: [ - [46.4,41.9,50.4,40.3,48.9,38.3,45.6,39.9,45,41.2,46.4,41.9], + [46.4,41.9,50.4,40.3,48.9,38.3,45.6,39.9,45,41.2,46.4,41.9], ], }, { name: "Bahamas", iso: "BS", rings: [ - [-78.2,25.2,-77.5,23.8,-78.2,25.2], + [-78.2,25.2,-77.5,23.8,-78.2,25.2], ], }, { name: "Bangladesh", iso: "BD", rings: [ - [92.7,22,92.4,20.7,91.4,22.8,89,22.1,88.6,26.4,92.4,25,91.2,23.5,92.7,22], + [92.7,22,92.4,20.7,91.4,22.8,89,22.1,88.6,26.4,92.4,25,91.2,23.5,92.7,22], ], }, { name: "Belarus", iso: "BY", rings: [ - [28.2,56.2,30.9,55.6,32.7,53.4,31.8,52.1,23.5,51.6,23.5,53.9,28.2,56.2], + [28.2,56.2,30.9,55.6,32.7,53.4,31.8,52.1,23.5,51.6,23.5,53.9,28.2,56.2], ], }, { name: "Belgium", iso: "BE", rings: [ - [6.2,50.8,5.7,49.5,2.5,51.1,6.2,50.8], + [6.2,50.8,5.7,49.5,2.5,51.1,6.2,50.8], ], }, { name: "Belize", iso: "BZ", rings: [ - [-89.1,17.8,-88.1,18.3,-88.9,15.9,-89.1,17.8], + [-89.1,17.8,-88.1,18.3,-88.9,15.9,-89.1,17.8], ], }, { name: "Benin", iso: "BJ", rings: [ - [2.7,6.3,0.8,10.5,2.8,12.2,2.7,6.3], + [2.7,6.3,0.8,10.5,2.8,12.2,2.7,6.3], ], }, { name: "Bhutan", iso: "BT", rings: [ - [91.7,27.8,88.8,27.1,91.7,27.8], + [91.7,27.8,88.8,27.1,91.7,27.8], ], }, { name: "Bolivia", iso: "BO", rings: [ - [-69.5,-11,-65.3,-9.8,-65.4,-11.6,-60.5,-13.8,-60.2,-16.3,-58.2,-16.3,-57.9,-20,-61.8,-19.6,-62.7,-22.2,-67.8,-22.9,-69.5,-11], + [-69.5,-11,-65.3,-9.8,-65.4,-11.6,-60.5,-13.8,-60.2,-16.3,-58.2,-16.3,-57.9,-20,-61.8,-19.6,-62.7,-22.2,-67.8,-22.9,-69.5,-11], ], }, { name: "Bosnia and Herz.", iso: "BA", rings: [ - [18.6,42.7,16,45.2,19.4,44.9,18.6,42.7], + [18.6,42.7,16,45.2,19.4,44.9,18.6,42.7], ], }, { name: "Botswana", iso: "BW", rings: [ - [29.4,-22.1,25.7,-25.5,21.6,-26.7,19.9,-24.8,20.9,-18.3,25.3,-17.7,29.4,-22.1], + [29.4,-22.1,25.7,-25.5,21.6,-26.7,19.9,-24.8,20.9,-18.3,25.3,-17.7,29.4,-22.1], ], }, { name: "Brazil", iso: "BR", rings: [ - [-53.4,-33.8,-53.8,-32,-57.6,-30.2,-53.6,-26.1,-55.8,-22.4,-57.9,-22.1,-58.2,-16.3,-60.2,-16.3,-60.5,-13.8,-65.4,-11.6,-65.3,-9.8,-70.5,-11,-70.5,-9.5,-72.2,-10.1,-74,-7.5,-72.9,-5.3,-69.9,-4.3,-69.8,1.7,-65.5,0.8,-63.4,2.2,-64.8,4.1,-60.7,5.2,-59,1.3,-52.9,2.1,-51.3,4.2,-50.4,-0.1,-44.6,-2.7,-40,-2.9,-35.6,-5.1,-34.7,-7.3,-38.7,-13.1,-40.9,-21.9,-47.6,-24.9,-48.9,-28.7,-53.4,-33.8], + [-53.4,-33.8,-53.8,-32,-57.6,-30.2,-53.6,-26.1,-55.8,-22.4,-57.9,-22.1,-58.2,-16.3,-60.2,-16.3,-60.5,-13.8,-65.4,-11.6,-65.3,-9.8,-70.5,-11,-70.5,-9.5,-72.2,-10.1,-74,-7.5,-72.9,-5.3,-69.9,-4.3,-69.8,1.7,-65.5,0.8,-63.4,2.2,-64.8,4.1,-60.7,5.2,-59,1.3,-52.9,2.1,-51.3,4.2,-50.4,-0.1,-44.6,-2.7,-40,-2.9,-35.6,-5.1,-34.7,-7.3,-38.7,-13.1,-40.9,-21.9,-47.6,-24.9,-48.9,-28.7,-53.4,-33.8], ], }, { name: "Bulgaria", iso: "BG", rings: [ - [22.7,44.2,28.6,43.7,28,42,23,41.3,22.7,44.2], + [22.7,44.2,28.6,43.7,28,42,23,41.3,22.7,44.2], ], }, { name: "Burkina Faso", iso: "BF", rings: [ - [-5.4,10.4,-4.3,13.2,-1.1,15,2.2,12.6,0.9,11,-2.9,11,-2.8,9.6,-5.4,10.4], + [-5.4,10.4,-4.3,13.2,-1.1,15,2.2,12.6,0.9,11,-2.9,11,-2.8,9.6,-5.4,10.4], ], }, { name: "Burundi", iso: "BI", rings: [ - [30.5,-2.4,29.3,-4.5,29,-2.8,30.5,-2.4], + [30.5,-2.4,29.3,-4.5,29,-2.8,30.5,-2.4], ], }, { name: "Cambodia", iso: "KH", rings: [ - [102.6,12.2,103,14.2,107.6,13.5,106.2,11,103.5,10.6,102.6,12.2], + [102.6,12.2,103,14.2,107.6,13.5,106.2,11,103.5,10.6,102.6,12.2], ], }, { name: "Cameroon", iso: "CM", rings: [ - [14.5,12.9,14.5,4.7,15.9,1.7,9.6,2.3,8.8,5.5,11.7,7,14.5,12.9], + [14.5,12.9,14.5,4.7,15.9,1.7,9.6,2.3,8.8,5.5,11.7,7,14.5,12.9], ], }, { name: "Canada", iso: "CA", rings: [ - [-122.8,49,-127.4,50.8,-130.5,54.3,-130,55.9,-135.5,59.8,-137.5,58.9,-141,60.3,-141,69.7,-136.5,68.9,-128.1,70.5,-113.5,67.7,-106.1,68.8,-101.5,67.6,-97.7,68.6,-96.1,67.3,-94.2,69.1,-96.5,70.1,-95.2,71.9,-87.4,67.2,-85.5,69.9,-82.6,69.7,-81.4,67.1,-85.8,66.6,-90.7,63.6,-94.7,58.9,-92.3,57.1,-82.3,55.1,-79.9,51.2,-78.6,52.6,-79.8,54.7,-76.5,56.5,-78.5,58.8,-77.3,59.9,-78.1,62.3,-73.8,62.4,-69.6,61.1,-67.6,58.2,-64.6,60.3,-61.8,56.3,-57.3,54.6,-55.7,52.1,-60,50.2,-66.4,50.2,-71.1,46.8,-65.1,49.2,-64.5,46.2,-60.5,47,-59.8,45.9,-65.4,43.5,-66.2,44.5,-64.4,45.3,-67.1,45.1,-69.2,47.4,-71.5,45,-82.4,41.7,-82.6,45.3,-88.4,48.3,-122.8,49], - [-84,62.5,-81.9,62.9,-84,62.5], - [-79.8,72.8,-80.8,73.7,-76.3,72.8,-79.8,72.8], - [-93.6,75,-96.8,74.9,-93.6,75], - [-93.8,77.5,-96.4,77.8,-93.8,77.5], - [-96.8,78.8,-95.6,78.4,-98.6,78.9,-96.8,78.8], - [-88.2,74.4,-97.1,76.8,-79.8,74.9,-88.2,74.4], - [-111.3,78.2,-109.9,78,-113.5,77.7,-111.3,78.2], - [-111,78.8,-109.7,78.6,-112.5,78.4,-111,78.8], - [-55.6,51.3,-56.8,49.8,-53.5,49.2,-53.1,46.7,-59.3,47.6,-55.6,51.3], - [-83.9,65.1,-80.1,63.7,-87.2,63.5,-85.9,65.7,-83.9,65.1], - [-78.8,72.4,-68.8,70.5,-67,69.2,-68.8,68.7,-61.9,66.9,-63.9,65,-68,66.3,-64.7,63.4,-68.8,63.7,-66.2,61.9,-68.9,62.3,-78.6,64.6,-74,65.5,-73.3,68.1,-79,70.2,-88.7,70.4,-90.2,72.2,-85.8,73.8,-85.8,72.5,-82.3,73.8,-78.8,72.4], - [-94.5,74.1,-90.5,73.9,-95.4,72.1,-96,73.4,-94.5,74.1], - [-122.9,76.1,-116.2,77.6,-122.9,76.1], - [-132.7,54,-131.2,52.2,-132.7,54], - [-105.5,79.3,-99.7,77.9,-105.5,79.3], - [-123.5,48.5,-128.4,50.8,-123.5,48.5], - [-121.5,74.4,-115.5,73.5,-123.1,70.9,-125.9,71.9,-123.9,73.7,-124.9,74.3,-121.5,74.4], - [-107.8,75.8,-105.7,75.5,-117.7,75.2,-115.4,76.5,-107.8,75.8], - [-106.5,73.1,-101.1,69.6,-113.3,68.5,-117.3,70,-112.4,70.4,-119.4,71.6,-115.2,73.3,-108.2,71.7,-108.4,73.1,-106.5,73.1], - [-100.4,72.7,-101.5,73.4,-97.4,73.8,-96.5,72.6,-98.4,71.3,-102.5,72.5,-100.4,72.7], - [-106.6,73.6,-104.5,73.4,-106.6,73.6], - [-98.5,76.7,-98.2,75,-102.5,75.6,-98.5,76.7], - [-96,80.6,-92.4,81.3,-85.8,79.3,-92.9,78.3,-96,80.6], - [-91.6,81.9,-61.8,82.6,-76.9,79.3,-75.4,78.5,-80.6,76.2,-89.5,76.5,-88.3,77.9,-85,77.5,-88,78.4,-85.1,79.3,-86.9,80.3,-81.8,80.5,-91.6,81.9], - [-75.2,67.4,-77.2,67.6,-75.2,67.4], - [-96.3,69.5,-99.8,69.4,-96.3,69.5], - [-64.5,49.9,-61.8,49.1,-64.5,49.9], - [-64,47,-62,46.4,-64,47], + [-122.8,49,-127.4,50.8,-130.5,54.3,-130,55.9,-135.5,59.8,-137.5,58.9,-141,60.3,-141,69.7,-136.5,68.9,-128.1,70.5,-113.5,67.7,-106.1,68.8,-101.5,67.6,-97.7,68.6,-96.1,67.3,-94.2,69.1,-96.5,70.1,-95.2,71.9,-87.4,67.2,-85.5,69.9,-82.6,69.7,-81.4,67.1,-85.8,66.6,-90.7,63.6,-94.7,58.9,-92.3,57.1,-82.3,55.1,-79.9,51.2,-78.6,52.6,-79.8,54.7,-76.5,56.5,-78.5,58.8,-77.3,59.9,-78.1,62.3,-73.8,62.4,-69.6,61.1,-67.6,58.2,-64.6,60.3,-61.8,56.3,-57.3,54.6,-55.7,52.1,-60,50.2,-66.4,50.2,-71.1,46.8,-65.1,49.2,-64.5,46.2,-60.5,47,-59.8,45.9,-65.4,43.5,-66.2,44.5,-64.4,45.3,-67.1,45.1,-69.2,47.4,-71.5,45,-82.4,41.7,-82.6,45.3,-88.4,48.3,-122.8,49], + [-84,62.5,-81.9,62.9,-84,62.5], + [-79.8,72.8,-80.8,73.7,-76.3,72.8,-79.8,72.8], + [-93.6,75,-96.8,74.9,-93.6,75], + [-93.8,77.5,-96.4,77.8,-93.8,77.5], + [-96.8,78.8,-95.6,78.4,-98.6,78.9,-96.8,78.8], + [-88.2,74.4,-97.1,76.8,-79.8,74.9,-88.2,74.4], + [-111.3,78.2,-109.9,78,-113.5,77.7,-111.3,78.2], + [-111,78.8,-109.7,78.6,-112.5,78.4,-111,78.8], + [-55.6,51.3,-56.8,49.8,-53.5,49.2,-53.1,46.7,-59.3,47.6,-55.6,51.3], + [-83.9,65.1,-80.1,63.7,-87.2,63.5,-85.9,65.7,-83.9,65.1], + [-78.8,72.4,-68.8,70.5,-67,69.2,-68.8,68.7,-61.9,66.9,-63.9,65,-68,66.3,-64.7,63.4,-68.8,63.7,-66.2,61.9,-68.9,62.3,-78.6,64.6,-74,65.5,-73.3,68.1,-79,70.2,-88.7,70.4,-90.2,72.2,-85.8,73.8,-85.8,72.5,-82.3,73.8,-78.8,72.4], + [-94.5,74.1,-90.5,73.9,-95.4,72.1,-96,73.4,-94.5,74.1], + [-122.9,76.1,-116.2,77.6,-122.9,76.1], + [-132.7,54,-131.2,52.2,-132.7,54], + [-105.5,79.3,-99.7,77.9,-105.5,79.3], + [-123.5,48.5,-128.4,50.8,-123.5,48.5], + [-121.5,74.4,-115.5,73.5,-123.1,70.9,-125.9,71.9,-123.9,73.7,-124.9,74.3,-121.5,74.4], + [-107.8,75.8,-105.7,75.5,-117.7,75.2,-115.4,76.5,-107.8,75.8], + [-106.5,73.1,-101.1,69.6,-113.3,68.5,-117.3,70,-112.4,70.4,-119.4,71.6,-115.2,73.3,-108.2,71.7,-108.4,73.1,-106.5,73.1], + [-100.4,72.7,-101.5,73.4,-97.4,73.8,-96.5,72.6,-98.4,71.3,-102.5,72.5,-100.4,72.7], + [-106.6,73.6,-104.5,73.4,-106.6,73.6], + [-98.5,76.7,-98.2,75,-102.5,75.6,-98.5,76.7], + [-96,80.6,-92.4,81.3,-85.8,79.3,-92.9,78.3,-96,80.6], + [-91.6,81.9,-61.8,82.6,-76.9,79.3,-75.4,78.5,-80.6,76.2,-89.5,76.5,-88.3,77.9,-85,77.5,-88,78.4,-85.1,79.3,-86.9,80.3,-81.8,80.5,-91.6,81.9], + [-75.2,67.4,-77.2,67.6,-75.2,67.4], + [-96.3,69.5,-99.8,69.4,-96.3,69.5], + [-64.5,49.9,-61.8,49.1,-64.5,49.9], + [-64,47,-62,46.4,-64,47], ], }, { name: "Central African Rep.", iso: "CF", rings: [ - [27.4,5.2,22.4,4,19.5,5,16,2.3,14.5,5.5,15.3,7.4,22.9,11.1,27.4,5.2], + [27.4,5.2,22.4,4,19.5,5,16,2.3,14.5,5.5,15.3,7.4,22.9,11.1,27.4,5.2], ], }, { name: "Chad", iso: "TD", rings: [ - [23.8,19.6,23.9,15.6,21.9,12.6,22.9,11.1,15.3,7.4,13.5,14.4,15.9,20.4,14.9,22.9,23.8,19.6], + [23.8,19.6,23.9,15.6,21.9,12.6,22.9,11.1,15.3,7.4,13.5,14.4,15.9,20.4,14.9,22.9,23.8,19.6], ], }, { name: "Chile", iso: "CL", rings: [ - [-68.6,-52.6,-68.6,-54.9,-67,-54.9,-68.1,-55.6,-74.7,-52.8,-71.1,-54.1,-68.6,-52.6], - [-69.6,-17.6,-67,-23,-70.5,-31.4,-69.8,-34.2,-72.1,-42.3,-71.2,-44.8,-73.4,-49.3,-71.9,-52,-68.6,-52.3,-71.4,-53.9,-74.9,-52.3,-75.6,-48.7,-74.1,-46.9,-75.6,-46.6,-72.7,-42.4,-74.3,-43.2,-69.6,-17.6], + [-68.6,-52.6,-68.6,-54.9,-67,-54.9,-68.1,-55.6,-74.7,-52.8,-71.1,-54.1,-68.6,-52.6], + [-69.6,-17.6,-67,-23,-70.5,-31.4,-69.8,-34.2,-72.1,-42.3,-71.2,-44.8,-73.4,-49.3,-71.9,-52,-68.6,-52.3,-71.4,-53.9,-74.9,-52.3,-75.6,-48.7,-74.1,-46.9,-75.6,-46.6,-72.7,-42.4,-74.3,-43.2,-69.6,-17.6], ], }, { name: "China", iso: "CN", rings: [ - [109.5,18.2,108.6,19.4,110.8,20.1,109.5,18.2], - [80.3,42.3,80,44.9,87.8,49.3,91,46.9,90.9,45.3,96.3,42.7,109.2,42.5,111.9,45.1,119.7,46.7,115.5,48.1,122.2,53.4,125.9,52.8,131,47.8,135,48.5,133.1,45.1,131,45,130.6,42.4,121.1,38.9,121.6,40.9,117.5,38.7,122.4,37.5,119.2,34.9,121.9,31.7,121.7,28.2,118.7,24.5,110.4,20.3,105.3,23.4,101.7,22.3,101.8,21.2,99.2,22.1,97.6,23.9,98.7,27.5,96.1,29.5,88.8,27.3,78.7,31.5,78.9,34.3,73.7,39.4,80.3,42.3], + [109.5,18.2,108.6,19.4,110.8,20.1,109.5,18.2], + [80.3,42.3,80,44.9,87.8,49.3,91,46.9,90.9,45.3,96.3,42.7,109.2,42.5,111.9,45.1,119.7,46.7,115.5,48.1,122.2,53.4,125.9,52.8,131,47.8,135,48.5,133.1,45.1,131,45,130.6,42.4,121.1,38.9,121.6,40.9,117.5,38.7,122.4,37.5,119.2,34.9,121.9,31.7,121.7,28.2,118.7,24.5,110.4,20.3,105.3,23.4,101.7,22.3,101.8,21.2,99.2,22.1,97.6,23.9,98.7,27.5,96.1,29.5,88.8,27.3,78.7,31.5,78.9,34.3,73.7,39.4,80.3,42.3], ], }, { name: "Colombia", iso: "CO", rings: [ - [-66.9,1.3,-69.8,1.7,-69.9,-4.3,-70,-2.7,-77.4,0.4,-79,1.7,-77.1,3.8,-77.5,8.5,-71.4,12.4,-73.3,9.2,-72,7,-67.3,6.1,-66.9,1.3], + [-66.9,1.3,-69.8,1.7,-69.9,-4.3,-70,-2.7,-77.4,0.4,-79,1.7,-77.1,3.8,-77.5,8.5,-71.4,12.4,-73.3,9.2,-72,7,-67.3,6.1,-66.9,1.3], ], }, { name: "Congo", iso: "CG", rings: [ - [18.5,3.5,16,-3.5,11.9,-5,11.5,-2.8,14.4,-1.3,13.1,2.3,15.9,1.7,18.5,3.5], + [18.5,3.5,16,-3.5,11.9,-5,11.5,-2.8,14.4,-1.3,13.1,2.3,15.9,1.7,18.5,3.5], ], }, { name: "Costa Rica", iso: "CR", rings: [ - [-82.5,9.6,-83,8.2,-85.9,10.9,-82.5,9.6], + [-82.5,9.6,-83,8.2,-85.9,10.9,-82.5,9.6], ], }, { name: "Côte d'Ivoire", iso: "CI", rings: [ - [-8,10.2,-2.8,9.6,-2.9,5,-7.7,4.4,-8,10.2], + [-8,10.2,-2.8,9.6,-2.9,5,-7.7,4.4,-8,10.2], ], }, { name: "Croatia", iso: "HR", rings: [ - [16.6,46.5,19.4,45.2,15.8,44.8,18.5,42.5,13.7,45.1,16.6,46.5], + [16.6,46.5,19.4,45.2,15.8,44.8,18.5,42.5,13.7,45.1,16.6,46.5], ], }, { name: "Cuba", iso: "CU", rings: [ - [-82.3,23.2,-74.2,20.3,-77.8,19.9,-81.8,22.6,-85,21.9,-82.3,23.2], + [-82.3,23.2,-74.2,20.3,-77.8,19.9,-81.8,22.6,-85,21.9,-82.3,23.2], ], }, { name: "Cyprus", iso: "CY", rings: [ - [32.7,35.1,34,35,32.7,35.1], + [32.7,35.1,34,35,32.7,35.1], ], }, { name: "Czechia", iso: "CZ", rings: [ - [15,51.1,18.9,49.5,12.5,49.5,15,51.1], + [15,51.1,18.9,49.5,12.5,49.5,15,51.1], ], }, { name: "Dem. Rep. Congo", iso: "CD", rings: [ - [29.3,-4.5,30.7,-8.3,28.7,-8.5,28.4,-11.8,29.7,-13.3,22.2,-11.1,21.7,-7.3,17.5,-8.1,16.3,-5.9,12.2,-5.8,16,-3.5,19.5,5,29.7,4.6,31.2,2.2,29.3,-4.5], + [29.3,-4.5,30.7,-8.3,28.7,-8.5,28.4,-11.8,29.7,-13.3,22.2,-11.1,21.7,-7.3,17.5,-8.1,16.3,-5.9,12.2,-5.8,16,-3.5,19.5,5,29.7,4.6,31.2,2.2,29.3,-4.5], ], }, { name: "Denmark", iso: "DK", rings: [ - [9.9,55,8.1,56.5,10.6,57.7,9.9,55], - [12.4,56.1,12.1,54.8,11,55.4,12.4,56.1], + [9.9,55,8.1,56.5,10.6,57.7,9.9,55], + [12.4,56.1,12.1,54.8,11,55.4,12.4,56.1], ], }, { name: "Djibouti", iso: "DJ", rings: [ - [42.4,12.5,42.8,10.9,42.4,12.5], + [42.4,12.5,42.8,10.9,42.4,12.5], ], }, { name: "Dominican Rep.", iso: "DO", rings: [ - [-71.7,18,-71.6,19.9,-68.3,18.6,-71.7,18], + [-71.7,18,-71.6,19.9,-68.3,18.6,-71.7,18], ], }, { name: "Ecuador", iso: "EC", rings: [ - [-75.4,-0.2,-78.6,-4.5,-80.4,-4.4,-80.1,0.8,-75.4,-0.2], + [-75.4,-0.2,-78.6,-4.5,-80.4,-4.4,-80.1,0.8,-75.4,-0.2], ], }, { name: "Egypt", iso: "EG", rings: [ - [36.9,22,25,22,25.2,31.6,34.3,31.2,34.2,27.8,32.3,29.8,36.9,22], + [36.9,22,25,22,25.2,31.6,34.3,31.2,34.2,27.8,32.3,29.8,36.9,22], ], }, { name: "El Salvador", iso: "SV", rings: [ - [-89.4,14.4,-87.9,13.1,-90.1,13.7,-89.4,14.4], + [-89.4,14.4,-87.9,13.1,-90.1,13.7,-89.4,14.4], ], }, { name: "Eq. Guinea", iso: "GQ", rings: [ - [9.6,2.3,11.3,1.1,9.5,1,9.6,2.3], + [9.6,2.3,11.3,1.1,9.5,1,9.6,2.3], ], }, { name: "Eritrea", iso: "ER", rings: [ - [36.4,14.4,38.4,18,43.1,12.7,36.4,14.4], + [36.4,14.4,38.4,18,43.1,12.7,36.4,14.4], ], }, { name: "Estonia", iso: "EE", rings: [ - [28,59.5,27.3,57.5,23.3,59.2,28,59.5], + [28,59.5,27.3,57.5,23.3,59.2,28,59.5], ], }, { name: "eSwatini", iso: "SZ", rings: [ - [32.1,-26.7,31,-25.7,32.1,-26.7], + [32.1,-26.7,31,-25.7,32.1,-26.7], ], }, { name: "Ethiopia", iso: "ET", rings: [ - [47.8,8,45,5,39.6,3.4,36.2,4.4,33,7.8,37.9,15,41.6,13.5,43.7,9.2,47.8,8], + [47.8,8,45,5,39.6,3.4,36.2,4.4,33,7.8,37.9,15,41.6,13.5,43.7,9.2,47.8,8], ], }, { name: "Falkland Is.", iso: "FK", rings: [ - [-61.2,-51.8,-57.7,-51.5,-61.2,-51.8], + [-61.2,-51.8,-57.7,-51.5,-61.2,-51.8], ], }, { name: "Finland", iso: "FI", rings: [ - [28.6,69.1,31.1,62.4,28.1,60.5,21.3,60.7,21.5,63.2,25.4,65.1,20.6,69.1,24.7,68.6,27.7,70.2,28.6,69.1], + [28.6,69.1,31.1,62.4,28.1,60.5,21.3,60.7,21.5,63.2,25.4,65.1,20.6,69.1,24.7,68.6,27.7,70.2,28.6,69.1], ], }, { name: "Fr. S. Antarctic Lands", iso: "TF", rings: [ - [68.9,-48.6,70.6,-49.3,68.7,-49.8,68.9,-48.6], + [68.9,-48.6,70.6,-49.3,68.7,-49.8,68.9,-48.6], ], }, { name: "France", iso: "FR", rings: [ - [-51.7,4.2,-52.9,2.1,-54.5,2.3,-54,5.8,-51.7,4.2], - [6.2,49.5,8.1,49,6,46.7,7.4,43.7,1.8,42.3,-1.9,43.4,-1.2,46,-4.6,48.7,-1.6,48.6,-1.9,49.8,2.5,51.1,6.2,49.5], - [8.7,42.6,9.2,41.4,8.7,42.6], + [-51.7,4.2,-52.9,2.1,-54.5,2.3,-54,5.8,-51.7,4.2], + [6.2,49.5,8.1,49,6,46.7,7.4,43.7,1.8,42.3,-1.9,43.4,-1.2,46,-4.6,48.7,-1.6,48.6,-1.9,49.8,2.5,51.1,6.2,49.5], + [8.7,42.6,9.2,41.4,8.7,42.6], ], }, { name: "Gabon", iso: "GA", rings: [ - [11.3,2.3,14.3,1.2,14.4,-1.3,11.1,-4,8.8,-1.1,11.3,2.3], + [11.3,2.3,14.3,1.2,14.4,-1.3,11.1,-4,8.8,-1.1,11.3,2.3], ], }, { name: "Gambia", iso: "GM", rings: [ - [-16.7,13.6,-13.8,13.5,-16.7,13.6], + [-16.7,13.6,-13.8,13.5,-16.7,13.6], ], }, { name: "Georgia", iso: "GE", rings: [ - [40,43.4,46.6,41.2,41.6,41.5,40,43.4], + [40,43.4,46.6,41.2,41.6,41.5,40,43.4], ], }, { name: "Germany", iso: "DE", rings: [ - [14.1,53.8,15,51.1,12.2,50.3,12.9,47.5,7.5,47.6,8.1,49,6,50.1,7.1,53.7,9.9,55,14.1,53.8], + [14.1,53.8,15,51.1,12.2,50.3,12.9,47.5,7.5,47.6,8.1,49,6,50.1,7.1,53.7,9.9,55,14.1,53.8], ], }, { name: "Ghana", iso: "GH", rings: [ - [0,11,1.1,5.9,-2.9,5,-2.9,11,0,11], + [0,11,1.1,5.9,-2.9,5,-2.9,11,0,11], ], }, { name: "Greece", iso: "GR", rings: [ - [26.3,35.3,23.5,35.3,26.3,35.3], - [23,41.3,26.6,41.6,22.6,40.3,24,37.7,22.5,36.4,20.2,39.6,23,41.3], + [26.3,35.3,23.5,35.3,26.3,35.3], + [23,41.3,26.6,41.6,22.6,40.3,24,37.7,22.5,36.4,20.2,39.6,23,41.3], ], }, { name: "Greenland", iso: "GL", rings: [ - [-46.8,82.6,-27.1,83.5,-20.8,82.7,-31.9,82.2,-12.2,81.3,-20,80.2,-17.7,80.1,-19.7,78.8,-18.5,77,-21.7,76.6,-19.4,74.3,-24.8,72.3,-21.8,70.7,-25.5,71.4,-26.4,70.2,-22.3,70.1,-39.8,65.5,-43.4,60.1,-48.3,60.9,-51.6,63.6,-54,67.2,-50.9,69.9,-54.7,69.6,-54.4,70.8,-51.4,70.6,-55.8,71.7,-54.7,72.6,-58.6,75.5,-68.5,76.1,-71.4,77,-66.8,77.4,-73.3,78,-65.7,79.4,-68,80.1,-62.7,81.8,-44.5,81.7,-46.8,82.6], + [-46.8,82.6,-27.1,83.5,-20.8,82.7,-31.9,82.2,-12.2,81.3,-20,80.2,-17.7,80.1,-19.7,78.8,-18.5,77,-21.7,76.6,-19.4,74.3,-24.8,72.3,-21.8,70.7,-25.5,71.4,-26.4,70.2,-22.3,70.1,-39.8,65.5,-43.4,60.1,-48.3,60.9,-51.6,63.6,-54,67.2,-50.9,69.9,-54.7,69.6,-54.4,70.8,-51.4,70.6,-55.8,71.7,-54.7,72.6,-58.6,75.5,-68.5,76.1,-71.4,77,-66.8,77.4,-73.3,78,-65.7,79.4,-68,80.1,-62.7,81.8,-44.5,81.7,-46.8,82.6], ], }, { name: "Guatemala", iso: "GT", rings: [ - [-92.2,14.5,-90.5,16.1,-91,17.8,-89.1,17.8,-88.2,15.7,-89.4,14.4,-92.2,14.5], + [-92.2,14.5,-90.5,16.1,-91,17.8,-89.1,17.8,-88.2,15.7,-89.4,14.4,-92.2,14.5], ], }, { name: "Guinea", iso: "GN", rings: [ - [-13.7,12.6,-9.1,12.3,-8.3,7.7,-11.1,10,-13.2,8.9,-15.1,11,-13.7,12.6], + [-13.7,12.6,-9.1,12.3,-8.3,7.7,-11.1,10,-13.2,8.9,-15.1,11,-13.7,12.6], ], }, { name: "Guinea-Bissau", iso: "GW", rings: [ - [-16.7,12.4,-13.7,11.8,-15.1,11,-16.7,12.4], + [-16.7,12.4,-13.7,11.8,-15.1,11,-16.7,12.4], ], }, { name: "Guyana", iso: "GY", rings: [ - [-56.5,1.9,-59.6,1.8,-61.4,6,-59.8,8.4,-57.1,6,-58,4.1,-56.5,1.9], + [-56.5,1.9,-59.6,1.8,-61.4,6,-59.8,8.4,-57.1,6,-58,4.1,-56.5,1.9], ], }, { name: "Haiti", iso: "HT", rings: [ - [-71.7,19.7,-71.7,18,-74.5,18.3,-71.7,19.7], + [-71.7,19.7,-71.7,18,-74.5,18.3,-71.7,19.7], ], }, { name: "Honduras", iso: "HN", rings: [ - [-83.1,15,-87.3,13,-89.4,14.4,-87.9,15.9,-83.1,15], + [-83.1,15,-87.3,13,-89.4,14.4,-87.9,15.9,-83.1,15], ], }, { name: "Hungary", iso: "HU", rings: [ - [22.1,48.4,21,46.3,16.2,46.9,17,48.1,22.1,48.4], + [22.1,48.4,21,46.3,16.2,46.9,17,48.1,22.1,48.4], ], }, { name: "Iceland", iso: "IS", rings: [ - [-14.5,66.5,-13.6,65.1,-18.7,63.5,-22.8,64,-21.8,64.4,-24,64.9,-22.2,65.4,-24.3,65.6,-14.5,66.5], + [-14.5,66.5,-13.6,65.1,-18.7,63.5,-22.8,64,-21.8,64.4,-24,64.9,-22.2,65.4,-24.3,65.6,-14.5,66.5], ], }, { name: "India", iso: "IN", rings: [ - [97.3,28.3,92.7,22,91.2,23.5,92.4,25,88.6,26.4,88.9,21.7,80.3,15.9,79.9,10.4,77.5,8,72.6,21.4,70.5,20.9,68.2,23.7,71,24.4,69.5,26.9,75.3,32.3,73.7,34.3,77.8,35.5,78.7,31.5,81.1,30.2,80.1,28.8,83.3,27.4,88.1,26.4,88.7,28.1,92,26.8,96.1,29.5,97.3,28.3], + [97.3,28.3,92.7,22,91.2,23.5,92.4,25,88.6,26.4,88.9,21.7,80.3,15.9,79.9,10.4,77.5,8,72.6,21.4,70.5,20.9,68.2,23.7,71,24.4,69.5,26.9,75.3,32.3,73.7,34.3,77.8,35.5,78.7,31.5,81.1,30.2,80.1,28.8,83.3,27.4,88.1,26.4,88.7,28.1,92,26.8,96.1,29.5,97.3,28.3], ], }, { name: "Indonesia", iso: "ID", rings: [ - [141,-2.6,141,-9.1,137.6,-8.4,137.9,-5.4,133,-4.1,132,-2.8,133.7,-2.2,130.5,-0.9,134,-0.8,135.5,-3.4,137.4,-1.7,141,-2.6], - [125,-8.9,123.5,-10.2,125,-8.9], - [117.9,4.1,119,0.9,116.1,-4,110.2,-2.9,109.7,2,110.5,0.8,113.8,1.2,115.9,4.3,117.9,4.1], - [129.4,-2.8,130.8,-3.9,127.9,-3.4,129.4,-2.8], - [127.9,2.2,128.1,-0.9,127.9,2.2], - [122.9,0.9,125.2,1.4,120,-0.5,123.3,-0.6,121.5,-1.9,123.2,-5.3,121,-2.6,119.8,-5.7,118.8,-2.8,119.8,0.2,122.9,0.9], - [120.3,-10.3,119,-9.6,120.3,-10.3], - [121.3,-8.5,122.9,-8.1,119.9,-8.8,121.3,-8.5], - [118.3,-8.4,116.7,-9,118.3,-8.4], - [108.5,-6.4,115.7,-8.4,105.4,-6.9,108.5,-6.4], - [104.4,-1.1,106.1,-3.1,105.8,-5.9,102.6,-4.2,95.3,5.5,97.5,5.2,104.4,-1.1], + [141,-2.6,141,-9.1,137.6,-8.4,137.9,-5.4,133,-4.1,132,-2.8,133.7,-2.2,130.5,-0.9,134,-0.8,135.5,-3.4,137.4,-1.7,141,-2.6], + [125,-8.9,123.5,-10.2,125,-8.9], + [117.9,4.1,119,0.9,116.1,-4,110.2,-2.9,109.7,2,110.5,0.8,113.8,1.2,115.9,4.3,117.9,4.1], + [129.4,-2.8,130.8,-3.9,127.9,-3.4,129.4,-2.8], + [127.9,2.2,128.1,-0.9,127.9,2.2], + [122.9,0.9,125.2,1.4,120,-0.5,123.3,-0.6,121.5,-1.9,123.2,-5.3,121,-2.6,119.8,-5.7,118.8,-2.8,119.8,0.2,122.9,0.9], + [120.3,-10.3,119,-9.6,120.3,-10.3], + [121.3,-8.5,122.9,-8.1,119.9,-8.8,121.3,-8.5], + [118.3,-8.4,116.7,-9,118.3,-8.4], + [108.5,-6.4,115.7,-8.4,105.4,-6.9,108.5,-6.4], + [104.4,-1.1,106.1,-3.1,105.8,-5.9,102.6,-4.2,95.3,5.5,97.5,5.2,104.4,-1.1], ], }, { name: "Iran", iso: "IR", rings: [ - [48.6,29.9,45.4,34,46.1,35.7,44.1,39.4,48.1,39.6,50.8,36.9,56.6,38.1,61.1,36.5,60.9,29.8,63.3,26.8,61.5,25.1,57.4,25.7,48.6,29.9], + [48.6,29.9,45.4,34,46.1,35.7,44.1,39.4,48.1,39.6,50.8,36.9,56.6,38.1,61.1,36.5,60.9,29.8,63.3,26.8,61.5,25.1,57.4,25.7,48.6,29.9], ], }, { name: "Iraq", iso: "IQ", rings: [ - [39.2,32.2,41.3,36.4,44.8,37.2,48.6,29.9,44.7,29.2,39.2,32.2], + [39.2,32.2,41.3,36.4,44.8,37.2,48.6,29.9,44.7,29.2,39.2,32.2], ], }, { name: "Ireland", iso: "IE", rings: [ - [-6.2,53.9,-6.8,52.3,-10,51.8,-7.6,55.1,-6.2,53.9], + [-6.2,53.9,-6.8,52.3,-10,51.8,-7.6,55.1,-6.2,53.9], ], }, { name: "Israel", iso: "IL", rings: [ - [35.7,32.7,34.9,29.5,34.3,31.2,35.7,32.7], + [35.7,32.7,34.9,29.5,34.3,31.2,35.7,32.7], ], }, { name: "Italy", iso: "IT", rings: [ - [10.4,46.9,13.8,46.5,12.6,44.1,18.3,39.8,16.9,40.4,15.7,37.9,15.4,40,10.2,43.9,7.4,43.7,6.8,46,10.4,46.9], - [14.8,38.1,15.1,36.6,12.4,37.6,14.8,38.1], - [8.7,40.9,9.8,40.5,8.8,38.9,8.7,40.9], + [10.4,46.9,13.8,46.5,12.6,44.1,18.3,39.8,16.9,40.4,15.7,37.9,15.4,40,10.2,43.9,7.4,43.7,6.8,46,10.4,46.9], + [14.8,38.1,15.1,36.6,12.4,37.6,14.8,38.1], + [8.7,40.9,9.8,40.5,8.8,38.9,8.7,40.9], ], }, { name: "Jamaica", iso: "JM", rings: [ - [-77.6,18.5,-76.2,17.9,-77.6,18.5], + [-77.6,18.5,-76.2,17.9,-77.6,18.5], ], }, { name: "Japan", iso: "JP", rings: [ - [141.9,39.2,140.3,35.1,135.8,33.5,135.1,34.6,131,33.9,132,33.1,130.2,31.4,129.4,33.3,139.4,38.2,140.3,41.2,141.9,39.2], - [144.6,44,145.5,43.3,140,41.6,142,45.6,144.6,44], - [132.4,33.5,134.8,33.8,132.4,33.5], + [141.9,39.2,140.3,35.1,135.8,33.5,135.1,34.6,131,33.9,132,33.1,130.2,31.4,129.4,33.3,139.4,38.2,140.3,41.2,141.9,39.2], + [144.6,44,145.5,43.3,140,41.6,142,45.6,144.6,44], + [132.4,33.5,134.8,33.8,132.4,33.5], ], }, { name: "Jordan", iso: "JO", rings: [ - [35.5,32.4,38.8,33.4,39.2,32.2,37,31.5,38,30.5,36.1,29.2,34.9,29.5,35.5,32.4], + [35.5,32.4,38.8,33.4,39.2,32.2,37,31.5,38,30.5,36.1,29.2,34.9,29.5,35.5,32.4], ], }, { name: "Kazakhstan", iso: "KZ", rings: [ - [87.4,49.2,80,44.9,80.3,42.3,74.2,43.3,68.6,40.7,64.9,43.7,62,43.5,58.5,45.6,55.9,45,56,41.3,52.5,41.8,50.3,44.6,53,45.3,53,46.9,49.1,46.4,46.5,48.4,50.8,51.7,61.3,50.8,60,52,61.4,54,69.1,55.4,73.4,53.5,76.9,54.5,80,50.9,87.4,49.2], + [87.4,49.2,80,44.9,80.3,42.3,74.2,43.3,68.6,40.7,64.9,43.7,62,43.5,58.5,45.6,55.9,45,56,41.3,52.5,41.8,50.3,44.6,53,45.3,53,46.9,49.1,46.4,46.5,48.4,50.8,51.7,61.3,50.8,60,52,61.4,54,69.1,55.4,73.4,53.5,76.9,54.5,80,50.9,87.4,49.2], ], }, { name: "Kenya", iso: "KE", rings: [ - [39.2,-4.7,33.9,-0.9,35.3,5.5,38.1,3.6,41.9,3.9,41.6,-1.7,39.2,-4.7], + [39.2,-4.7,33.9,-0.9,35.3,5.5,38.1,3.6,41.9,3.9,41.6,-1.7,39.2,-4.7], ], }, { name: "Kosovo", iso: "XK", rings: [ - [20.6,41.9,20.6,43.2,21.8,42.7,20.6,41.9], + [20.6,41.9,20.6,43.2,21.8,42.7,20.6,41.9], ], }, { name: "Kuwait", iso: "KW", rings: [ - [48,30,48.4,28.6,46.6,29.1,48,30], + [48,30,48.4,28.6,46.6,29.1,48,30], ], }, { name: "Kyrgyzstan", iso: "KG", rings: [ - [71,42.3,74.2,43.3,80.3,42.3,73.7,39.4,69.5,39.5,73.1,40.9,71,42.3], + [71,42.3,74.2,43.3,80.3,42.3,73.7,39.4,69.5,39.5,73.1,40.9,71,42.3], ], }, { name: "Laos", iso: "LA", rings: [ - [107.4,14.2,105.2,14.3,104,18.2,101.1,17.5,100.1,20.4,101.7,22.3,104.4,20.8,103.9,19.3,107.4,14.2], + [107.4,14.2,105.2,14.3,104,18.2,101.1,17.5,100.1,20.4,101.7,22.3,104.4,20.8,103.9,19.3,107.4,14.2], ], }, { name: "Latvia", iso: "LV", rings: [ - [27.3,57.5,28.2,56.2,26.5,55.6,21.1,56,22.5,57.8,27.3,57.5], + [27.3,57.5,28.2,56.2,26.5,55.6,21.1,56,22.5,57.8,27.3,57.5], ], }, { name: "Lebanon", iso: "LB", rings: [ - [35.8,33.3,36.4,34.6,35.8,33.3], + [35.8,33.3,36.4,34.6,35.8,33.3], ], }, { name: "Lesotho", iso: "LS", rings: [ - [29,-29,28.1,-30.5,27,-29.9,29,-29], + [29,-29,28.1,-30.5,27,-29.9,29,-29], ], }, { name: "Liberia", iso: "LR", rings: [ - [-8.4,7.7,-7.7,4.4,-11.4,6.8,-10.2,8.4,-8.4,7.7], + [-8.4,7.7,-7.7,4.4,-11.4,6.8,-10.2,8.4,-8.4,7.7], ], }, { name: "Libya", iso: "LY", rings: [ - [25,22,23.8,19.6,10.3,24.4,10,31.4,11.5,33.1,19.1,30.3,20.9,32.7,24.9,31.9,25,22], + [25,22,23.8,19.6,10.3,24.4,10,31.4,11.5,33.1,19.1,30.3,20.9,32.7,24.9,31.9,25,22], ], }, { name: "Lithuania", iso: "LT", rings: [ - [26.5,55.6,23.5,53.9,21.1,56,26.5,55.6], + [26.5,55.6,23.5,53.9,21.1,56,26.5,55.6], ], }, { name: "Madagascar", iso: "MG", rings: [ - [49.5,-12.5,50.4,-15.7,47.1,-24.9,45.4,-25.6,43.3,-22.8,44,-17.4,49.5,-12.5], + [49.5,-12.5,50.4,-15.7,47.1,-24.9,45.4,-25.6,43.3,-22.8,44,-17.4,49.5,-12.5], ], }, { name: "Malawi", iso: "MW", rings: [ - [32.8,-9.2,35.7,-14.6,35,-16.8,32.7,-13.7,32.8,-9.2], + [32.8,-9.2,35.7,-14.6,35,-16.8,32.7,-13.7,32.8,-9.2], ], }, { name: "Malaysia", iso: "MY", rings: [ - [100.1,6.5,103,5.5,104.2,1.3,101.4,2.8,100.1,6.5], - [117.9,4.1,115.9,4.3,114.6,1.4,109.8,1.3,115.3,4.3,116.7,6.9,119.2,5.4,117.9,4.1], + [100.1,6.5,103,5.5,104.2,1.3,101.4,2.8,100.1,6.5], + [117.9,4.1,115.9,4.3,114.6,1.4,109.8,1.3,115.3,4.3,116.7,6.9,119.2,5.4,117.9,4.1], ], }, { name: "Mali", iso: "ML", rings: [ - [-11.5,12.4,-11.7,15.4,-5.5,15.5,-6.5,25,4.3,19.2,3.6,15.6,-4,13.5,-5.4,10.4,-11.5,12.4], + [-11.5,12.4,-11.7,15.4,-5.5,15.5,-6.5,25,4.3,19.2,3.6,15.6,-4,13.5,-5.4,10.4,-11.5,12.4], ], }, { name: "Mauritania", iso: "MR", rings: [ - [-17.1,21,-12.9,21.3,-12,25.9,-8.7,25.9,-8.7,27.4,-4.9,25,-6.5,25,-5.5,15.5,-12.2,14.6,-14.6,16.6,-16.5,16.1,-17.1,21], + [-17.1,21,-12.9,21.3,-12,25.9,-8.7,25.9,-8.7,27.4,-4.9,25,-6.5,25,-5.5,15.5,-12.2,14.6,-14.6,16.6,-16.5,16.1,-17.1,21], ], }, { name: "Mexico", iso: "MX", rings: [ - [-117.1,32.5,-106.5,31.8,-103.9,29.3,-101.7,29.8,-97.1,25.9,-97.9,22.4,-95.9,18.8,-91.4,18.9,-90.3,21,-87.1,21.5,-87.8,18.3,-91,17.8,-90.5,16.1,-92.2,14.5,-103.5,18.3,-113.1,31.2,-114.9,31.4,-109.9,22.8,-115.1,27.7,-114.2,28.6,-117.1,32.5], + [-117.1,32.5,-106.5,31.8,-103.9,29.3,-101.7,29.8,-97.1,25.9,-97.9,22.4,-95.9,18.8,-91.4,18.9,-90.3,21,-87.1,21.5,-87.8,18.3,-91,17.8,-90.5,16.1,-92.2,14.5,-103.5,18.3,-113.1,31.2,-114.9,31.4,-109.9,22.8,-115.1,27.7,-114.2,28.6,-117.1,32.5], ], }, { name: "Moldova", iso: "MD", rings: [ - [26.6,48.2,30,46.4,28.2,45.5,26.6,48.2], + [26.6,48.2,30,46.4,28.2,45.5,26.6,48.2], ], }, { name: "Mongolia", iso: "MN", rings: [ - [87.8,49.3,92.2,50.8,97.3,49.7,98.9,52,108.5,49.3,116.7,49.9,115.7,47.7,119.8,47,105,41.6,96.3,42.7,90.9,45.3,91,46.9,87.8,49.3], + [87.8,49.3,92.2,50.8,97.3,49.7,98.9,52,108.5,49.3,116.7,49.9,115.7,47.7,119.8,47,105,41.6,96.3,42.7,90.9,45.3,91,46.9,87.8,49.3], ], }, { name: "Montenegro", iso: "ME", rings: [ - [20.1,42.6,18.5,42.5,20.1,42.6], + [20.1,42.6,18.5,42.5,20.1,42.6], ], }, { name: "Morocco", iso: "MA", rings: [ - [-2.2,35.2,-1.3,32.3,-8.7,28.8,-8.8,27.1,-11.4,26.9,-14.8,21.5,-17,21.4,-14.4,26.3,-9.6,29.9,-8.7,33.2,-5.9,35.8,-2.2,35.2], + [-2.2,35.2,-1.3,32.3,-8.7,28.8,-8.8,27.1,-11.4,26.9,-14.8,21.5,-17,21.4,-14.4,26.3,-9.6,29.9,-8.7,33.2,-5.9,35.8,-2.2,35.2], ], }, { name: "Mozambique", iso: "MZ", rings: [ - [34.6,-11.5,40.3,-10.3,40.8,-14.7,34.8,-19.8,35.5,-24.1,32.1,-26.7,31.2,-22.3,32.8,-16.7,30.2,-14.8,33.2,-14,35,-16.8,34.6,-11.5], + [34.6,-11.5,40.3,-10.3,40.8,-14.7,34.8,-19.8,35.5,-24.1,32.1,-26.7,31.2,-22.3,32.8,-16.7,30.2,-14.8,33.2,-14,35,-16.8,34.6,-11.5], ], }, { name: "Myanmar", iso: "MM", rings: [ - [100.1,20.4,97.4,18.4,99.6,11.9,98.6,9.9,97.2,16.9,94.2,16,92.3,21.5,97.3,28.3,98.7,27.5,97.6,23.9,101.2,21.8,100.1,20.4], + [100.1,20.4,97.4,18.4,99.6,11.9,98.6,9.9,97.2,16.9,94.2,16,92.3,21.5,97.3,28.3,98.7,27.5,97.6,23.9,101.2,21.8,100.1,20.4], ], }, { name: "N. Cyprus", iso: "-99", rings: [ - [32.7,35.1,34.6,35.7,32.7,35.1], + [32.7,35.1,34.6,35.7,32.7,35.1], ], }, { name: "Namibia", iso: "NA", rings: [ - [19.9,-24.8,19.9,-28.5,16.3,-28.6,11.7,-17.3,25.1,-17.6,20.9,-18.3,19.9,-24.8], + [19.9,-24.8,19.9,-28.5,16.3,-28.6,11.7,-17.3,25.1,-17.6,20.9,-18.3,19.9,-24.8], ], }, { name: "Nepal", iso: "NP", rings: [ - [88.1,27.9,87.2,26.4,80.1,28.8,81.5,30.4,88.1,27.9], + [88.1,27.9,87.2,26.4,80.1,28.8,81.5,30.4,88.1,27.9], ], }, { name: "Netherlands", iso: "NL", rings: [ - [6.9,53.5,6.2,50.8,3.3,51.3,6.9,53.5], + [6.9,53.5,6.2,50.8,3.3,51.3,6.9,53.5], ], }, { name: "New Caledonia", iso: "NC", rings: [ - [165.8,-21.1,167.1,-22.2,164,-20.1,165.8,-21.1], + [165.8,-21.1,167.1,-22.2,164,-20.1,165.8,-21.1], ], }, { name: "New Zealand", iso: "NZ", rings: [ - [176.9,-40.1,174.7,-41.3,174.7,-37.4,172.6,-34.5,176,-37.6,178.5,-37.7,176.9,-40.1], - [169.7,-43.6,172.8,-40.5,174.2,-41.3,170.6,-45.9,166.7,-46.2,169.7,-43.6], + [176.9,-40.1,174.7,-41.3,174.7,-37.4,172.6,-34.5,176,-37.6,178.5,-37.7,176.9,-40.1], + [169.7,-43.6,172.8,-40.5,174.2,-41.3,170.6,-45.9,166.7,-46.2,169.7,-43.6], ], }, { name: "Nicaragua", iso: "NI", rings: [ - [-83.7,10.9,-87.7,12.9,-83.1,15,-83.7,10.9], + [-83.7,10.9,-87.7,12.9,-83.1,15,-83.7,10.9], ], }, { name: "Niger", iso: "NE", rings: [ - [14.9,22.9,15.9,20.4,13.5,14.4,14.2,12.5,5.4,13.9,3.6,11.7,1,12.9,0.4,14.9,3.6,15.6,4.3,19.2,12,23.5,14.9,22.9], + [14.9,22.9,15.9,20.4,13.5,14.4,14.2,12.5,5.4,13.9,3.6,11.7,1,12.9,0.4,14.9,3.6,15.6,4.3,19.2,12,23.5,14.9,22.9], ], }, { name: "Nigeria", iso: "NG", rings: [ - [2.7,6.3,4.4,13.7,13.1,13.6,14.6,12.1,11.7,7,8.5,4.8,5.9,4.3,2.7,6.3], + [2.7,6.3,4.4,13.7,13.1,13.6,14.6,12.1,11.7,7,8.5,4.8,5.9,4.3,2.7,6.3], ], }, { name: "North Korea", iso: "KP", rings: [ - [130.6,42.4,127.5,39.8,128.2,38.4,124.7,38.1,125.1,40.6,130.6,42.4], + [130.6,42.4,127.5,39.8,128.2,38.4,124.7,38.1,125.1,40.6,130.6,42.4], ], }, { name: "North Macedonia", iso: "MK", rings: [ - [22.4,42.3,23,41.3,20.6,41.1,22.4,42.3], + [22.4,42.3,23,41.3,20.6,41.1,22.4,42.3], ], }, { name: "Norway", iso: "NO", rings: [ - [15.1,79.7,21.5,79,15.9,76.8,10.4,79.7,15.1,79.7], - [31.1,69.6,18,68.6,12.6,64.1,11,58.9,5.7,58.6,5,62,19.2,69.8,28.2,71.2,31.1,69.6], - [27.4,80.1,17.4,80.3,27.4,80.1], - [24.7,77.9,20.7,77.7,24.7,77.9], + [15.1,79.7,21.5,79,15.9,76.8,10.4,79.7,15.1,79.7], + [31.1,69.6,18,68.6,12.6,64.1,11,58.9,5.7,58.6,5,62,19.2,69.8,28.2,71.2,31.1,69.6], + [27.4,80.1,17.4,80.3,27.4,80.1], + [24.7,77.9,20.7,77.7,24.7,77.9], ], }, { name: "Oman", iso: "OM", rings: [ - [55.2,22.7,56.4,24.9,59.8,22.3,57.7,18.9,53.1,16.7,52,19,55,20,55.2,22.7], + [55.2,22.7,56.4,24.9,59.8,22.3,57.7,18.9,53.1,16.7,52,19,55,20,55.2,22.7], ], }, { name: "Pakistan", iso: "PK", rings: [ - [77.8,35.5,73.7,34.3,75.3,32.3,69.5,26.9,71,24.4,61.5,25.1,63.3,26.8,60.9,29.8,66.3,29.9,71.8,36.5,75.2,37.1,77.8,35.5], + [77.8,35.5,73.7,34.3,75.3,32.3,69.5,26.9,71,24.4,61.5,25.1,63.3,26.8,60.9,29.8,66.3,29.9,71.8,36.5,75.2,37.1,77.8,35.5], ], }, { name: "Panama", iso: "PA", rings: [ - [-77.4,8.7,-77.9,7.2,-79.1,9,-80.9,7.2,-82.9,9.5,-77.4,8.7], + [-77.4,8.7,-77.9,7.2,-79.1,9,-80.9,7.2,-82.9,9.5,-77.4,8.7], ], }, { name: "Papua New Guinea", iso: "PG", rings: [ - [141,-2.6,147.6,-6.1,147.2,-7.4,150.7,-10.6,144.7,-7.6,141,-9.1,141,-2.6], - [152.6,-3.7,152.8,-4.8,150.7,-2.7,152.6,-3.7], - [151.3,-5.8,148.3,-5.7,152.1,-4.1,151.3,-5.8], - [154.8,-5.3,155.9,-6.8,154.8,-5.3], + [141,-2.6,147.6,-6.1,147.2,-7.4,150.7,-10.6,144.7,-7.6,141,-9.1,141,-2.6], + [152.6,-3.7,152.8,-4.8,150.7,-2.7,152.6,-3.7], + [151.3,-5.8,148.3,-5.7,152.1,-4.1,151.3,-5.8], + [154.8,-5.3,155.9,-6.8,154.8,-5.3], ], }, { name: "Paraguay", iso: "PY", rings: [ - [-58.2,-20.2,-57.9,-22.1,-54.3,-24,-55.7,-27.4,-58.6,-27.1,-57.8,-25.2,-62.7,-22.2,-61.8,-19.6,-58.2,-20.2], + [-58.2,-20.2,-57.9,-22.1,-54.3,-24,-55.7,-27.4,-58.6,-27.1,-57.8,-25.2,-62.7,-22.2,-61.8,-19.6,-58.2,-20.2], ], }, { name: "Peru", iso: "PE", rings: [ - [-69.9,-4.3,-72.9,-5.3,-74,-7.5,-68.7,-12.6,-70.4,-18.3,-76,-14.6,-81.4,-4.7,-80.3,-3.4,-78.6,-4.5,-75.1,-0.1,-73.1,-2.3,-70,-2.7,-69.9,-4.3], + [-69.9,-4.3,-72.9,-5.3,-74,-7.5,-68.7,-12.6,-70.4,-18.3,-76,-14.6,-81.4,-4.7,-80.3,-3.4,-78.6,-4.5,-75.1,-0.1,-73.1,-2.3,-70,-2.7,-69.9,-4.3], ], }, { name: "Philippines", iso: "PH", rings: [ - [122.6,10,124.1,11.2,123,9,122.6,10], - [126.4,8.4,125.4,5.6,123.6,7.8,121.9,7.2,125.4,9.8,126.4,8.4], - [118.5,9.3,117.2,8.4,119.5,11.4,118.5,9.3], - [122.3,18.2,121.7,14.3,124.1,12.5,119.9,15.4,120.7,18.5,122.3,18.2], - [125.5,12.2,124.8,10.1,124.3,12.6,125.5,12.2], + [122.6,10,124.1,11.2,123,9,122.6,10], + [126.4,8.4,125.4,5.6,123.6,7.8,121.9,7.2,125.4,9.8,126.4,8.4], + [118.5,9.3,117.2,8.4,119.5,11.4,118.5,9.3], + [122.3,18.2,121.7,14.3,124.1,12.5,119.9,15.4,120.7,18.5,122.3,18.2], + [125.5,12.2,124.8,10.1,124.3,12.6,125.5,12.2], ], }, { name: "Poland", iso: "PL", rings: [ - [23.5,53.9,24,50.7,22.8,49,16.2,50.4,14.1,53,17.6,54.9,23.5,53.9], + [23.5,53.9,24,50.7,22.8,49,16.2,50.4,14.1,53,17.6,54.9,23.5,53.9], ], }, { name: "Portugal", iso: "PT", rings: [ - [-9,41.9,-6.4,41.4,-7.9,36.8,-9.5,38.7,-9,41.9], + [-9,41.9,-6.4,41.4,-7.9,36.8,-9.5,38.7,-9,41.9], ], }, { name: "Puerto Rico", iso: "PR", rings: [ - [-66.3,18.5,-67.2,17.9,-66.3,18.5], + [-66.3,18.5,-67.2,17.9,-66.3,18.5], ], }, { name: "Qatar", iso: "QA", rings: [ - [50.8,24.8,51.3,26.1,50.8,24.8], + [50.8,24.8,51.3,26.1,50.8,24.8], ], }, { name: "Romania", iso: "RO", rings: [ - [28.2,45.5,29.6,45.3,28.6,43.7,22.9,43.8,20.2,46.1,26.6,48.2,28.2,45.5], + [28.2,45.5,29.6,45.3,28.6,43.7,22.9,43.8,20.2,46.1,26.6,48.2,28.2,45.5], ], }, { name: "Russia", iso: "RU", rings: [ - [49.1,46.4,46.7,44.6,47.8,41.2,36.7,45.2,40.1,49.6,31.8,52.1,32.7,53.4,30.9,55.6,27.3,57.5,29.1,60,28.1,60.5,31.5,62.9,30,63.6,28.6,69.1,32.1,69.9,41.1,67.5,38.4,66,33.2,66.6,37,63.8,37.2,65.1,43.9,66.1,43.5,68.6,46.3,68.3,46.3,66.7,53.7,68.9,59.9,68.3,60.6,69.9,68.5,68.1,66.7,71,69.9,73,72.8,72.2,71.8,71.4,73.7,68.4,71.3,66.3,72.4,66.2,75.1,67.8,73.1,71.4,74.7,72.8,76.4,71.2,81.5,71.8,80.5,73.6,104.4,77.7,114.1,75.8,109.4,74.2,127,73.6,131.3,70.8,139.9,71.5,139.1,72.4,140.5,72.8,159,70.9,160.9,69.4,180,69,180,65,177.4,64.6,179.2,62.3,170.3,59.9,163.5,59.9,162,58.2,163.2,57.6,162.1,54.9,156.8,51,155.9,56.8,164.5,62.6,160.1,60.5,156.7,61.4,154.2,59.8,155,59.1,142.2,59,135.1,54.7,141.3,53.1,140.1,48.4,134.9,43.4,130.8,42.2,131,45,133.1,45.1,135,48.5,131,47.8,123.6,53.5,120.2,52.8,117.9,49.5,108.5,49.3,98.9,52,97.3,49.7,92.2,50.8,87.4,49.2,80,50.9,76.9,54.5,73.4,53.5,69.1,55.4,61.4,54,60,52,61.3,50.8,50.8,51.7,47.5,50.5,46.5,48.4,49.1,46.4], - [93.8,81,100.2,79.8,97.8,78.8,91.2,80.3,93.8,81], - [102.8,79.3,105.4,78.7,99.4,77.9,102.8,79.3], - [138.8,76.1,145.1,75.6,137,75.3,138.8,76.1], - [148.2,75.3,150.7,75.1,146.1,75.2,148.2,75.3], - [139.9,73.4,143.6,73.2,139.9,73.4], - [44.8,80.6,51.5,80.7,44.8,80.6], - [22.7,54.3,19.7,54.4,22.7,54.3], - [53.5,73.7,61.2,76.3,68.9,76.5,58.5,74.3,55.4,72.4,57.5,70.7,51.6,71.5,53.5,73.7], - [142.9,53.7,144.7,49,143.2,49.3,143.5,46.1,142.1,46,141.7,53.3,142.9,53.7], - [-174.9,67.2,-169.9,66,-173,64.3,-178.7,66.1,-180,65,-180,69,-174.9,67.2], - [-178.7,70.9,-180,71.5,-177.6,71.3,-178.7,70.9], - [33.4,46,36.5,45.5,33.9,44.4,32.5,45.3,33.4,46], + [49.1,46.4,46.7,44.6,47.8,41.2,36.7,45.2,40.1,49.6,31.8,52.1,32.7,53.4,30.9,55.6,27.3,57.5,29.1,60,28.1,60.5,31.5,62.9,30,63.6,28.6,69.1,32.1,69.9,41.1,67.5,38.4,66,33.2,66.6,37,63.8,37.2,65.1,43.9,66.1,43.5,68.6,46.3,68.3,46.3,66.7,53.7,68.9,59.9,68.3,60.6,69.9,68.5,68.1,66.7,71,69.9,73,72.8,72.2,71.8,71.4,73.7,68.4,71.3,66.3,72.4,66.2,75.1,67.8,73.1,71.4,74.7,72.8,76.4,71.2,81.5,71.8,80.5,73.6,104.4,77.7,114.1,75.8,109.4,74.2,127,73.6,131.3,70.8,139.9,71.5,139.1,72.4,140.5,72.8,159,70.9,160.9,69.4,180,69,180,65,177.4,64.6,179.2,62.3,170.3,59.9,163.5,59.9,162,58.2,163.2,57.6,162.1,54.9,156.8,51,155.9,56.8,164.5,62.6,160.1,60.5,156.7,61.4,154.2,59.8,155,59.1,142.2,59,135.1,54.7,141.3,53.1,140.1,48.4,134.9,43.4,130.8,42.2,131,45,133.1,45.1,135,48.5,131,47.8,123.6,53.5,120.2,52.8,117.9,49.5,108.5,49.3,98.9,52,97.3,49.7,92.2,50.8,87.4,49.2,80,50.9,76.9,54.5,73.4,53.5,69.1,55.4,61.4,54,60,52,61.3,50.8,50.8,51.7,47.5,50.5,46.5,48.4,49.1,46.4], + [93.8,81,100.2,79.8,97.8,78.8,91.2,80.3,93.8,81], + [102.8,79.3,105.4,78.7,99.4,77.9,102.8,79.3], + [138.8,76.1,145.1,75.6,137,75.3,138.8,76.1], + [148.2,75.3,150.7,75.1,146.1,75.2,148.2,75.3], + [139.9,73.4,143.6,73.2,139.9,73.4], + [44.8,80.6,51.5,80.7,44.8,80.6], + [22.7,54.3,19.7,54.4,22.7,54.3], + [53.5,73.7,61.2,76.3,68.9,76.5,58.5,74.3,55.4,72.4,57.5,70.7,51.6,71.5,53.5,73.7], + [142.9,53.7,144.7,49,143.2,49.3,143.5,46.1,142.1,46,141.7,53.3,142.9,53.7], + [-174.9,67.2,-169.9,66,-173,64.3,-178.7,66.1,-180,65,-180,69,-174.9,67.2], + [-178.7,70.9,-180,71.5,-177.6,71.3,-178.7,70.9], + [33.4,46,36.5,45.5,33.9,44.4,32.5,45.3,33.4,46], ], }, { name: "Rwanda", iso: "RW", rings: [ - [30.4,-1.1,29,-2.8,30.4,-1.1], + [30.4,-1.1,29,-2.8,30.4,-1.1], ], }, { name: "S. Sudan", iso: "SS", rings: [ - [30.8,3.5,23.9,8.6,25.8,10.4,31.4,9.8,33.2,12.2,33,7.8,35.3,5.5,30.8,3.5], + [30.8,3.5,23.9,8.6,25.8,10.4,31.4,9.8,33.2,12.2,33,7.8,35.3,5.5,30.8,3.5], ], }, { name: "Saudi Arabia", iso: "SA", rings: [ - [35,29.4,39.2,32.2,47.5,29,52,23,55.2,22.7,55,20,47,16.9,43.4,17.6,42.8,16.3,35,29.4], + [35,29.4,39.2,32.2,47.5,29,52,23,55.2,22.7,55,20,47,16.9,43.4,17.6,42.8,16.3,35,29.4], ], }, { name: "Senegal", iso: "SN", rings: [ - [-16.7,13.6,-17.6,14.7,-14.6,16.6,-11.5,12.4,-16.7,12.4,-13.8,13.5,-16.7,13.6], + [-16.7,13.6,-17.6,14.7,-14.6,16.6,-11.5,12.4,-16.7,12.4,-13.8,13.5,-16.7,13.6], ], }, { name: "Serbia", iso: "RS", rings: [ - [18.8,45.9,22.7,44.6,22.5,42.5,19.2,43.5,18.8,45.9], + [18.8,45.9,22.7,44.6,22.5,42.5,19.2,43.5,18.8,45.9], ], }, { name: "Sierra Leone", iso: "SL", rings: [ - [-13.2,8.9,-11.1,10,-10.2,8.4,-11.4,6.8,-13.2,8.9], + [-13.2,8.9,-11.1,10,-10.2,8.4,-11.4,6.8,-13.2,8.9], ], }, { name: "Slovakia", iso: "SK", rings: [ - [22.6,49.1,16.9,48.5,22.6,49.1], + [22.6,49.1,16.9,48.5,22.6,49.1], ], }, { name: "Slovenia", iso: "SI", rings: [ - [13.8,46.5,16.6,46.5,15.3,45.5,13.8,46.5], + [13.8,46.5,16.6,46.5,15.3,45.5,13.8,46.5], ], }, { name: "Solomon Is.", iso: "SB", rings: [ - [159.6,-8,158.2,-7.4,159.6,-8], + [159.6,-8,158.2,-7.4,159.6,-8], ], }, { name: "Somalia", iso: "SO", rings: [ - [41.6,-1.7,41,2.8,45,5,48.9,9.5,48.9,11.4,51.1,12,48.6,5.3,41.6,-1.7], + [41.6,-1.7,41,2.8,45,5,48.9,9.5,48.9,11.4,51.1,12,48.6,5.3,41.6,-1.7], ], }, { name: "Somaliland", iso: "-99", rings: [ - [48.9,11.4,47.8,8,42.6,10.6,48.9,11.4], + [48.9,11.4,47.8,8,42.6,10.6,48.9,11.4], ], }, { name: "South Africa", iso: "ZA", rings: [ - [16.3,-28.6,19.9,-28.5,19.9,-24.8,21.6,-26.7,25.7,-25.5,29.4,-22.1,31.2,-22.3,31.9,-24.4,30.7,-26.7,32.8,-26.7,28.2,-32.8,20.1,-34.8,18.4,-34.1,16.3,-28.6], + [16.3,-28.6,19.9,-28.5,19.9,-24.8,21.6,-26.7,25.7,-25.5,29.4,-22.1,31.2,-22.3,31.9,-24.4,30.7,-26.7,32.8,-26.7,28.2,-32.8,20.1,-34.8,18.4,-34.1,16.3,-28.6], ], }, { name: "South Korea", iso: "KR", rings: [ - [126.2,37.7,128.3,38.6,129.1,35.1,126.5,34.4,126.2,37.7], + [126.2,37.7,128.3,38.6,129.1,35.1,126.5,34.4,126.2,37.7], ], }, { name: "Spain", iso: "ES", rings: [ - [-7.5,37.1,-6.4,41.4,-9.4,43,3,42.5,-2.1,36.7,-7.5,37.1], + [-7.5,37.1,-6.4,41.4,-9.4,43,3,42.5,-2.1,36.7,-7.5,37.1], ], }, { name: "Sri Lanka", iso: "LK", rings: [ - [81.8,7.5,80.3,6,80.1,9.8,81.8,7.5], + [81.8,7.5,80.3,6,80.1,9.8,81.8,7.5], ], }, { name: "Sudan", iso: "SD", rings: [ - [24.6,8.2,21.9,12.6,25,22,36.9,22,38.4,18,34,8.7,32.7,12.2,31.4,9.8,25.1,10.3,24.6,8.2], + [24.6,8.2,21.9,12.6,25,22,36.9,22,38.4,18,34,8.7,32.7,12.2,31.4,9.8,25.1,10.3,24.6,8.2], ], }, { name: "Suriname", iso: "SR", rings: [ - [-54.5,2.3,-56.5,1.9,-57.6,3.3,-57.1,6,-54,5.8,-54.5,2.3], + [-54.5,2.3,-56.5,1.9,-57.6,3.3,-57.1,6,-54,5.8,-54.5,2.3], ], }, { name: "Sweden", iso: "SE", rings: [ - [11,58.9,12.6,61.3,11.9,63.1,16.8,68,20.6,69.1,23.5,67.9,23.9,66,17.8,62.7,17.1,61.3,18.8,60.1,15.9,56.1,12.9,55.4,11,58.9], + [11,58.9,12.6,61.3,11.9,63.1,16.8,68,20.6,69.1,23.5,67.9,23.9,66,17.8,62.7,17.1,61.3,18.8,60.1,15.9,56.1,12.9,55.4,11,58.9], ], }, { name: "Switzerland", iso: "CH", rings: [ - [9.6,47.5,10.4,46.5,6,46.3,9.6,47.5], + [9.6,47.5,10.4,46.5,6,46.3,9.6,47.5], ], }, { name: "Syria", iso: "SY", rings: [ - [35.7,32.7,36.7,36.8,42.3,37.2,41,34.4,35.7,32.7], + [35.7,32.7,36.7,36.8,42.3,37.2,41,34.4,35.7,32.7], ], }, { name: "Taiwan", iso: "TW", rings: [ - [121.8,24.4,120.7,22,120.1,23.6,121.8,24.4], + [121.8,24.4,120.7,22,120.1,23.6,121.8,24.4], ], }, { name: "Tajikistan", iso: "TJ", rings: [ - [67.8,37.1,67.7,39.6,70.7,41,69.5,39.5,73.7,39.4,75,37.4,71.8,36.7,70.8,38.5,67.8,37.1], + [67.8,37.1,67.7,39.6,70.7,41,69.5,39.5,73.7,39.4,75,37.4,71.8,36.7,70.8,38.5,67.8,37.1], ], }, { name: "Tanzania", iso: "TZ", rings: [ - [33.9,-0.9,39.2,-4.7,39.5,-10.9,34.6,-11.5,29.6,-6.5,30.4,-1.1,33.9,-0.9], + [33.9,-0.9,39.2,-4.7,39.5,-10.9,34.6,-11.5,29.6,-6.5,30.4,-1.1,33.9,-0.9], ], }, { name: "Thailand", iso: "TH", rings: [ - [105.2,14.3,103,14.2,102.6,12.2,100.1,13.4,99.2,9.2,102.1,6.2,101.2,5.7,98.2,8.4,99.6,11.9,97.4,18.4,100.1,20.4,101.1,17.5,104.7,17.4,105.2,14.3], + [105.2,14.3,103,14.2,102.6,12.2,100.1,13.4,99.2,9.2,102.1,6.2,101.2,5.7,98.2,8.4,99.6,11.9,97.4,18.4,100.1,20.4,101.1,17.5,104.7,17.4,105.2,14.3], ], }, { name: "Timor-Leste", iso: "TL", rings: [ - [125,-8.9,127.3,-8.4,125,-8.9], + [125,-8.9,127.3,-8.4,125,-8.9], ], }, { name: "Togo", iso: "TG", rings: [ - [0.9,11,1.1,5.9,0.9,11], + [0.9,11,1.1,5.9,0.9,11], ], }, { name: "Tunisia", iso: "TN", rings: [ - [9.5,30.3,7.5,34.1,9.5,37.3,11,37.1,10.1,34.3,11.5,33.1,9.5,30.3], + [9.5,30.3,7.5,34.1,9.5,37.3,11,37.1,10.1,34.3,11.5,33.1,9.5,30.3], ], }, { name: "Turkey", iso: "TR", rings: [ - [44.8,37.2,29.7,36.1,27.6,36.7,26.2,39.5,33.5,42,42.6,41.6,44.8,39.7,44.8,37.2], - [26.1,41.8,29,41.3,26.4,40.2,26.1,41.8], + [44.8,37.2,29.7,36.1,27.6,36.7,26.2,39.5,33.5,42,42.6,41.6,44.8,39.7,44.8,37.2], + [26.1,41.8,29,41.3,26.4,40.2,26.1,41.8], ], }, { name: "Turkmenistan", iso: "TM", rings: [ - [52.5,41.8,57.1,41.3,58.6,42.8,66.5,37.4,62.2,35.3,57.3,38,53.9,37.2,52.7,40,54.7,41,52.5,41.8], + [52.5,41.8,57.1,41.3,58.6,42.8,66.5,37.4,62.2,35.3,57.3,38,53.9,37.2,52.7,40,54.7,41,52.5,41.8], ], }, { name: "Uganda", iso: "UG", rings: [ - [33.9,-0.9,29.6,-1.3,31.2,3.8,34.5,3.6,33.9,-0.9], + [33.9,-0.9,29.6,-1.3,31.2,3.8,34.5,3.6,33.9,-0.9], ], }, { name: "Ukraine", iso: "UA", rings: [ - [31.8,52.1,40.1,49.6,39.7,47.9,35,45.7,31.7,46.7,28.7,45.3,30,46.4,28.7,48.1,22.1,48.4,23.5,51.6,31.8,52.1], + [31.8,52.1,40.1,49.6,39.7,47.9,35,45.7,31.7,46.7,28.7,45.3,30,46.4,28.7,48.1,22.1,48.4,23.5,51.6,31.8,52.1], ], }, { name: "United Arab Emirates", iso: "AE", rings: [ - [51.6,24.2,56.3,25.7,55,22.5,51.6,24.2], + [51.6,24.2,56.3,25.7,55,22.5,51.6,24.2], ], }, { name: "United Kingdom", iso: "GB", rings: [ - [-6.2,53.9,-7.6,55.1,-6.2,53.9], - [-3.1,53.4,-6.1,56.8,-5,58.6,-2,57.7,-3.1,56,1.7,52.7,1.4,51.3,-5.8,50.2,-3.4,51.4,-5.3,52,-4.6,53.5,-3.1,53.4], + [-6.2,53.9,-7.6,55.1,-6.2,53.9], + [-3.1,53.4,-6.1,56.8,-5,58.6,-2,57.7,-3.1,56,1.7,52.7,1.4,51.3,-5.8,50.2,-3.4,51.4,-5.3,52,-4.6,53.5,-3.1,53.4], ], }, { name: "United States of America", iso: "US", rings: [ - [-122.8,49,-88.4,48.3,-82.6,45.3,-82.7,41.7,-71.5,45,-69.2,47.4,-67,44.8,-70.1,43.7,-70,41.6,-75.5,39.5,-75.9,37.2,-76.3,39.2,-77,38.2,-75.7,35.6,-81.3,31.4,-80.4,25.2,-83.7,29.9,-86.4,30.4,-94.7,29.5,-97.5,25.8,-101,29.4,-103.9,29.3,-106.5,31.8,-117.1,32.5,-120.6,34.6,-124.4,40.3,-124.7,48.2,-122.6,47.1,-122.8,49], - [-166.5,60.4,-165.6,59.9,-167.5,60.2,-166.5,60.4], - [-153.2,58,-152.1,57.6,-154.5,57,-153.2,58], - [-141,69.7,-141,60.3,-137.5,58.9,-135.5,59.8,-130,55.9,-130.5,54.8,-134.1,58.1,-139.9,59.5,-147.1,60.9,-151.7,59.2,-150.6,61.3,-158.4,56,-164.9,54.6,-157,58.9,-162,58.7,-165.3,60.5,-165.7,62.1,-160.8,64.8,-168.1,65.7,-161.7,66.1,-166.2,68.9,-156.6,71.4,-141,69.7], - [-171.7,63.8,-168.7,63.3,-171.7,63.8], + [-122.8,49,-88.4,48.3,-82.6,45.3,-82.7,41.7,-71.5,45,-69.2,47.4,-67,44.8,-70.1,43.7,-70,41.6,-75.5,39.5,-75.9,37.2,-76.3,39.2,-77,38.2,-75.7,35.6,-81.3,31.4,-80.4,25.2,-83.7,29.9,-86.4,30.4,-94.7,29.5,-97.5,25.8,-101,29.4,-103.9,29.3,-106.5,31.8,-117.1,32.5,-120.6,34.6,-124.4,40.3,-124.7,48.2,-122.6,47.1,-122.8,49], + [-166.5,60.4,-165.6,59.9,-167.5,60.2,-166.5,60.4], + [-153.2,58,-152.1,57.6,-154.5,57,-153.2,58], + [-141,69.7,-141,60.3,-137.5,58.9,-135.5,59.8,-130,55.9,-130.5,54.8,-134.1,58.1,-139.9,59.5,-147.1,60.9,-151.7,59.2,-150.6,61.3,-158.4,56,-164.9,54.6,-157,58.9,-162,58.7,-165.3,60.5,-165.7,62.1,-160.8,64.8,-168.1,65.7,-161.7,66.1,-166.2,68.9,-156.6,71.4,-141,69.7], + [-171.7,63.8,-168.7,63.3,-171.7,63.8], ], }, { name: "Uruguay", iso: "UY", rings: [ - [-57.6,-30.2,-53.8,-32,-53.8,-34.4,-58.4,-33.9,-57.6,-30.2], + [-57.6,-30.2,-53.8,-32,-53.8,-34.4,-58.4,-33.9,-57.6,-30.2], ], }, { name: "Uzbekistan", iso: "UZ", rings: [ - [56,41.3,55.9,45,58.5,45.6,62,43.5,64.9,43.7,68.3,40.7,71,42.3,73.1,40.9,67.7,39.6,67.8,37.1,58.6,42.8,56,41.3], + [56,41.3,55.9,45,58.5,45.6,62,43.5,64.9,43.7,68.3,40.7,71,42.3,73.1,40.9,67.7,39.6,67.8,37.1,58.6,42.8,56,41.3], ], }, { name: "Venezuela", iso: "VE", rings: [ - [-60.7,5.2,-64.8,4.1,-63.4,2.2,-66.3,0.7,-67.8,2.8,-67.3,6.1,-72,7,-72.9,10.5,-71.3,11.8,-71.3,9.1,-69.9,12.2,-68.2,10.6,-61.9,10.7,-59.8,8.4,-60.7,5.2], + [-60.7,5.2,-64.8,4.1,-63.4,2.2,-66.3,0.7,-67.8,2.8,-67.3,6.1,-72,7,-72.9,10.5,-71.3,11.8,-71.3,9.1,-69.9,12.2,-68.2,10.6,-61.9,10.7,-59.8,8.4,-60.7,5.2], ], }, { name: "Vietnam", iso: "VN", rings: [ - [104.3,10.5,107.5,12.3,107.6,15.2,102.2,22.5,105.3,23.4,108.1,21.6,105.7,19.1,108.9,15.3,109.2,11.7,105.2,8.6,104.3,10.5], + [104.3,10.5,107.5,12.3,107.6,15.2,102.2,22.5,105.3,23.4,108.1,21.6,105.7,19.1,108.9,15.3,109.2,11.7,105.2,8.6,104.3,10.5], ], }, { name: "W. Sahara", iso: "EH", rings: [ - [-8.7,27.7,-8.7,25.9,-12,25.9,-12.9,21.3,-17.1,21,-14.8,21.5,-11.4,26.9,-8.7,27.7], + [-8.7,27.7,-8.7,25.9,-12,25.9,-12.9,21.3,-17.1,21,-14.8,21.5,-11.4,26.9,-8.7,27.7], ], }, { name: "Yemen", iso: "YE", rings: [ - [52,19,52.2,15.6,43.5,12.6,43.4,17.6,47,16.9,52,19], + [52,19,52.2,15.6,43.5,12.6,43.4,17.6,47,16.9,52,19], ], }, { name: "Zambia", iso: "ZM", rings: [ - [30.7,-8.3,33.2,-9.7,33.2,-14,27,-17.9,23.2,-17.5,21.9,-12.9,24,-12.9,23.9,-10.9,29.7,-13.3,28.4,-9.2,30.7,-8.3], + [30.7,-8.3,33.2,-9.7,33.2,-14,27,-17.9,23.2,-17.5,21.9,-12.9,24,-12.9,23.9,-10.9,29.7,-13.3,28.4,-9.2,30.7,-8.3], ], }, { name: "Zimbabwe", iso: "ZW", rings: [ - [31.2,-22.3,28,-21.5,25.3,-17.7,30.3,-15.5,32.8,-16.7,31.2,-22.3], + [31.2,-22.3,28,-21.5,25.3,-17.7,30.3,-15.5,32.8,-16.7,31.2,-22.3], ], }, ]; diff --git a/ports/conformance/fixtures/widgets.json b/ports/conformance/fixtures/widgets.json index e0b7877..be6dbd2 100644 --- a/ports/conformance/fixtures/widgets.json +++ b/ports/conformance/fixtures/widgets.json @@ -5264,6 +5264,5706 @@ ] } }, + { + "name": "world", + "width": 60, + "height": 16, + "result": { + "width": 60, + "height": 16, + "chars": [ + [ + 14, + 32 + ], + [ + 1, + 10368 + ], + [ + 3, + 10432 + ], + [ + 1, + 10368 + ], + [ + 5, + 10432 + ], + [ + 1, + 10404 + ], + [ + 1, + 10468 + ], + [ + 2, + 10432 + ], + [ + 4, + 32 + ], + [ + 1, + 10368 + ], + [ + 2, + 10432 + ], + [ + 2, + 32 + ], + [ + 2, + 10432 + ], + [ + 6, + 32 + ], + [ + 1, + 10432 + ], + [ + 14, + 32 + ], + [ + 1, + 10436 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 6, + 10432 + ], + [ + 1, + 10422 + ], + [ + 1, + 10404 + ], + [ + 1, + 10340 + ], + [ + 1, + 10276 + ], + [ + 1, + 10368 + ], + [ + 1, + 10304 + ], + [ + 1, + 10267 + ], + [ + 1, + 10258 + ], + [ + 1, + 10248 + ], + [ + 1, + 10267 + ], + [ + 1, + 10259 + ], + [ + 1, + 10402 + ], + [ + 1, + 10436 + ], + [ + 3, + 32 + ], + [ + 1, + 10400 + ], + [ + 1, + 10334 + ], + [ + 4, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10368 + ], + [ + 1, + 10304 + ], + [ + 1, + 10368 + ], + [ + 1, + 10432 + ], + [ + 1, + 32 + ], + [ + 1, + 10304 + ], + [ + 1, + 10272 + ], + [ + 1, + 10388 + ], + [ + 1, + 10258 + ], + [ + 1, + 10242 + ], + [ + 1, + 10436 + ], + [ + 1, + 10276 + ], + [ + 2, + 10258 + ], + [ + 1, + 10249 + ], + [ + 1, + 10241 + ], + [ + 1, + 10294 + ], + [ + 2, + 10276 + ], + [ + 1, + 10432 + ], + [ + 1, + 10256 + ], + [ + 1, + 10276 + ], + [ + 1, + 10258 + ], + [ + 5, + 10432 + ], + [ + 1, + 10251 + ], + [ + 1, + 10256 + ], + [ + 1, + 10242 + ], + [ + 1, + 10435 + ], + [ + 1, + 10432 + ], + [ + 1, + 10340 + ], + [ + 1, + 10428 + ], + [ + 1, + 10432 + ], + [ + 1, + 10304 + ], + [ + 1, + 32 + ], + [ + 2, + 10249 + ], + [ + 2, + 10241 + ], + [ + 1, + 10337 + ], + [ + 1, + 10256 + ], + [ + 1, + 10258 + ], + [ + 1, + 10249 + ], + [ + 1, + 10300 + ], + [ + 1, + 10255 + ], + [ + 1, + 32 + ], + [ + 1, + 10259 + ], + [ + 1, + 10276 + ], + [ + 1, + 10258 + ], + [ + 1, + 10249 + ], + [ + 1, + 10256 + ], + [ + 1, + 10266 + ], + [ + 1, + 10267 + ], + [ + 1, + 32 + ], + [ + 1, + 10432 + ], + [ + 1, + 10400 + ], + [ + 1, + 10384 + ], + [ + 1, + 10413 + ], + [ + 1, + 10254 + ], + [ + 1, + 10424 + ], + [ + 1, + 10267 + ], + [ + 1, + 10251 + ], + [ + 1, + 10249 + ], + [ + 1, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10248 + ], + [ + 1, + 10249 + ], + [ + 1, + 10241 + ], + [ + 10, + 32 + ], + [ + 2, + 10432 + ], + [ + 1, + 10464 + ], + [ + 1, + 10468 + ], + [ + 1, + 10436 + ], + [ + 1, + 10432 + ], + [ + 1, + 10302 + ], + [ + 2, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10249 + ], + [ + 1, + 10241 + ], + [ + 3, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10464 + ], + [ + 2, + 10276 + ], + [ + 1, + 10372 + ], + [ + 2, + 10432 + ], + [ + 1, + 10304 + ], + [ + 1, + 10297 + ], + [ + 1, + 10243 + ], + [ + 1, + 10368 + ], + [ + 1, + 10432 + ], + [ + 1, + 10464 + ], + [ + 1, + 10308 + ], + [ + 6, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10295 + ], + [ + 1, + 10246 + ], + [ + 1, + 10432 + ], + [ + 1, + 10249 + ], + [ + 1, + 10416 + ], + [ + 1, + 10450 + ], + [ + 1, + 10322 + ], + [ + 1, + 10404 + ], + [ + 1, + 10400 + ], + [ + 1, + 10322 + ], + [ + 1, + 10290 + ], + [ + 1, + 10262 + ], + [ + 1, + 10249 + ], + [ + 1, + 10266 + ], + [ + 3, + 10276 + ], + [ + 1, + 10262 + ], + [ + 2, + 10276 + ], + [ + 1, + 10260 + ], + [ + 1, + 10274 + ], + [ + 1, + 10372 + ], + [ + 1, + 10329 + ], + [ + 1, + 10486 + ], + [ + 1, + 32 + ], + [ + 1, + 10296 + ], + [ + 1, + 10250 + ], + [ + 12, + 32 + ], + [ + 1, + 10275 + ], + [ + 1, + 10304 + ], + [ + 4, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10259 + ], + [ + 1, + 10349 + ], + [ + 1, + 10251 + ], + [ + 1, + 10249 + ], + [ + 8, + 32 + ], + [ + 1, + 10425 + ], + [ + 1, + 10482 + ], + [ + 1, + 10242 + ], + [ + 1, + 10400 + ], + [ + 1, + 10248 + ], + [ + 1, + 10265 + ], + [ + 2, + 10248 + ], + [ + 1, + 10241 + ], + [ + 1, + 10246 + ], + [ + 1, + 10288 + ], + [ + 1, + 10267 + ], + [ + 1, + 10285 + ], + [ + 1, + 10358 + ], + [ + 2, + 10242 + ], + [ + 1, + 32 + ], + [ + 1, + 10257 + ], + [ + 2, + 10258 + ], + [ + 1, + 10249 + ], + [ + 1, + 10404 + ], + [ + 1, + 10400 + ], + [ + 1, + 10308 + ], + [ + 1, + 10249 + ], + [ + 1, + 10267 + ], + [ + 1, + 10241 + ], + [ + 15, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10249 + ], + [ + 1, + 10257 + ], + [ + 1, + 10274 + ], + [ + 2, + 10258 + ], + [ + 1, + 10430 + ], + [ + 1, + 10304 + ], + [ + 9, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10340 + ], + [ + 1, + 10250 + ], + [ + 1, + 32 + ], + [ + 1, + 10265 + ], + [ + 1, + 10304 + ], + [ + 1, + 10251 + ], + [ + 1, + 10311 + ], + [ + 1, + 10248 + ], + [ + 1, + 10241 + ], + [ + 1, + 10258 + ], + [ + 1, + 10368 + ], + [ + 1, + 10304 + ], + [ + 1, + 10454 + ], + [ + 1, + 10453 + ], + [ + 1, + 10241 + ], + [ + 1, + 10258 + ], + [ + 1, + 10276 + ], + [ + 1, + 10368 + ], + [ + 1, + 10308 + ], + [ + 1, + 10304 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10304 + ], + [ + 1, + 10248 + ], + [ + 1, + 10241 + ], + [ + 18, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10257 + ], + [ + 1, + 10266 + ], + [ + 1, + 10294 + ], + [ + 1, + 10368 + ], + [ + 1, + 10308 + ], + [ + 1, + 10242 + ], + [ + 1, + 10304 + ], + [ + 1, + 10242 + ], + [ + 7, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10304 + ], + [ + 1, + 10246 + ], + [ + 1, + 10368 + ], + [ + 1, + 10432 + ], + [ + 1, + 10304 + ], + [ + 1, + 10400 + ], + [ + 1, + 10251 + ], + [ + 1, + 10441 + ], + [ + 1, + 10247 + ], + [ + 1, + 10470 + ], + [ + 1, + 10294 + ], + [ + 1, + 10241 + ], + [ + 1, + 32 + ], + [ + 1, + 10264 + ], + [ + 1, + 10308 + ], + [ + 1, + 10324 + ], + [ + 1, + 10250 + ], + [ + 1, + 10275 + ], + [ + 1, + 10248 + ], + [ + 1, + 10410 + ], + [ + 1, + 10304 + ], + [ + 1, + 10248 + ], + [ + 1, + 10368 + ], + [ + 24, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10265 + ], + [ + 1, + 10248 + ], + [ + 1, + 10427 + ], + [ + 1, + 10477 + ], + [ + 2, + 10242 + ], + [ + 5, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10243 + ], + [ + 1, + 10241 + ], + [ + 1, + 10243 + ], + [ + 1, + 10268 + ], + [ + 1, + 10304 + ], + [ + 1, + 10248 + ], + [ + 1, + 10251 + ], + [ + 1, + 10468 + ], + [ + 1, + 10368 + ], + [ + 1, + 10265 + ], + [ + 1, + 10241 + ], + [ + 3, + 32 + ], + [ + 1, + 10264 + ], + [ + 1, + 10259 + ], + [ + 1, + 32 + ], + [ + 1, + 10272 + ], + [ + 1, + 10291 + ], + [ + 1, + 10249 + ], + [ + 1, + 10464 + ], + [ + 1, + 10250 + ], + [ + 1, + 10248 + ], + [ + 1, + 10304 + ], + [ + 24, + 32 + ], + [ + 1, + 10416 + ], + [ + 1, + 10298 + ], + [ + 1, + 10307 + ], + [ + 1, + 10304 + ], + [ + 1, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10249 + ], + [ + 1, + 10386 + ], + [ + 1, + 10246 + ], + [ + 6, + 32 + ], + [ + 1, + 10257 + ], + [ + 1, + 10241 + ], + [ + 2, + 10368 + ], + [ + 1, + 10308 + ], + [ + 1, + 10402 + ], + [ + 9, + 32 + ], + [ + 1, + 10257 + ], + [ + 1, + 10261 + ], + [ + 1, + 10276 + ], + [ + 1, + 10244 + ], + [ + 1, + 10368 + ], + [ + 1, + 10244 + ], + [ + 1, + 10299 + ], + [ + 1, + 10359 + ], + [ + 1, + 10256 + ], + [ + 1, + 10288 + ], + [ + 1, + 10244 + ], + [ + 20, + 32 + ], + [ + 1, + 10259 + ], + [ + 1, + 10254 + ], + [ + 1, + 10400 + ], + [ + 1, + 10304 + ], + [ + 2, + 32 + ], + [ + 1, + 10332 + ], + [ + 7, + 32 + ], + [ + 1, + 10400 + ], + [ + 1, + 10276 + ], + [ + 1, + 10296 + ], + [ + 1, + 10388 + ], + [ + 1, + 10310 + ], + [ + 1, + 10241 + ], + [ + 1, + 10452 + ], + [ + 1, + 10247 + ], + [ + 9, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10276 + ], + [ + 2, + 10250 + ], + [ + 1, + 10283 + ], + [ + 1, + 10254 + ], + [ + 1, + 10374 + ], + [ + 2, + 32 + ], + [ + 1, + 10432 + ], + [ + 19, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10319 + ], + [ + 1, + 10241 + ], + [ + 1, + 10436 + ], + [ + 1, + 10304 + ], + [ + 1, + 10249 + ], + [ + 9, + 32 + ], + [ + 1, + 10272 + ], + [ + 1, + 10323 + ], + [ + 1, + 10465 + ], + [ + 1, + 10243 + ], + [ + 1, + 32 + ], + [ + 1, + 10248 + ], + [ + 10, + 32 + ], + [ + 1, + 10264 + ], + [ + 1, + 10308 + ], + [ + 1, + 10368 + ], + [ + 1, + 10432 + ], + [ + 1, + 10368 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10247 + ], + [ + 21, + 32 + ], + [ + 1, + 10424 + ], + [ + 1, + 10311 + ], + [ + 1, + 10356 + ], + [ + 1, + 10251 + ], + [ + 1, + 10241 + ], + [ + 11, + 32 + ], + [ + 1, + 10249 + ], + [ + 15, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10241 + ], + [ + 1, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10258 + ], + [ + 1, + 10266 + ], + [ + 3, + 32 + ], + [ + 1, + 10336 + ], + [ + 1, + 10262 + ], + [ + 17, + 32 + ], + [ + 1, + 10296 + ], + [ + 1, + 10276 + ], + [ + 1, + 10272 + ], + [ + 1, + 10244 + ], + [ + 20, + 32 + ], + [ + 1, + 10258 + ], + [ + 15, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10241 + ], + [ + 19, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10464 + ], + [ + 1, + 10244 + ], + [ + 14, + 32 + ], + [ + 1, + 10432 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 3, + 10432 + ], + [ + 1, + 10304 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 2, + 10432 + ], + [ + 1, + 10276 + ], + [ + 4, + 10432 + ], + [ + 1, + 10276 + ], + [ + 1, + 10372 + ], + [ + 2, + 10432 + ], + [ + 7, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10272 + ], + [ + 1, + 10432 + ], + [ + 7, + 10276 + ], + [ + 1, + 10292 + ], + [ + 1, + 10262 + ], + [ + 3, + 10258 + ], + [ + 1, + 10486 + ], + [ + 1, + 10302 + ], + [ + 1, + 10266 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10432 + ], + [ + 1, + 32 + ], + [ + 1, + 10432 + ], + [ + 1, + 10336 + ], + [ + 1, + 10276 + ], + [ + 1, + 10262 + ], + [ + 7, + 10249 + ], + [ + 1, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10241 + ], + [ + 3, + 32 + ], + [ + 1, + 10266 + ], + [ + 1, + 10249 + ], + [ + 1, + 10241 + ], + [ + 11, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10377 + ], + [ + 1, + 10486 + ], + [ + 1, + 10242 + ], + [ + 1, + 32 + ], + [ + 1, + 10454 + ], + [ + 3, + 10450 + ], + [ + 2, + 10459 + ], + [ + 1, + 10434 + ], + [ + 11, + 10432 + ], + [ + 1, + 10441 + ], + [ + 1, + 10449 + ], + [ + 2, + 10450 + ], + [ + 1, + 10442 + ], + [ + 2, + 10441 + ], + [ + 1, + 10433 + ], + [ + 30, + 10432 + ], + [ + 1, + 10441 + ], + [ + 2, + 10450 + ], + [ + 1, + 10482 + ] + ], + "fg": [ + [ + 14, + 29806811 + ], + [ + 14, + 19148864 + ], + [ + 4, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 6, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 14, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 20, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 4, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 32, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 10, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 7, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 9, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 10, + 29806811 + ], + [ + 7, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 14, + 19148864 + ], + [ + 6, + 29806811 + ], + [ + 26, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 12, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 4, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 8, + 29806811 + ], + [ + 16, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 10, + 19148864 + ], + [ + 15, + 29806811 + ], + [ + 8, + 19148864 + ], + [ + 9, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 17, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 18, + 29806811 + ], + [ + 9, + 19148864 + ], + [ + 7, + 29806811 + ], + [ + 13, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 10, + 19148864 + ], + [ + 24, + 29806811 + ], + [ + 7, + 19148864 + ], + [ + 5, + 29806811 + ], + [ + 12, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 7, + 19148864 + ], + [ + 24, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 6, + 29806811 + ], + [ + 6, + 19148864 + ], + [ + 9, + 29806811 + ], + [ + 11, + 19148864 + ], + [ + 20, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 7, + 29806811 + ], + [ + 8, + 19148864 + ], + [ + 9, + 29806811 + ], + [ + 7, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 19, + 29806811 + ], + [ + 6, + 19148864 + ], + [ + 9, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 10, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 21, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 11, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 15, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 17, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 20, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 15, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 19, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 14, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 12, + 19148864 + ], + [ + 7, + 29806811 + ], + [ + 18, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 11, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 11, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 60, + 19148864 + ] + ], + "bg": [ + [ + 960, + 17106698 + ] + ], + "attrs": [ + [ + 960, + 0 + ] + ], + "clusters": [], + "text": [ + " ⢀⣀⣀⣀⢀⣀⣀⣀⣀⣀⢤⣤⣀⣀ ⢀⣀⣀ ⣀⣀ ⣀ ", + "⣄ ⢀⣀⣀⣀⣀⣀⣀⢶⢤⡤⠤⢀⡀⠛⠒⠈⠛⠓⢢⣄ ⢠⡞ ⠈⢀⡀⢀⣀ ⡀⠠⢔⠒⠂⣄⠤⠒⠒⠉⠁⠶⠤⠤⣀⠐⠤⠒⣀⣀⣀⣀⣀", + "⠋⠐⠂⣃⣀⡤⢼⣀⡀ ⠉⠉⠁⠁⡡⠐⠒⠉⠼⠏ ⠓⠤⠒⠉⠐⠚⠛ ⣀⢠⢐⢭⠎⢸⠛⠋⠉ ⠉⠈⠉⠁ ⣀⣀⣠⣤⣄⣀⠾", + " ⠈⠉⠁ ⠉⣠⠤⠤⢄⣀⣀⡀⠹⠃⢀⣀⣠⡄ ⠈⠷⠆⣀⠉⢰⣒⡒⢤⢠⡒⠲⠖⠉⠚⠤⠤⠤⠖⠤⠤⠔⠢⢄⡙⣶ ⠸⠊ ", + " ⠣⡀ ⠈⠓⡭⠋⠉ ⢹⣲⠂⢠⠈⠙⠈⠈⠁⠆⠰⠛⠭⡶⠂⠂ ⠑⠒⠒⠉⢤⢠⡄⠉⠛⠁ ", + " ⠈⠉⠑⠢⠒⠒⢾⡀ ⢀⡤⠊ ⠙⡀⠋⡇⠈⠁⠒⢀⡀⣖⣕⠁⠒⠤⢀⡄⡀ ⢀⡀⠈⠁ ", + " ⠈⠑⠚⠶⢀⡄⠂⡀⠂ ⠉⡀⠆⢀⣀⡀⢠⠋⣉⠇⣦⠶⠁ ⠘⡄⡔⠊⠣⠈⢪⡀⠈⢀ ", + " ⠈⠙⠈⢻⣭⠂⠂ ⠈⠃⠁⠃⠜⡀⠈⠋⣤⢀⠙⠁ ⠘⠓ ⠠⠳⠉⣠⠊⠈⡀ ", + " ⢰⠺⡃⡀ ⠈⠉⢒⠆ ⠑⠁⢀⢀⡄⢢ ⠑⠕⠤⠄⢀⠄⠻⡷⠐⠰⠄ ", + " ⠓⠎⢠⡀ ⡜ ⢠⠤⠸⢔⡆⠁⣔⠇ ⢀⠤⠊⠊⠫⠎⢆ ⣀ ", + " ⢀⡏⠁⣄⡀⠉ ⠠⡓⣡⠃ ⠈ ⠘⡄⢀⣀⢀ ⢀⠇ ", + " ⢸⡇⡴⠋⠁ ⠉ ⠈⠁ ⠉⠒⠚ ⡠⠖", + " ⠸⠤⠠⠄ ⠒ ⠈⠁ ", + " ⢀⣠⠄ ⣀ ⢀⣀⣀⣀⡀ ⢀⣀⣀⠤⣀⣀⣀⣀⠤⢄⣀⣀ ", + " ⢀⠠⣀⠤⠤⠤⠤⠤⠤⠤⠴⠖⠒⠒⠒⣶⠾⠚ ⢀⣀ ⣀⡠⠤⠖⠉⠉⠉⠉⠉⠉⠉ ⠉⠁ ⠚⠉⠁ ⠉⢉⣶⠂ ", + "⣖⣒⣒⣒⣛⣛⣂⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣉⣑⣒⣒⣊⣉⣉⣁⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣉⣒⣒⣲" + ] + } + }, + { + "name": "world-zoom", + "width": 40, + "height": 12, + "result": { + "width": 40, + "height": 12, + "chars": [ + [ + 6, + 10258 + ], + [ + 1, + 10241 + ], + [ + 7, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10274 + ], + [ + 1, + 10372 + ], + [ + 2, + 32 + ], + [ + 2, + 10432 + ], + [ + 4, + 32 + ], + [ + 1, + 10332 + ], + [ + 1, + 10248 + ], + [ + 1, + 10310 + ], + [ + 1, + 10326 + ], + [ + 1, + 10293 + ], + [ + 13, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10441 + ], + [ + 1, + 10293 + ], + [ + 1, + 10262 + ], + [ + 1, + 10242 + ], + [ + 9, + 32 + ], + [ + 2, + 10249 + ], + [ + 1, + 10368 + ], + [ + 1, + 10254 + ], + [ + 2, + 32 + ], + [ + 1, + 10336 + ], + [ + 1, + 10250 + ], + [ + 2, + 32 + ], + [ + 1, + 10439 + ], + [ + 1, + 10488 + ], + [ + 11, + 32 + ], + [ + 1, + 10276 + ], + [ + 1, + 10258 + ], + [ + 1, + 10250 + ], + [ + 1, + 10249 + ], + [ + 13, + 32 + ], + [ + 1, + 10326 + ], + [ + 1, + 10258 + ], + [ + 1, + 10250 + ], + [ + 1, + 32 + ], + [ + 1, + 10336 + ], + [ + 1, + 10250 + ], + [ + 3, + 32 + ], + [ + 1, + 10352 + ], + [ + 1, + 10257 + ], + [ + 1, + 10274 + ], + [ + 1, + 10372 + ], + [ + 24, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10432 + ], + [ + 1, + 10340 + ], + [ + 1, + 10279 + ], + [ + 2, + 10258 + ], + [ + 1, + 10249 + ], + [ + 4, + 32 + ], + [ + 1, + 10480 + ], + [ + 1, + 10277 + ], + [ + 1, + 10276 + ], + [ + 1, + 10258 + ], + [ + 1, + 10250 + ], + [ + 1, + 10241 + ], + [ + 14, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10336 + ], + [ + 1, + 10260 + ], + [ + 1, + 10362 + ], + [ + 1, + 10432 + ], + [ + 1, + 10336 + ], + [ + 1, + 10276 + ], + [ + 1, + 10326 + ], + [ + 1, + 10249 + ], + [ + 1, + 10325 + ], + [ + 1, + 10249 + ], + [ + 9, + 32 + ], + [ + 1, + 10332 + ], + [ + 1, + 10402 + ], + [ + 18, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10258 + ], + [ + 1, + 10274 + ], + [ + 1, + 10285 + ], + [ + 1, + 10432 + ], + [ + 1, + 32 + ], + [ + 1, + 10296 + ], + [ + 1, + 10400 + ], + [ + 1, + 10260 + ], + [ + 1, + 10290 + ], + [ + 1, + 10304 + ], + [ + 6, + 32 + ], + [ + 1, + 10432 + ], + [ + 1, + 10276 + ], + [ + 1, + 10266 + ], + [ + 1, + 32 + ], + [ + 1, + 10400 + ], + [ + 1, + 10243 + ], + [ + 18, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10276 + ], + [ + 1, + 10250 + ], + [ + 1, + 10241 + ], + [ + 2, + 32 + ], + [ + 1, + 10264 + ], + [ + 1, + 10308 + ], + [ + 1, + 10368 + ], + [ + 1, + 10467 + ], + [ + 2, + 32 + ], + [ + 1, + 10432 + ], + [ + 1, + 10276 + ], + [ + 1, + 10258 + ], + [ + 1, + 10249 + ], + [ + 3, + 32 + ], + [ + 1, + 10464 + ], + [ + 1, + 10243 + ], + [ + 20, + 32 + ], + [ + 1, + 10257 + ], + [ + 1, + 10308 + ], + [ + 4, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10241 + ], + [ + 1, + 10400 + ], + [ + 1, + 10258 + ], + [ + 1, + 10299 + ], + [ + 1, + 10272 + ], + [ + 1, + 10292 + ], + [ + 1, + 10258 + ], + [ + 1, + 10274 + ], + [ + 1, + 10260 + ], + [ + 1, + 10258 + ], + [ + 1, + 10249 + ], + [ + 23, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10310 + ], + [ + 6, + 32 + ], + [ + 1, + 10275 + ], + [ + 1, + 10250 + ], + [ + 30, + 32 + ], + [ + 1, + 10424 + ], + [ + 38, + 32 + ], + [ + 1, + 10336 + ], + [ + 1, + 10243 + ], + [ + 36, + 32 + ], + [ + 1, + 10432 + ], + [ + 1, + 10268 + ], + [ + 1, + 32 + ], + [ + 1, + 10432 + ], + [ + 1, + 10308 + ], + [ + 30, + 32 + ] + ], + "fg": [ + [ + 7, + 19148864 + ], + [ + 7, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 4, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 13, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 9, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 11, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 13, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 24, + 29806811 + ], + [ + 7, + 19148864 + ], + [ + 4, + 29806811 + ], + [ + 6, + 19148864 + ], + [ + 14, + 29806811 + ], + [ + 11, + 19148864 + ], + [ + 9, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 18, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 6, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 18, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 20, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 4, + 29806811 + ], + [ + 12, + 19148864 + ], + [ + 23, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 6, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 30, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 38, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 36, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 30, + 29806811 + ] + ], + "bg": [ + [ + 480, + 17106698 + ] + ], + "attrs": [ + [ + 480, + 0 + ] + ], + "clusters": [], + "text": [ + "⠒⠒⠒⠒⠒⠒⠁ ⠈⠢⢄ ⣀⣀ ⡜⠈⡆⡖⠵ ", + " ⠈⣉⠵⠖⠂ ⠉⠉⢀⠎ ⡠⠊ ⣇⣸ ", + "⠤⠒⠊⠉ ⡖⠒⠊ ⡠⠊ ⡰⠑⠢⢄ ", + " ⢀⣀⡤⠧⠒⠒⠉ ⣰⠥⠤⠒⠊⠁ ", + " ⢀⡠⠔⡺⣀⡠⠤⡖⠉⡕⠉ ⡜⢢ ", + " ⠉⠒⠢⠭⣀ ⠸⢠⠔⠲⡀ ⣀⠤⠚ ⢠⠃ ", + " ⢀⠤⠊⠁ ⠘⡄⢀⣣ ⣀⠤⠒⠉ ⣠⠃ ", + " ⠑⡄ ⠉⠁⢠⠒⠻⠠⠴⠒⠢⠔⠒⠉ ", + " ⠈⡆ ⠣⠊ ", + " ⢸ ", + " ⡠⠃ ", + " ⣀⠜ ⣀⡄ " + ] + } + }, + { + "name": "world-highlight", + "width": 60, + "height": 16, + "result": { + "width": 60, + "height": 16, + "chars": [ + [ + 14, + 32 + ], + [ + 1, + 10368 + ], + [ + 3, + 10432 + ], + [ + 1, + 10368 + ], + [ + 5, + 10432 + ], + [ + 1, + 10404 + ], + [ + 1, + 10468 + ], + [ + 2, + 10432 + ], + [ + 4, + 32 + ], + [ + 1, + 10368 + ], + [ + 2, + 10432 + ], + [ + 2, + 32 + ], + [ + 2, + 10432 + ], + [ + 6, + 32 + ], + [ + 1, + 10432 + ], + [ + 14, + 32 + ], + [ + 1, + 10436 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 6, + 10432 + ], + [ + 1, + 10422 + ], + [ + 1, + 10404 + ], + [ + 1, + 10340 + ], + [ + 1, + 10276 + ], + [ + 1, + 10368 + ], + [ + 1, + 10304 + ], + [ + 1, + 10267 + ], + [ + 1, + 10258 + ], + [ + 1, + 10248 + ], + [ + 1, + 10267 + ], + [ + 1, + 10259 + ], + [ + 1, + 10402 + ], + [ + 1, + 10436 + ], + [ + 3, + 32 + ], + [ + 1, + 10400 + ], + [ + 1, + 10334 + ], + [ + 4, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10368 + ], + [ + 1, + 10304 + ], + [ + 1, + 10368 + ], + [ + 1, + 10432 + ], + [ + 1, + 32 + ], + [ + 1, + 10304 + ], + [ + 1, + 10272 + ], + [ + 1, + 10388 + ], + [ + 1, + 10258 + ], + [ + 1, + 10242 + ], + [ + 1, + 10436 + ], + [ + 1, + 10276 + ], + [ + 2, + 10258 + ], + [ + 1, + 10249 + ], + [ + 1, + 10241 + ], + [ + 1, + 10294 + ], + [ + 2, + 10276 + ], + [ + 1, + 10432 + ], + [ + 1, + 10256 + ], + [ + 1, + 10276 + ], + [ + 1, + 10258 + ], + [ + 5, + 10432 + ], + [ + 1, + 10251 + ], + [ + 1, + 10256 + ], + [ + 1, + 10242 + ], + [ + 1, + 10435 + ], + [ + 1, + 10432 + ], + [ + 1, + 10340 + ], + [ + 1, + 10428 + ], + [ + 1, + 10432 + ], + [ + 1, + 10304 + ], + [ + 1, + 32 + ], + [ + 2, + 10249 + ], + [ + 2, + 10241 + ], + [ + 1, + 10337 + ], + [ + 1, + 10256 + ], + [ + 1, + 10258 + ], + [ + 1, + 10249 + ], + [ + 1, + 10300 + ], + [ + 1, + 10255 + ], + [ + 1, + 32 + ], + [ + 1, + 10259 + ], + [ + 1, + 10276 + ], + [ + 1, + 10258 + ], + [ + 1, + 10249 + ], + [ + 1, + 10256 + ], + [ + 1, + 10266 + ], + [ + 1, + 10267 + ], + [ + 1, + 32 + ], + [ + 1, + 10432 + ], + [ + 1, + 10400 + ], + [ + 1, + 10384 + ], + [ + 1, + 10413 + ], + [ + 1, + 10254 + ], + [ + 1, + 10424 + ], + [ + 1, + 10267 + ], + [ + 1, + 10251 + ], + [ + 1, + 10249 + ], + [ + 1, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10248 + ], + [ + 1, + 10249 + ], + [ + 1, + 10241 + ], + [ + 10, + 32 + ], + [ + 2, + 10432 + ], + [ + 1, + 10464 + ], + [ + 1, + 10468 + ], + [ + 1, + 10436 + ], + [ + 1, + 10432 + ], + [ + 1, + 10302 + ], + [ + 2, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10249 + ], + [ + 1, + 10241 + ], + [ + 3, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10464 + ], + [ + 2, + 10276 + ], + [ + 1, + 10372 + ], + [ + 2, + 10432 + ], + [ + 1, + 10304 + ], + [ + 1, + 10297 + ], + [ + 1, + 10243 + ], + [ + 1, + 10368 + ], + [ + 1, + 10432 + ], + [ + 1, + 10464 + ], + [ + 1, + 10308 + ], + [ + 6, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10295 + ], + [ + 1, + 10246 + ], + [ + 1, + 10432 + ], + [ + 1, + 10249 + ], + [ + 1, + 10416 + ], + [ + 1, + 10450 + ], + [ + 1, + 10322 + ], + [ + 1, + 10404 + ], + [ + 1, + 10400 + ], + [ + 1, + 10322 + ], + [ + 1, + 10290 + ], + [ + 1, + 10262 + ], + [ + 1, + 10249 + ], + [ + 1, + 10266 + ], + [ + 3, + 10276 + ], + [ + 1, + 10262 + ], + [ + 2, + 10276 + ], + [ + 1, + 10260 + ], + [ + 1, + 10274 + ], + [ + 1, + 10372 + ], + [ + 1, + 10329 + ], + [ + 1, + 10486 + ], + [ + 1, + 32 + ], + [ + 1, + 10296 + ], + [ + 1, + 10250 + ], + [ + 12, + 32 + ], + [ + 1, + 10275 + ], + [ + 1, + 10304 + ], + [ + 4, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10259 + ], + [ + 1, + 10349 + ], + [ + 1, + 10251 + ], + [ + 1, + 10249 + ], + [ + 8, + 32 + ], + [ + 1, + 10425 + ], + [ + 1, + 10482 + ], + [ + 1, + 10242 + ], + [ + 1, + 10400 + ], + [ + 1, + 10248 + ], + [ + 1, + 10265 + ], + [ + 2, + 10248 + ], + [ + 1, + 10241 + ], + [ + 1, + 10246 + ], + [ + 1, + 10288 + ], + [ + 1, + 10267 + ], + [ + 1, + 10285 + ], + [ + 1, + 10358 + ], + [ + 2, + 10242 + ], + [ + 1, + 32 + ], + [ + 1, + 10257 + ], + [ + 2, + 10258 + ], + [ + 1, + 10249 + ], + [ + 1, + 10404 + ], + [ + 1, + 10400 + ], + [ + 1, + 10308 + ], + [ + 1, + 10249 + ], + [ + 1, + 10267 + ], + [ + 1, + 10241 + ], + [ + 15, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10249 + ], + [ + 1, + 10257 + ], + [ + 1, + 10274 + ], + [ + 2, + 10258 + ], + [ + 1, + 10430 + ], + [ + 1, + 10304 + ], + [ + 9, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10340 + ], + [ + 1, + 10250 + ], + [ + 1, + 32 + ], + [ + 1, + 10265 + ], + [ + 1, + 10304 + ], + [ + 1, + 10251 + ], + [ + 1, + 10311 + ], + [ + 1, + 10248 + ], + [ + 1, + 10241 + ], + [ + 1, + 10258 + ], + [ + 1, + 10368 + ], + [ + 1, + 10304 + ], + [ + 1, + 10454 + ], + [ + 1, + 10453 + ], + [ + 1, + 10241 + ], + [ + 1, + 10258 + ], + [ + 1, + 10276 + ], + [ + 1, + 10368 + ], + [ + 1, + 10308 + ], + [ + 1, + 10304 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10304 + ], + [ + 1, + 10248 + ], + [ + 1, + 10241 + ], + [ + 18, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10257 + ], + [ + 1, + 10266 + ], + [ + 1, + 10294 + ], + [ + 1, + 10368 + ], + [ + 1, + 10308 + ], + [ + 1, + 10242 + ], + [ + 1, + 10304 + ], + [ + 1, + 10242 + ], + [ + 7, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10304 + ], + [ + 1, + 10246 + ], + [ + 1, + 10368 + ], + [ + 1, + 10432 + ], + [ + 1, + 10304 + ], + [ + 1, + 10400 + ], + [ + 1, + 10251 + ], + [ + 1, + 10441 + ], + [ + 1, + 10247 + ], + [ + 1, + 10470 + ], + [ + 1, + 10294 + ], + [ + 1, + 10241 + ], + [ + 1, + 32 + ], + [ + 1, + 10264 + ], + [ + 1, + 10308 + ], + [ + 1, + 10324 + ], + [ + 1, + 10250 + ], + [ + 1, + 10275 + ], + [ + 1, + 10248 + ], + [ + 1, + 10410 + ], + [ + 1, + 10304 + ], + [ + 1, + 10248 + ], + [ + 1, + 10368 + ], + [ + 24, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10265 + ], + [ + 1, + 10248 + ], + [ + 1, + 10427 + ], + [ + 1, + 10477 + ], + [ + 2, + 10242 + ], + [ + 5, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10243 + ], + [ + 1, + 10241 + ], + [ + 1, + 10243 + ], + [ + 1, + 10268 + ], + [ + 1, + 10304 + ], + [ + 1, + 10248 + ], + [ + 1, + 10251 + ], + [ + 1, + 10468 + ], + [ + 1, + 10368 + ], + [ + 1, + 10265 + ], + [ + 1, + 10241 + ], + [ + 3, + 32 + ], + [ + 1, + 10264 + ], + [ + 1, + 10259 + ], + [ + 1, + 32 + ], + [ + 1, + 10272 + ], + [ + 1, + 10291 + ], + [ + 1, + 10249 + ], + [ + 1, + 10464 + ], + [ + 1, + 10250 + ], + [ + 1, + 10248 + ], + [ + 1, + 10304 + ], + [ + 24, + 32 + ], + [ + 1, + 10416 + ], + [ + 1, + 10298 + ], + [ + 1, + 10307 + ], + [ + 1, + 10304 + ], + [ + 1, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10249 + ], + [ + 1, + 10386 + ], + [ + 1, + 10246 + ], + [ + 6, + 32 + ], + [ + 1, + 10257 + ], + [ + 1, + 10241 + ], + [ + 2, + 10368 + ], + [ + 1, + 10308 + ], + [ + 1, + 10402 + ], + [ + 9, + 32 + ], + [ + 1, + 10257 + ], + [ + 1, + 10261 + ], + [ + 1, + 10276 + ], + [ + 1, + 10244 + ], + [ + 1, + 10368 + ], + [ + 1, + 10244 + ], + [ + 1, + 10299 + ], + [ + 1, + 10359 + ], + [ + 1, + 10256 + ], + [ + 1, + 10288 + ], + [ + 1, + 10244 + ], + [ + 20, + 32 + ], + [ + 1, + 10259 + ], + [ + 1, + 10254 + ], + [ + 1, + 10400 + ], + [ + 1, + 10304 + ], + [ + 2, + 32 + ], + [ + 1, + 10332 + ], + [ + 7, + 32 + ], + [ + 1, + 10400 + ], + [ + 1, + 10276 + ], + [ + 1, + 10296 + ], + [ + 1, + 10388 + ], + [ + 1, + 10310 + ], + [ + 1, + 10241 + ], + [ + 1, + 10452 + ], + [ + 1, + 10247 + ], + [ + 9, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10276 + ], + [ + 2, + 10250 + ], + [ + 1, + 10283 + ], + [ + 1, + 10254 + ], + [ + 1, + 10374 + ], + [ + 2, + 32 + ], + [ + 1, + 10432 + ], + [ + 19, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10319 + ], + [ + 1, + 10241 + ], + [ + 1, + 10436 + ], + [ + 1, + 10304 + ], + [ + 1, + 10249 + ], + [ + 9, + 32 + ], + [ + 1, + 10272 + ], + [ + 1, + 10323 + ], + [ + 1, + 10465 + ], + [ + 1, + 10243 + ], + [ + 1, + 32 + ], + [ + 1, + 10248 + ], + [ + 10, + 32 + ], + [ + 1, + 10264 + ], + [ + 1, + 10308 + ], + [ + 1, + 10368 + ], + [ + 1, + 10432 + ], + [ + 1, + 10368 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10247 + ], + [ + 21, + 32 + ], + [ + 1, + 10424 + ], + [ + 1, + 10311 + ], + [ + 1, + 10356 + ], + [ + 1, + 10251 + ], + [ + 1, + 10241 + ], + [ + 11, + 32 + ], + [ + 1, + 10249 + ], + [ + 15, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10241 + ], + [ + 1, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10258 + ], + [ + 1, + 10266 + ], + [ + 3, + 32 + ], + [ + 1, + 10336 + ], + [ + 1, + 10262 + ], + [ + 17, + 32 + ], + [ + 1, + 10296 + ], + [ + 1, + 10276 + ], + [ + 1, + 10272 + ], + [ + 1, + 10244 + ], + [ + 20, + 32 + ], + [ + 1, + 10258 + ], + [ + 15, + 32 + ], + [ + 1, + 10248 + ], + [ + 1, + 10241 + ], + [ + 19, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10464 + ], + [ + 1, + 10244 + ], + [ + 14, + 32 + ], + [ + 1, + 10432 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 3, + 10432 + ], + [ + 1, + 10304 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 2, + 10432 + ], + [ + 1, + 10276 + ], + [ + 4, + 10432 + ], + [ + 1, + 10276 + ], + [ + 1, + 10372 + ], + [ + 2, + 10432 + ], + [ + 7, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10272 + ], + [ + 1, + 10432 + ], + [ + 7, + 10276 + ], + [ + 1, + 10292 + ], + [ + 1, + 10262 + ], + [ + 3, + 10258 + ], + [ + 1, + 10486 + ], + [ + 1, + 10302 + ], + [ + 1, + 10266 + ], + [ + 1, + 32 + ], + [ + 1, + 10368 + ], + [ + 1, + 10432 + ], + [ + 1, + 32 + ], + [ + 1, + 10432 + ], + [ + 1, + 10336 + ], + [ + 1, + 10276 + ], + [ + 1, + 10262 + ], + [ + 7, + 10249 + ], + [ + 1, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10241 + ], + [ + 3, + 32 + ], + [ + 1, + 10266 + ], + [ + 1, + 10249 + ], + [ + 1, + 10241 + ], + [ + 11, + 32 + ], + [ + 1, + 10249 + ], + [ + 1, + 10377 + ], + [ + 1, + 10486 + ], + [ + 1, + 10242 + ], + [ + 1, + 32 + ], + [ + 1, + 10454 + ], + [ + 3, + 10450 + ], + [ + 2, + 10459 + ], + [ + 1, + 10434 + ], + [ + 11, + 10432 + ], + [ + 1, + 10441 + ], + [ + 1, + 10449 + ], + [ + 2, + 10450 + ], + [ + 1, + 10442 + ], + [ + 2, + 10441 + ], + [ + 1, + 10433 + ], + [ + 30, + 10432 + ], + [ + 1, + 10441 + ], + [ + 2, + 10450 + ], + [ + 1, + 10482 + ] + ], + "fg": [ + [ + 14, + 29806811 + ], + [ + 14, + 19148864 + ], + [ + 4, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 6, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 14, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 20, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 4, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 32, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 10, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 7, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 9, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 10, + 29806811 + ], + [ + 7, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 14, + 19148864 + ], + [ + 6, + 29806811 + ], + [ + 26, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 12, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 4, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 8, + 29806811 + ], + [ + 16, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 8, + 19148864 + ], + [ + 2, + 22467805 + ], + [ + 15, + 29806811 + ], + [ + 8, + 19148864 + ], + [ + 9, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 17, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 2, + 22467805 + ], + [ + 18, + 29806811 + ], + [ + 9, + 19148864 + ], + [ + 7, + 29806811 + ], + [ + 13, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 10, + 19148864 + ], + [ + 24, + 29806811 + ], + [ + 7, + 19148864 + ], + [ + 5, + 29806811 + ], + [ + 12, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 7, + 19148864 + ], + [ + 24, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 1, + 22467805 + ], + [ + 1, + 29806811 + ], + [ + 4, + 22467805 + ], + [ + 6, + 29806811 + ], + [ + 6, + 19148864 + ], + [ + 9, + 29806811 + ], + [ + 11, + 19148864 + ], + [ + 20, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 1, + 22467805 + ], + [ + 7, + 29806811 + ], + [ + 8, + 19148864 + ], + [ + 9, + 29806811 + ], + [ + 7, + 19148864 + ], + [ + 2, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 19, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 1, + 22467805 + ], + [ + 9, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 10, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 21, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 11, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 15, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 17, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 20, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 15, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 19, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 14, + 29806811 + ], + [ + 1, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 5, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 12, + 19148864 + ], + [ + 7, + 29806811 + ], + [ + 18, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 11, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 2, + 19148864 + ], + [ + 3, + 29806811 + ], + [ + 3, + 19148864 + ], + [ + 11, + 29806811 + ], + [ + 4, + 19148864 + ], + [ + 1, + 29806811 + ], + [ + 60, + 19148864 + ] + ], + "bg": [ + [ + 960, + 17106698 + ] + ], + "attrs": [ + [ + 960, + 0 + ] + ], + "clusters": [], + "text": [ + " ⢀⣀⣀⣀⢀⣀⣀⣀⣀⣀⢤⣤⣀⣀ ⢀⣀⣀ ⣀⣀ ⣀ ", + "⣄ ⢀⣀⣀⣀⣀⣀⣀⢶⢤⡤⠤⢀⡀⠛⠒⠈⠛⠓⢢⣄ ⢠⡞ ⠈⢀⡀⢀⣀ ⡀⠠⢔⠒⠂⣄⠤⠒⠒⠉⠁⠶⠤⠤⣀⠐⠤⠒⣀⣀⣀⣀⣀", + "⠋⠐⠂⣃⣀⡤⢼⣀⡀ ⠉⠉⠁⠁⡡⠐⠒⠉⠼⠏ ⠓⠤⠒⠉⠐⠚⠛ ⣀⢠⢐⢭⠎⢸⠛⠋⠉ ⠉⠈⠉⠁ ⣀⣀⣠⣤⣄⣀⠾", + " ⠈⠉⠁ ⠉⣠⠤⠤⢄⣀⣀⡀⠹⠃⢀⣀⣠⡄ ⠈⠷⠆⣀⠉⢰⣒⡒⢤⢠⡒⠲⠖⠉⠚⠤⠤⠤⠖⠤⠤⠔⠢⢄⡙⣶ ⠸⠊ ", + " ⠣⡀ ⠈⠓⡭⠋⠉ ⢹⣲⠂⢠⠈⠙⠈⠈⠁⠆⠰⠛⠭⡶⠂⠂ ⠑⠒⠒⠉⢤⢠⡄⠉⠛⠁ ", + " ⠈⠉⠑⠢⠒⠒⢾⡀ ⢀⡤⠊ ⠙⡀⠋⡇⠈⠁⠒⢀⡀⣖⣕⠁⠒⠤⢀⡄⡀ ⢀⡀⠈⠁ ", + " ⠈⠑⠚⠶⢀⡄⠂⡀⠂ ⠉⡀⠆⢀⣀⡀⢠⠋⣉⠇⣦⠶⠁ ⠘⡄⡔⠊⠣⠈⢪⡀⠈⢀ ", + " ⠈⠙⠈⢻⣭⠂⠂ ⠈⠃⠁⠃⠜⡀⠈⠋⣤⢀⠙⠁ ⠘⠓ ⠠⠳⠉⣠⠊⠈⡀ ", + " ⢰⠺⡃⡀ ⠈⠉⢒⠆ ⠑⠁⢀⢀⡄⢢ ⠑⠕⠤⠄⢀⠄⠻⡷⠐⠰⠄ ", + " ⠓⠎⢠⡀ ⡜ ⢠⠤⠸⢔⡆⠁⣔⠇ ⢀⠤⠊⠊⠫⠎⢆ ⣀ ", + " ⢀⡏⠁⣄⡀⠉ ⠠⡓⣡⠃ ⠈ ⠘⡄⢀⣀⢀ ⢀⠇ ", + " ⢸⡇⡴⠋⠁ ⠉ ⠈⠁ ⠉⠒⠚ ⡠⠖", + " ⠸⠤⠠⠄ ⠒ ⠈⠁ ", + " ⢀⣠⠄ ⣀ ⢀⣀⣀⣀⡀ ⢀⣀⣀⠤⣀⣀⣀⣀⠤⢄⣀⣀ ", + " ⢀⠠⣀⠤⠤⠤⠤⠤⠤⠤⠴⠖⠒⠒⠒⣶⠾⠚ ⢀⣀ ⣀⡠⠤⠖⠉⠉⠉⠉⠉⠉⠉ ⠉⠁ ⠚⠉⠁ ⠉⢉⣶⠂ ", + "⣖⣒⣒⣒⣛⣛⣂⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣉⣑⣒⣒⣊⣉⣉⣁⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣉⣒⣒⣲" + ] + } + }, { "name": "badge", "width": 20, diff --git a/ports/conformance/generate.ts b/ports/conformance/generate.ts index 34222fb663c98139ac16e102c0fba6aacd3dbb7b..5ec0f68fcf461ea175e946ad1bc30a584b6319fc 100644 GIT binary patch delta 403 zcma)$F-yZh6vu~degfV8WHEtjYf2k1(1MPF;2?BxdL~D5lH7${u(qXi6Eb%c+}(tJ zjeZR`KZJKjkWS)*8{WP5`@i4EYwytZKEK-SaEL1`R9&Pfse;gGa*l-LGijtXEriTa zsbX0O$u)#T%Cnkt&7hUpH9#e##-nQT6PPlAHX)^kKr(erRjVhSG(t%mpd?4825x(+ z6E3;$IRlR&x?l(+244r5&M+TXg}K$crOG!)*P9?<4m~FOE2T<3wJ2+5@U^0 z$nzMX1xhkM;UvO_VcE@MjKro Ly`S#C+v(*GMlXF2 delta 18 ZcmX@p$-J_SdBfAr$@-n_oBO+MEdf&@2tEJ+ diff --git a/ports/cpp/CMakeLists.txt b/ports/cpp/CMakeLists.txt index bf46e4e..507b29c 100644 --- a/ports/cpp/CMakeLists.txt +++ b/ports/cpp/CMakeLists.txt @@ -14,7 +14,7 @@ if(HQTUI_LTO) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) endif() add_library(hqtui_cpp INTERFACE) -add_library(hqtui_cpp_widgets src/widgets.cpp src/scrollbar.cpp src/chart.cpp src/calendar.cpp src/canvas.cpp src/shadow.cpp) +add_library(hqtui_cpp_widgets src/widgets.cpp src/scrollbar.cpp src/chart.cpp src/calendar.cpp src/canvas.cpp src/shadow.cpp src/world.cpp src/world_data.cpp) set_target_properties(hqtui_cpp_widgets PROPERTIES POSITION_INDEPENDENT_CODE ON) target_compile_features(hqtui_cpp_widgets PUBLIC cxx_std_17) if(MSVC) @@ -71,6 +71,8 @@ if(UNIX) # keeps the published C++ snippets true. add_executable(hqtui-widgets examples/widgets.cpp) target_link_libraries(hqtui-widgets PRIVATE hqtui::cpp) + add_executable(hqtui-world-probe examples/world-probe.cpp) + target_link_libraries(hqtui-world-probe PRIVATE hqtui::cpp) endif() if(BUILD_TESTING) find_package(Python3 COMPONENTS Interpreter) diff --git a/ports/cpp/examples/world-probe.cpp b/ports/cpp/examples/world-probe.cpp new file mode 100644 index 0000000..22b14cd --- /dev/null +++ b/ports/cpp/examples/world-probe.cpp @@ -0,0 +1,45 @@ +/// Prints what the country lookup answers for a fixed set of points. +/// +/// The same probe exists for every port, so "the ports agree about the world" is +/// a diff rather than a hope. +#include +#include + +using namespace hqtui; + +int main() { + struct Place { + const char *name; + double lon, lat; + }; + const Place places[] = { + {"Paris", 2.35, 48.86}, {"Tokyo", 139.7, 35.7}, + {"Cairo", 31.2, 30.0}, {"Brasilia", -47.9, -15.8}, + {"Canberra", 149.1, -35.3}, {"Denver", -105.0, 39.7}, + {"Moscow", 37.6, 55.75}, {"Delhi", 77.2, 28.6}, + {"Nairobi", 36.8, -1.3}, {"Pacific", -140.0, 0.0}, + {"Atlantic", -30.0, 0.0}, {"SouthernOcean", 80.0, -40.0}, + {"NorthPacific", -150.0, 40.0}, + }; + for (auto &place : places) { + const CountryOutline *found = country_at(place.lon, place.lat); + std::printf("%s %s\n", place.name, found ? found->name.c_str() : "-"); + } + + // The cell path, which has to agree with what the canvas drew. + const int cells[][2] = {{173, 28}, {74, 2}, {20, 25}, {88, 7}}; + for (auto &cell : cells) { + const CountryOutline *found = country_at_cell(cell[0], cell[1], 200, 50); + std::printf("cell:%d,%d %s\n", cell[0], cell[1], found ? found->name.c_str() : "-"); + } + + // And the projection itself, so a drift shows up as a number rather than as a + // country that happens to still be right. + const int probes[][2] = {{0, 0}, {99, 25}, {50, 13}}; + for (auto &cell : probes) { + double lon = 0, lat = 0; + degrees_at(cell[0], cell[1], 100, 26, Bounds{-180, 180}, Bounds{-90, 90}, &lon, &lat); + std::printf("degrees:%d,%d %.4f %.4f\n", cell[0], cell[1], lon, lat); + } + return 0; +} diff --git a/ports/cpp/include/hqtui/widgets.hpp b/ports/cpp/include/hqtui/widgets.hpp index a5edc9c..e9f18e7 100644 --- a/ports/cpp/include/hqtui/widgets.hpp +++ b/ports/cpp/include/hqtui/widgets.hpp @@ -592,6 +592,51 @@ Projection canvas_projection(const Braille &, Bounds x, Bounds y); /// A canvas drawn in the caller's own coordinates rather than in pixels. void draw_canvas(Surface, const Canvas &); +struct CountryOutline { + std::string name; + /// ISO 3166-1 alpha-2, where Natural Earth has one. + std::string iso; + /// Longitude and latitude, interleaved. More than one ring means more than + /// one landmass. + std::vector> rings; +}; +/// The country outlines, built once on first use. +const std::vector &world_countries(); +struct WorldShapeOptions { + /// Colour for countries with nothing special about them. + Color color = 0; + /// Countries to pick out, by name or ISO code. + std::vector highlight; + Color highlight_color = 0; +}; +/// The world as canvas shapes, one closed polyline per landmass. +std::vector world_shapes(const WorldShapeOptions & = {}); +/// The country containing a point, or null for open water. +const CountryOutline *country_at(double lon, double lat); +/// Look a country up by name or ISO code. +const CountryOutline *find_country(std::string_view key); +/// The window a country fills, with a little room around it. +void country_bounds(const CountryOutline &, double margin, Bounds *x, Bounds *y); +/// The degrees under a terminal cell, given the window the map was drawn with. +bool degrees_at(int column, int row, int width, int height, Bounds x, Bounds y, double *lon, + double *lat); +struct WorldMap { + /// The window on the globe. Unset means all of it. + std::optional x, y; + /// Coastline colour. + Color color = 0; + /// Countries to pick out, by name or ISO code. + std::vector highlight; + Color highlight_color = 0; + std::optional background; + bool grid = false; +}; +/// A world map drawn in degrees. +void draw_world_map(Surface, const WorldMap & = {}); +/// The country under a cell of a map drawn with these bounds. +const CountryOutline *country_at_cell(int column, int row, int width, int height, + const WorldMap & = {}); + struct Shadow { /// How far the shadow falls. int offset_x = 1, offset_y = 1; @@ -1075,6 +1120,10 @@ class UI { void shapes(Canvas o, Constraint size = fr()) { draw([=](Surface s) { draw_canvas(s, o); }, size); } + /// A world map, and the country under whatever gets clicked. + void world_map(WorldMap o, Constraint size = fr()) { + draw([=](Surface s) { draw_world_map(s, o); }, size); + } void sparkline(Sparkline o) { draw([=](Surface s) { draw_sparkline(s, o); }, cells(1)); } diff --git a/ports/cpp/src/world.cpp b/ports/cpp/src/world.cpp new file mode 100644 index 0000000..f2b1f36 --- /dev/null +++ b/ports/cpp/src/world.cpp @@ -0,0 +1,169 @@ +/// The world, as shapes for the canvas, and the lookup that makes it clickable. +/// +/// The canvas already draws in the caller's own coordinates, and longitude and +/// latitude are just another pair of axes -- so a map is a list of polylines in +/// degrees, and nothing here needs a projection of its own beyond deciding +/// which window on the globe to show. +/// +/// The interesting half is the other direction. A click arrives as a terminal +/// cell, and a country is a polygon, so answering "what did they click" means +/// turning the cell back into degrees and testing it against the outlines. +/// Doing it that way rather than with bounding boxes is what makes the answer +/// right: Russia's bounding box covers most of the northern hemisphere, and +/// Chile's covers Argentina. +#include + +namespace hqtui { +namespace { + +bool equals_ignoring_case(std::string_view a, std::string_view b) { + if (a.size() != b.size()) + return false; + for (std::size_t i = 0; i < a.size(); i++) + if (std::tolower((unsigned char)a[i]) != std::tolower((unsigned char)b[i])) + return false; + return true; +} + +/// Match on either the name or the ISO code, case-insensitively. +bool matches(const CountryOutline &country, const std::vector &keys) { + for (auto &key : keys) { + if (key.empty()) + continue; + if (equals_ignoring_case(country.name, key)) + return true; + if (!country.iso.empty() && equals_ignoring_case(country.iso, key)) + return true; + } + return false; +} + +/// Whether a point is inside a ring, by ray casting. +/// +/// The ring is a flat list of interleaved coordinates, so this walks it two at a +/// time rather than allocating a pair per vertex -- it runs once per country per +/// click, and there are a couple of thousand vertices. +bool inside_ring(const std::vector &ring, double lon, double lat) { + bool inside = false; + std::size_t n = ring.size() / 2; + if (n == 0) + return false; + std::size_t j = n - 1; + for (std::size_t i = 0; i < n; i++) { + double xi = ring[i * 2], yi = ring[i * 2 + 1]; + double xj = ring[j * 2], yj = ring[j * 2 + 1]; + if ((yi > lat) != (yj > lat) && lon < (xj - xi) * (lat - yi) / (yj - yi) + xi) + inside = !inside; + j = i; + } + return inside; +} + +} // namespace + +std::vector world_shapes(const WorldShapeOptions &o) { + std::vector shapes; + for (auto &country : world_countries()) { + bool picked = !o.highlight.empty() && matches(country, o.highlight); + Color color = picked && o.highlight_color ? o.highlight_color : o.color; + for (auto &ring : country.rings) { + Shape shape; + shape.kind = HQ_SHAPE_POLYLINE; + shape.color = color; + std::size_t n = ring.size() / 2; + shape.points.reserve(n + 1); + for (std::size_t i = 0; i < n; i++) + shape.points.push_back({ring[i * 2], ring[i * 2 + 1]}); + // Closed: the last point joins the first, or every country has a gap in + // its coastline where the ring started. + if (n > 0) + shape.points.push_back(shape.points.front()); + shapes.push_back(std::move(shape)); + } + } + return shapes; +} + +const CountryOutline *country_at(double lon, double lat) { + if (!std::isfinite(lon) || !std::isfinite(lat)) + return nullptr; + for (auto &country : world_countries()) + for (auto &ring : country.rings) + if (inside_ring(ring, lon, lat)) + return &country; + return nullptr; +} + +const CountryOutline *find_country(std::string_view key) { + std::vector keys{std::string(key)}; + for (auto &country : world_countries()) + if (matches(country, keys)) + return &country; + return nullptr; +} + +void country_bounds(const CountryOutline &country, double margin, Bounds *x, Bounds *y) { + double min_lon = INFINITY, max_lon = -INFINITY; + double min_lat = INFINITY, max_lat = -INFINITY; + for (auto &ring : country.rings) + for (std::size_t i = 0; i + 1 < ring.size(); i += 2) { + min_lon = std::min(min_lon, ring[i]); + max_lon = std::max(max_lon, ring[i]); + min_lat = std::min(min_lat, ring[i + 1]); + max_lat = std::max(max_lat, ring[i + 1]); + } + if (!std::isfinite(min_lon)) { + *x = Bounds{-180, 180}; + *y = Bounds{-90, 90}; + return; + } + // A single-point country would give a zero-width window, which cannot be + // mapped onto anything. + double pad_x = std::max((max_lon - min_lon) * margin, 1.0); + double pad_y = std::max((max_lat - min_lat) * margin, 1.0); + *x = Bounds{min_lon - pad_x, max_lon + pad_x}; + *y = Bounds{min_lat - pad_y, max_lat + pad_y}; +} + +bool degrees_at(int column, int row, int width, int height, Bounds x, Bounds y, double *lon, + double *lat) { + if (width <= 0 || height <= 0) + return false; + // The canvas is 2x4 Braille pixels per cell, and it spans its bounds across + // `pixels - 1`, so the inverse has to use the same denominators or a click + // drifts from what was drawn. + double px = std::max(1, width * 2 - 1); + double py = std::max(1, height * 4 - 1); + *lon = x.min + ((column * 2 + 1) / px) * (x.max - x.min); + *lat = y.min + (1 - (row * 4 + 2) / py) * (y.max - y.min); + return true; +} + +void draw_world_map(Surface s, const WorldMap &o) { + if (s.rect().width <= 0 || s.rect().height <= 0) + return; + auto &t = theme(s); + WorldShapeOptions shapes; + shapes.color = o.color ? o.color : t.border; + shapes.highlight = o.highlight; + shapes.highlight_color = o.highlight_color ? o.highlight_color : t.accent; + + Canvas canvas; + canvas.shapes = world_shapes(shapes); + canvas.x = o.x.value_or(Bounds{-180, 180}); + canvas.y = o.y.value_or(Bounds{-90, 90}); + canvas.background = o.background; + canvas.grid = o.grid; + draw_canvas(s, canvas); +} + +const CountryOutline *country_at_cell(int column, int row, int width, int height, + const WorldMap &o) { + double lon = 0, lat = 0; + if (!degrees_at(column, row, width, height, o.x.value_or(Bounds{-180, 180}), + o.y.value_or(Bounds{-90, 90}), &lon, &lat)) + return nullptr; + return country_at(lon, lat); +} + +} // namespace hqtui diff --git a/ports/cpp/src/world_data.cpp b/ports/cpp/src/world_data.cpp new file mode 100644 index 0000000..ec897a7 --- /dev/null +++ b/ports/cpp/src/world_data.cpp @@ -0,0 +1,505 @@ +/// Country outlines, flattened for a terminal. +/// +/// Generated by packages/hqtui/scripts/generate-world.ts from Natural Earth's +/// 1:110m Admin 0 countries, which is public domain. Do not edit by hand. +/// +/// Each ring is longitude and latitude interleaved -- lon, lat, lon, lat -- +/// rather than a list of pairs, because at 1982 points the nested form +/// costs a container per coordinate for no gain. A country has more than one +/// ring when it is more than one landmass. +/// +/// 171 countries, 1982 points, simplified at 1 degrees. +#include + +namespace hqtui { +namespace { + +const double POOL[] = { + 66.5,37.4,70.8,38.5,71.8,36.7,75.2,37.1,71.3,36.1,69.3,31.9, + 66.3,29.9,60.9,29.8,61.2,35.7,66.5,37.4,21.0,40.8,19.4,40.3, + 19.7,42.7,21.0,40.8,-8.7,27.4,-8.7,28.8,-1.3,32.3,-1.2,35.7, + 8.4,36.9,7.5,34.1,9.8,29.4,9.3,26.1,12.0,23.5,3.2,19.1, + -8.7,27.4,12.3,-6.1,16.3,-5.9,17.5,-8.1,21.7,-7.3,22.2,-11.1, + 24.0,-11.2,24.0,-12.9,21.9,-12.9,23.2,-17.5,11.7,-17.3,13.7,-11.3, + 12.3,-6.1,-48.7,-78.0,-43.9,-78.5,-43.3,-80.0,-54.2,-80.6,-48.7,-78.0, + -66.3,-80.3,-59.6,-80.0,-66.3,-80.3,-73.9,-71.3,-70.3,-68.9,-68.3,-71.4, + -75.0,-72.1,-73.9,-71.3,-102.3,-71.9,-96.2,-72.5,-102.3,-71.9,-122.6,-73.7, + -118.7,-73.5,-122.6,-73.7,-127.3,-73.5,-124.0,-73.9,-127.3,-73.5,-163.7,-78.6, + -159.2,-79.5,-163.7,-78.6,180.0,-84.7,180.0,-90.0,-180.0,-90.0,-179.1,-84.1, + -143.1,-85.0,-153.6,-83.7,-152.9,-82.0,-156.8,-81.1,-146.4,-80.3,-155.3,-79.1, + -158.4,-76.9,-151.3,-77.4,-146.1,-76.5,-146.2,-75.4,-135.2,-74.3,-100.1,-74.9, + -103.7,-72.6,-74.9,-73.9,-67.4,-72.5,-67.7,-67.3,-57.8,-63.3,-65.7,-68.0, + -61.8,-70.7,-60.8,-73.7,-70.6,-76.6,-77.2,-76.7,-73.7,-77.9,-78.0,-79.2, + -58.2,-83.2,-28.5,-80.3,-35.6,-79.5,-35.8,-78.3,-17.5,-75.1,-15.4,-73.1, + -6.9,-70.9,27.1,-70.5,33.9,-68.5,38.6,-69.8,54.5,-65.8,61.4,-68.0, + 68.9,-67.9,69.7,-69.2,67.9,-71.9,69.9,-72.3,73.9,-69.9,88.0,-66.2, + 95.8,-67.4,102.8,-65.6,106.2,-66.9,113.6,-65.9,119.8,-67.3,135.1,-65.3, + 137.5,-67.0,145.5,-66.9,171.2,-71.7,163.6,-76.2,167.0,-78.8,161.8,-79.2, + 159.8,-80.9,169.4,-83.8,180.0,-84.7,-68.6,-52.6,-65.0,-54.7,-68.6,-54.9, + -68.6,-52.6,-57.6,-30.2,-58.5,-34.4,-56.8,-36.9,-62.3,-38.8,-62.7,-41.0, + -65.1,-41.1,-63.5,-42.6,-67.3,-45.6,-65.6,-47.2,-69.1,-50.7,-68.1,-52.3, + -71.9,-52.0,-73.4,-49.3,-71.2,-44.8,-72.1,-42.3,-68.4,-24.5,-66.3,-21.8, + -62.8,-22.0,-57.8,-25.2,-58.6,-27.1,-55.7,-27.4,-54.1,-25.5,-53.6,-26.9, + -57.6,-30.2,46.5,38.8,43.6,41.1,45.6,40.8,46.5,38.8,147.7,-40.8, + 147.9,-43.2,146.0,-43.5,144.7,-40.7,147.7,-40.8,126.1,-32.2,118.0,-35.1, + 115.0,-34.2,113.7,-22.5,120.9,-19.7,125.7,-14.2,129.6,-15.0,132.4,-11.1, + 136.5,-11.9,135.5,-15.0,140.2,-17.7,142.5,-10.7,146.4,-19.0,150.7,-22.4, + 153.6,-28.1,150.0,-37.4,146.3,-39.0,140.6,-38.0,138.2,-34.4,136.8,-35.3, + 137.8,-32.9,136.0,-34.9,131.3,-31.5,126.1,-32.2,17.0,48.1,14.6,46.4, + 9.5,47.1,12.9,47.5,13.6,48.9,17.0,48.1,46.4,41.9,50.4,40.3, + 48.9,38.3,45.6,39.9,45.0,41.2,46.4,41.9,-78.2,25.2,-77.5,23.8, + -78.2,25.2,92.7,22.0,92.4,20.7,91.4,22.8,89.0,22.1,88.6,26.4, + 92.4,25.0,91.2,23.5,92.7,22.0,28.2,56.2,30.9,55.6,32.7,53.4, + 31.8,52.1,23.5,51.6,23.5,53.9,28.2,56.2,6.2,50.8,5.7,49.5, + 2.5,51.1,6.2,50.8,-89.1,17.8,-88.1,18.3,-88.9,15.9,-89.1,17.8, + 2.7,6.3,0.8,10.5,2.8,12.2,2.7,6.3,91.7,27.8,88.8,27.1, + 91.7,27.8,-69.5,-11.0,-65.3,-9.8,-65.4,-11.6,-60.5,-13.8,-60.2,-16.3, + -58.2,-16.3,-57.9,-20.0,-61.8,-19.6,-62.7,-22.2,-67.8,-22.9,-69.5,-11.0, + 18.6,42.7,16.0,45.2,19.4,44.9,18.6,42.7,29.4,-22.1,25.7,-25.5, + 21.6,-26.7,19.9,-24.8,20.9,-18.3,25.3,-17.7,29.4,-22.1,-53.4,-33.8, + -53.8,-32.0,-57.6,-30.2,-53.6,-26.1,-55.8,-22.4,-57.9,-22.1,-58.2,-16.3, + -60.2,-16.3,-60.5,-13.8,-65.4,-11.6,-65.3,-9.8,-70.5,-11.0,-70.5,-9.5, + -72.2,-10.1,-74.0,-7.5,-72.9,-5.3,-69.9,-4.3,-69.8,1.7,-65.5,0.8, + -63.4,2.2,-64.8,4.1,-60.7,5.2,-59.0,1.3,-52.9,2.1,-51.3,4.2, + -50.4,-0.1,-44.6,-2.7,-40.0,-2.9,-35.6,-5.1,-34.7,-7.3,-38.7,-13.1, + -40.9,-21.9,-47.6,-24.9,-48.9,-28.7,-53.4,-33.8,22.7,44.2,28.6,43.7, + 28.0,42.0,23.0,41.3,22.7,44.2,-5.4,10.4,-4.3,13.2,-1.1,15.0, + 2.2,12.6,0.9,11.0,-2.9,11.0,-2.8,9.6,-5.4,10.4,30.5,-2.4, + 29.3,-4.5,29.0,-2.8,30.5,-2.4,102.6,12.2,103.0,14.2,107.6,13.5, + 106.2,11.0,103.5,10.6,102.6,12.2,14.5,12.9,14.5,4.7,15.9,1.7, + 9.6,2.3,8.8,5.5,11.7,7.0,14.5,12.9,-122.8,49.0,-127.4,50.8, + -130.5,54.3,-130.0,55.9,-135.5,59.8,-137.5,58.9,-141.0,60.3,-141.0,69.7, + -136.5,68.9,-128.1,70.5,-113.5,67.7,-106.1,68.8,-101.5,67.6,-97.7,68.6, + -96.1,67.3,-94.2,69.1,-96.5,70.1,-95.2,71.9,-87.4,67.2,-85.5,69.9, + -82.6,69.7,-81.4,67.1,-85.8,66.6,-90.7,63.6,-94.7,58.9,-92.3,57.1, + -82.3,55.1,-79.9,51.2,-78.6,52.6,-79.8,54.7,-76.5,56.5,-78.5,58.8, + -77.3,59.9,-78.1,62.3,-73.8,62.4,-69.6,61.1,-67.6,58.2,-64.6,60.3, + -61.8,56.3,-57.3,54.6,-55.7,52.1,-60.0,50.2,-66.4,50.2,-71.1,46.8, + -65.1,49.2,-64.5,46.2,-60.5,47.0,-59.8,45.9,-65.4,43.5,-66.2,44.5, + -64.4,45.3,-67.1,45.1,-69.2,47.4,-71.5,45.0,-82.4,41.7,-82.6,45.3, + -88.4,48.3,-122.8,49.0,-84.0,62.5,-81.9,62.9,-84.0,62.5,-79.8,72.8, + -80.8,73.7,-76.3,72.8,-79.8,72.8,-93.6,75.0,-96.8,74.9,-93.6,75.0, + -93.8,77.5,-96.4,77.8,-93.8,77.5,-96.8,78.8,-95.6,78.4,-98.6,78.9, + -96.8,78.8,-88.2,74.4,-97.1,76.8,-79.8,74.9,-88.2,74.4,-111.3,78.2, + -109.9,78.0,-113.5,77.7,-111.3,78.2,-111.0,78.8,-109.7,78.6,-112.5,78.4, + -111.0,78.8,-55.6,51.3,-56.8,49.8,-53.5,49.2,-53.1,46.7,-59.3,47.6, + -55.6,51.3,-83.9,65.1,-80.1,63.7,-87.2,63.5,-85.9,65.7,-83.9,65.1, + -78.8,72.4,-68.8,70.5,-67.0,69.2,-68.8,68.7,-61.9,66.9,-63.9,65.0, + -68.0,66.3,-64.7,63.4,-68.8,63.7,-66.2,61.9,-68.9,62.3,-78.6,64.6, + -74.0,65.5,-73.3,68.1,-79.0,70.2,-88.7,70.4,-90.2,72.2,-85.8,73.8, + -85.8,72.5,-82.3,73.8,-78.8,72.4,-94.5,74.1,-90.5,73.9,-95.4,72.1, + -96.0,73.4,-94.5,74.1,-122.9,76.1,-116.2,77.6,-122.9,76.1,-132.7,54.0, + -131.2,52.2,-132.7,54.0,-105.5,79.3,-99.7,77.9,-105.5,79.3,-123.5,48.5, + -128.4,50.8,-123.5,48.5,-121.5,74.4,-115.5,73.5,-123.1,70.9,-125.9,71.9, + -123.9,73.7,-124.9,74.3,-121.5,74.4,-107.8,75.8,-105.7,75.5,-117.7,75.2, + -115.4,76.5,-107.8,75.8,-106.5,73.1,-101.1,69.6,-113.3,68.5,-117.3,70.0, + -112.4,70.4,-119.4,71.6,-115.2,73.3,-108.2,71.7,-108.4,73.1,-106.5,73.1, + -100.4,72.7,-101.5,73.4,-97.4,73.8,-96.5,72.6,-98.4,71.3,-102.5,72.5, + -100.4,72.7,-106.6,73.6,-104.5,73.4,-106.6,73.6,-98.5,76.7,-98.2,75.0, + -102.5,75.6,-98.5,76.7,-96.0,80.6,-92.4,81.3,-85.8,79.3,-92.9,78.3, + -96.0,80.6,-91.6,81.9,-61.8,82.6,-76.9,79.3,-75.4,78.5,-80.6,76.2, + -89.5,76.5,-88.3,77.9,-85.0,77.5,-88.0,78.4,-85.1,79.3,-86.9,80.3, + -81.8,80.5,-91.6,81.9,-75.2,67.4,-77.2,67.6,-75.2,67.4,-96.3,69.5, + -99.8,69.4,-96.3,69.5,-64.5,49.9,-61.8,49.1,-64.5,49.9,-64.0,47.0, + -62.0,46.4,-64.0,47.0,27.4,5.2,22.4,4.0,19.5,5.0,16.0,2.3, + 14.5,5.5,15.3,7.4,22.9,11.1,27.4,5.2,23.8,19.6,23.9,15.6, + 21.9,12.6,22.9,11.1,15.3,7.4,13.5,14.4,15.9,20.4,14.9,22.9, + 23.8,19.6,-68.6,-52.6,-68.6,-54.9,-67.0,-54.9,-68.1,-55.6,-74.7,-52.8, + -71.1,-54.1,-68.6,-52.6,-69.6,-17.6,-67.0,-23.0,-70.5,-31.4,-69.8,-34.2, + -72.1,-42.3,-71.2,-44.8,-73.4,-49.3,-71.9,-52.0,-68.6,-52.3,-71.4,-53.9, + -74.9,-52.3,-75.6,-48.7,-74.1,-46.9,-75.6,-46.6,-72.7,-42.4,-74.3,-43.2, + -69.6,-17.6,109.5,18.2,108.6,19.4,110.8,20.1,109.5,18.2,80.3,42.3, + 80.0,44.9,87.8,49.3,91.0,46.9,90.9,45.3,96.3,42.7,109.2,42.5, + 111.9,45.1,119.7,46.7,115.5,48.1,122.2,53.4,125.9,52.8,131.0,47.8, + 135.0,48.5,133.1,45.1,131.0,45.0,130.6,42.4,121.1,38.9,121.6,40.9, + 117.5,38.7,122.4,37.5,119.2,34.9,121.9,31.7,121.7,28.2,118.7,24.5, + 110.4,20.3,105.3,23.4,101.7,22.3,101.8,21.2,99.2,22.1,97.6,23.9, + 98.7,27.5,96.1,29.5,88.8,27.3,78.7,31.5,78.9,34.3,73.7,39.4, + 80.3,42.3,-66.9,1.3,-69.8,1.7,-69.9,-4.3,-70.0,-2.7,-77.4,0.4, + -79.0,1.7,-77.1,3.8,-77.5,8.5,-71.4,12.4,-73.3,9.2,-72.0,7.0, + -67.3,6.1,-66.9,1.3,18.5,3.5,16.0,-3.5,11.9,-5.0,11.5,-2.8, + 14.4,-1.3,13.1,2.3,15.9,1.7,18.5,3.5,-82.5,9.6,-83.0,8.2, + -85.9,10.9,-82.5,9.6,-8.0,10.2,-2.8,9.6,-2.9,5.0,-7.7,4.4, + -8.0,10.2,16.6,46.5,19.4,45.2,15.8,44.8,18.5,42.5,13.7,45.1, + 16.6,46.5,-82.3,23.2,-74.2,20.3,-77.8,19.9,-81.8,22.6,-85.0,21.9, + -82.3,23.2,32.7,35.1,34.0,35.0,32.7,35.1,15.0,51.1,18.9,49.5, + 12.5,49.5,15.0,51.1,29.3,-4.5,30.7,-8.3,28.7,-8.5,28.4,-11.8, + 29.7,-13.3,22.2,-11.1,21.7,-7.3,17.5,-8.1,16.3,-5.9,12.2,-5.8, + 16.0,-3.5,19.5,5.0,29.7,4.6,31.2,2.2,29.3,-4.5,9.9,55.0, + 8.1,56.5,10.6,57.7,9.9,55.0,12.4,56.1,12.1,54.8,11.0,55.4, + 12.4,56.1,42.4,12.5,42.8,10.9,42.4,12.5,-71.7,18.0,-71.6,19.9, + -68.3,18.6,-71.7,18.0,-75.4,-0.2,-78.6,-4.5,-80.4,-4.4,-80.1,0.8, + -75.4,-0.2,36.9,22.0,25.0,22.0,25.2,31.6,34.3,31.2,34.2,27.8, + 32.3,29.8,36.9,22.0,-89.4,14.4,-87.9,13.1,-90.1,13.7,-89.4,14.4, + 9.6,2.3,11.3,1.1,9.5,1.0,9.6,2.3,36.4,14.4,38.4,18.0, + 43.1,12.7,36.4,14.4,28.0,59.5,27.3,57.5,23.3,59.2,28.0,59.5, + 32.1,-26.7,31.0,-25.7,32.1,-26.7,47.8,8.0,45.0,5.0,39.6,3.4, + 36.2,4.4,33.0,7.8,37.9,15.0,41.6,13.5,43.7,9.2,47.8,8.0, + -61.2,-51.8,-57.7,-51.5,-61.2,-51.8,28.6,69.1,31.1,62.4,28.1,60.5, + 21.3,60.7,21.5,63.2,25.4,65.1,20.6,69.1,24.7,68.6,27.7,70.2, + 28.6,69.1,68.9,-48.6,70.6,-49.3,68.7,-49.8,68.9,-48.6,-51.7,4.2, + -52.9,2.1,-54.5,2.3,-54.0,5.8,-51.7,4.2,6.2,49.5,8.1,49.0, + 6.0,46.7,7.4,43.7,1.8,42.3,-1.9,43.4,-1.2,46.0,-4.6,48.7, + -1.6,48.6,-1.9,49.8,2.5,51.1,6.2,49.5,8.7,42.6,9.2,41.4, + 8.7,42.6,11.3,2.3,14.3,1.2,14.4,-1.3,11.1,-4.0,8.8,-1.1, + 11.3,2.3,-16.7,13.6,-13.8,13.5,-16.7,13.6,40.0,43.4,46.6,41.2, + 41.6,41.5,40.0,43.4,14.1,53.8,15.0,51.1,12.2,50.3,12.9,47.5, + 7.5,47.6,8.1,49.0,6.0,50.1,7.1,53.7,9.9,55.0,14.1,53.8, + 0.0,11.0,1.1,5.9,-2.9,5.0,-2.9,11.0,0.0,11.0,26.3,35.3, + 23.5,35.3,26.3,35.3,23.0,41.3,26.6,41.6,22.6,40.3,24.0,37.7, + 22.5,36.4,20.2,39.6,23.0,41.3,-46.8,82.6,-27.1,83.5,-20.8,82.7, + -31.9,82.2,-12.2,81.3,-20.0,80.2,-17.7,80.1,-19.7,78.8,-18.5,77.0, + -21.7,76.6,-19.4,74.3,-24.8,72.3,-21.8,70.7,-25.5,71.4,-26.4,70.2, + -22.3,70.1,-39.8,65.5,-43.4,60.1,-48.3,60.9,-51.6,63.6,-54.0,67.2, + -50.9,69.9,-54.7,69.6,-54.4,70.8,-51.4,70.6,-55.8,71.7,-54.7,72.6, + -58.6,75.5,-68.5,76.1,-71.4,77.0,-66.8,77.4,-73.3,78.0,-65.7,79.4, + -68.0,80.1,-62.7,81.8,-44.5,81.7,-46.8,82.6,-92.2,14.5,-90.5,16.1, + -91.0,17.8,-89.1,17.8,-88.2,15.7,-89.4,14.4,-92.2,14.5,-13.7,12.6, + -9.1,12.3,-8.3,7.7,-11.1,10.0,-13.2,8.9,-15.1,11.0,-13.7,12.6, + -16.7,12.4,-13.7,11.8,-15.1,11.0,-16.7,12.4,-56.5,1.9,-59.6,1.8, + -61.4,6.0,-59.8,8.4,-57.1,6.0,-58.0,4.1,-56.5,1.9,-71.7,19.7, + -71.7,18.0,-74.5,18.3,-71.7,19.7,-83.1,15.0,-87.3,13.0,-89.4,14.4, + -87.9,15.9,-83.1,15.0,22.1,48.4,21.0,46.3,16.2,46.9,17.0,48.1, + 22.1,48.4,-14.5,66.5,-13.6,65.1,-18.7,63.5,-22.8,64.0,-21.8,64.4, + -24.0,64.9,-22.2,65.4,-24.3,65.6,-14.5,66.5,97.3,28.3,92.7,22.0, + 91.2,23.5,92.4,25.0,88.6,26.4,88.9,21.7,80.3,15.9,79.9,10.4, + 77.5,8.0,72.6,21.4,70.5,20.9,68.2,23.7,71.0,24.4,69.5,26.9, + 75.3,32.3,73.7,34.3,77.8,35.5,78.7,31.5,81.1,30.2,80.1,28.8, + 83.3,27.4,88.1,26.4,88.7,28.1,92.0,26.8,96.1,29.5,97.3,28.3, + 141.0,-2.6,141.0,-9.1,137.6,-8.4,137.9,-5.4,133.0,-4.1,132.0,-2.8, + 133.7,-2.2,130.5,-0.9,134.0,-0.8,135.5,-3.4,137.4,-1.7,141.0,-2.6, + 125.0,-8.9,123.5,-10.2,125.0,-8.9,117.9,4.1,119.0,0.9,116.1,-4.0, + 110.2,-2.9,109.7,2.0,110.5,0.8,113.8,1.2,115.9,4.3,117.9,4.1, + 129.4,-2.8,130.8,-3.9,127.9,-3.4,129.4,-2.8,127.9,2.2,128.1,-0.9, + 127.9,2.2,122.9,0.9,125.2,1.4,120.0,-0.5,123.3,-0.6,121.5,-1.9, + 123.2,-5.3,121.0,-2.6,119.8,-5.7,118.8,-2.8,119.8,0.2,122.9,0.9, + 120.3,-10.3,119.0,-9.6,120.3,-10.3,121.3,-8.5,122.9,-8.1,119.9,-8.8, + 121.3,-8.5,118.3,-8.4,116.7,-9.0,118.3,-8.4,108.5,-6.4,115.7,-8.4, + 105.4,-6.9,108.5,-6.4,104.4,-1.1,106.1,-3.1,105.8,-5.9,102.6,-4.2, + 95.3,5.5,97.5,5.2,104.4,-1.1,48.6,29.9,45.4,34.0,46.1,35.7, + 44.1,39.4,48.1,39.6,50.8,36.9,56.6,38.1,61.1,36.5,60.9,29.8, + 63.3,26.8,61.5,25.1,57.4,25.7,48.6,29.9,39.2,32.2,41.3,36.4, + 44.8,37.2,48.6,29.9,44.7,29.2,39.2,32.2,-6.2,53.9,-6.8,52.3, + -10.0,51.8,-7.6,55.1,-6.2,53.9,35.7,32.7,34.9,29.5,34.3,31.2, + 35.7,32.7,10.4,46.9,13.8,46.5,12.6,44.1,18.3,39.8,16.9,40.4, + 15.7,37.9,15.4,40.0,10.2,43.9,7.4,43.7,6.8,46.0,10.4,46.9, + 14.8,38.1,15.1,36.6,12.4,37.6,14.8,38.1,8.7,40.9,9.8,40.5, + 8.8,38.9,8.7,40.9,-77.6,18.5,-76.2,17.9,-77.6,18.5,141.9,39.2, + 140.3,35.1,135.8,33.5,135.1,34.6,131.0,33.9,132.0,33.1,130.2,31.4, + 129.4,33.3,139.4,38.2,140.3,41.2,141.9,39.2,144.6,44.0,145.5,43.3, + 140.0,41.6,142.0,45.6,144.6,44.0,132.4,33.5,134.8,33.8,132.4,33.5, + 35.5,32.4,38.8,33.4,39.2,32.2,37.0,31.5,38.0,30.5,36.1,29.2, + 34.9,29.5,35.5,32.4,87.4,49.2,80.0,44.9,80.3,42.3,74.2,43.3, + 68.6,40.7,64.9,43.7,62.0,43.5,58.5,45.6,55.9,45.0,56.0,41.3, + 52.5,41.8,50.3,44.6,53.0,45.3,53.0,46.9,49.1,46.4,46.5,48.4, + 50.8,51.7,61.3,50.8,60.0,52.0,61.4,54.0,69.1,55.4,73.4,53.5, + 76.9,54.5,80.0,50.9,87.4,49.2,39.2,-4.7,33.9,-0.9,35.3,5.5, + 38.1,3.6,41.9,3.9,41.6,-1.7,39.2,-4.7,20.6,41.9,20.6,43.2, + 21.8,42.7,20.6,41.9,48.0,30.0,48.4,28.6,46.6,29.1,48.0,30.0, + 71.0,42.3,74.2,43.3,80.3,42.3,73.7,39.4,69.5,39.5,73.1,40.9, + 71.0,42.3,107.4,14.2,105.2,14.3,104.0,18.2,101.1,17.5,100.1,20.4, + 101.7,22.3,104.4,20.8,103.9,19.3,107.4,14.2,27.3,57.5,28.2,56.2, + 26.5,55.6,21.1,56.0,22.5,57.8,27.3,57.5,35.8,33.3,36.4,34.6, + 35.8,33.3,29.0,-29.0,28.1,-30.5,27.0,-29.9,29.0,-29.0,-8.4,7.7, + -7.7,4.4,-11.4,6.8,-10.2,8.4,-8.4,7.7,25.0,22.0,23.8,19.6, + 10.3,24.4,10.0,31.4,11.5,33.1,19.1,30.3,20.9,32.7,24.9,31.9, + 25.0,22.0,26.5,55.6,23.5,53.9,21.1,56.0,26.5,55.6,49.5,-12.5, + 50.4,-15.7,47.1,-24.9,45.4,-25.6,43.3,-22.8,44.0,-17.4,49.5,-12.5, + 32.8,-9.2,35.7,-14.6,35.0,-16.8,32.7,-13.7,32.8,-9.2,100.1,6.5, + 103.0,5.5,104.2,1.3,101.4,2.8,100.1,6.5,117.9,4.1,115.9,4.3, + 114.6,1.4,109.8,1.3,115.3,4.3,116.7,6.9,119.2,5.4,117.9,4.1, + -11.5,12.4,-11.7,15.4,-5.5,15.5,-6.5,25.0,4.3,19.2,3.6,15.6, + -4.0,13.5,-5.4,10.4,-11.5,12.4,-17.1,21.0,-12.9,21.3,-12.0,25.9, + -8.7,25.9,-8.7,27.4,-4.9,25.0,-6.5,25.0,-5.5,15.5,-12.2,14.6, + -14.6,16.6,-16.5,16.1,-17.1,21.0,-117.1,32.5,-106.5,31.8,-103.9,29.3, + -101.7,29.8,-97.1,25.9,-97.9,22.4,-95.9,18.8,-91.4,18.9,-90.3,21.0, + -87.1,21.5,-87.8,18.3,-91.0,17.8,-90.5,16.1,-92.2,14.5,-103.5,18.3, + -113.1,31.2,-114.9,31.4,-109.9,22.8,-115.1,27.7,-114.2,28.6,-117.1,32.5, + 26.6,48.2,30.0,46.4,28.2,45.5,26.6,48.2,87.8,49.3,92.2,50.8, + 97.3,49.7,98.9,52.0,108.5,49.3,116.7,49.9,115.7,47.7,119.8,47.0, + 105.0,41.6,96.3,42.7,90.9,45.3,91.0,46.9,87.8,49.3,20.1,42.6, + 18.5,42.5,20.1,42.6,-2.2,35.2,-1.3,32.3,-8.7,28.8,-8.8,27.1, + -11.4,26.9,-14.8,21.5,-17.0,21.4,-14.4,26.3,-9.6,29.9,-8.7,33.2, + -5.9,35.8,-2.2,35.2,34.6,-11.5,40.3,-10.3,40.8,-14.7,34.8,-19.8, + 35.5,-24.1,32.1,-26.7,31.2,-22.3,32.8,-16.7,30.2,-14.8,33.2,-14.0, + 35.0,-16.8,34.6,-11.5,100.1,20.4,97.4,18.4,99.6,11.9,98.6,9.9, + 97.2,16.9,94.2,16.0,92.3,21.5,97.3,28.3,98.7,27.5,97.6,23.9, + 101.2,21.8,100.1,20.4,32.7,35.1,34.6,35.7,32.7,35.1,19.9,-24.8, + 19.9,-28.5,16.3,-28.6,11.7,-17.3,25.1,-17.6,20.9,-18.3,19.9,-24.8, + 88.1,27.9,87.2,26.4,80.1,28.8,81.5,30.4,88.1,27.9,6.9,53.5, + 6.2,50.8,3.3,51.3,6.9,53.5,165.8,-21.1,167.1,-22.2,164.0,-20.1, + 165.8,-21.1,176.9,-40.1,174.7,-41.3,174.7,-37.4,172.6,-34.5,176.0,-37.6, + 178.5,-37.7,176.9,-40.1,169.7,-43.6,172.8,-40.5,174.2,-41.3,170.6,-45.9, + 166.7,-46.2,169.7,-43.6,-83.7,10.9,-87.7,12.9,-83.1,15.0,-83.7,10.9, + 14.9,22.9,15.9,20.4,13.5,14.4,14.2,12.5,5.4,13.9,3.6,11.7, + 1.0,12.9,0.4,14.9,3.6,15.6,4.3,19.2,12.0,23.5,14.9,22.9, + 2.7,6.3,4.4,13.7,13.1,13.6,14.6,12.1,11.7,7.0,8.5,4.8, + 5.9,4.3,2.7,6.3,130.6,42.4,127.5,39.8,128.2,38.4,124.7,38.1, + 125.1,40.6,130.6,42.4,22.4,42.3,23.0,41.3,20.6,41.1,22.4,42.3, + 15.1,79.7,21.5,79.0,15.9,76.8,10.4,79.7,15.1,79.7,31.1,69.6, + 18.0,68.6,12.6,64.1,11.0,58.9,5.7,58.6,5.0,62.0,19.2,69.8, + 28.2,71.2,31.1,69.6,27.4,80.1,17.4,80.3,27.4,80.1,24.7,77.9, + 20.7,77.7,24.7,77.9,55.2,22.7,56.4,24.9,59.8,22.3,57.7,18.9, + 53.1,16.7,52.0,19.0,55.0,20.0,55.2,22.7,77.8,35.5,73.7,34.3, + 75.3,32.3,69.5,26.9,71.0,24.4,61.5,25.1,63.3,26.8,60.9,29.8, + 66.3,29.9,71.8,36.5,75.2,37.1,77.8,35.5,-77.4,8.7,-77.9,7.2, + -79.1,9.0,-80.9,7.2,-82.9,9.5,-77.4,8.7,141.0,-2.6,147.6,-6.1, + 147.2,-7.4,150.7,-10.6,144.7,-7.6,141.0,-9.1,141.0,-2.6,152.6,-3.7, + 152.8,-4.8,150.7,-2.7,152.6,-3.7,151.3,-5.8,148.3,-5.7,152.1,-4.1, + 151.3,-5.8,154.8,-5.3,155.9,-6.8,154.8,-5.3,-58.2,-20.2,-57.9,-22.1, + -54.3,-24.0,-55.7,-27.4,-58.6,-27.1,-57.8,-25.2,-62.7,-22.2,-61.8,-19.6, + -58.2,-20.2,-69.9,-4.3,-72.9,-5.3,-74.0,-7.5,-68.7,-12.6,-70.4,-18.3, + -76.0,-14.6,-81.4,-4.7,-80.3,-3.4,-78.6,-4.5,-75.1,-0.1,-73.1,-2.3, + -70.0,-2.7,-69.9,-4.3,122.6,10.0,124.1,11.2,123.0,9.0,122.6,10.0, + 126.4,8.4,125.4,5.6,123.6,7.8,121.9,7.2,125.4,9.8,126.4,8.4, + 118.5,9.3,117.2,8.4,119.5,11.4,118.5,9.3,122.3,18.2,121.7,14.3, + 124.1,12.5,119.9,15.4,120.7,18.5,122.3,18.2,125.5,12.2,124.8,10.1, + 124.3,12.6,125.5,12.2,23.5,53.9,24.0,50.7,22.8,49.0,16.2,50.4, + 14.1,53.0,17.6,54.9,23.5,53.9,-9.0,41.9,-6.4,41.4,-7.9,36.8, + -9.5,38.7,-9.0,41.9,-66.3,18.5,-67.2,17.9,-66.3,18.5,50.8,24.8, + 51.3,26.1,50.8,24.8,28.2,45.5,29.6,45.3,28.6,43.7,22.9,43.8, + 20.2,46.1,26.6,48.2,28.2,45.5,49.1,46.4,46.7,44.6,47.8,41.2, + 36.7,45.2,40.1,49.6,31.8,52.1,32.7,53.4,30.9,55.6,27.3,57.5, + 29.1,60.0,28.1,60.5,31.5,62.9,30.0,63.6,28.6,69.1,32.1,69.9, + 41.1,67.5,38.4,66.0,33.2,66.6,37.0,63.8,37.2,65.1,43.9,66.1, + 43.5,68.6,46.3,68.3,46.3,66.7,53.7,68.9,59.9,68.3,60.6,69.9, + 68.5,68.1,66.7,71.0,69.9,73.0,72.8,72.2,71.8,71.4,73.7,68.4, + 71.3,66.3,72.4,66.2,75.1,67.8,73.1,71.4,74.7,72.8,76.4,71.2, + 81.5,71.8,80.5,73.6,104.4,77.7,114.1,75.8,109.4,74.2,127.0,73.6, + 131.3,70.8,139.9,71.5,139.1,72.4,140.5,72.8,159.0,70.9,160.9,69.4, + 180.0,69.0,180.0,65.0,177.4,64.6,179.2,62.3,170.3,59.9,163.5,59.9, + 162.0,58.2,163.2,57.6,162.1,54.9,156.8,51.0,155.9,56.8,164.5,62.6, + 160.1,60.5,156.7,61.4,154.2,59.8,155.0,59.1,142.2,59.0,135.1,54.7, + 141.3,53.1,140.1,48.4,134.9,43.4,130.8,42.2,131.0,45.0,133.1,45.1, + 135.0,48.5,131.0,47.8,123.6,53.5,120.2,52.8,117.9,49.5,108.5,49.3, + 98.9,52.0,97.3,49.7,92.2,50.8,87.4,49.2,80.0,50.9,76.9,54.5, + 73.4,53.5,69.1,55.4,61.4,54.0,60.0,52.0,61.3,50.8,50.8,51.7, + 47.5,50.5,46.5,48.4,49.1,46.4,93.8,81.0,100.2,79.8,97.8,78.8, + 91.2,80.3,93.8,81.0,102.8,79.3,105.4,78.7,99.4,77.9,102.8,79.3, + 138.8,76.1,145.1,75.6,137.0,75.3,138.8,76.1,148.2,75.3,150.7,75.1, + 146.1,75.2,148.2,75.3,139.9,73.4,143.6,73.2,139.9,73.4,44.8,80.6, + 51.5,80.7,44.8,80.6,22.7,54.3,19.7,54.4,22.7,54.3,53.5,73.7, + 61.2,76.3,68.9,76.5,58.5,74.3,55.4,72.4,57.5,70.7,51.6,71.5, + 53.5,73.7,142.9,53.7,144.7,49.0,143.2,49.3,143.5,46.1,142.1,46.0, + 141.7,53.3,142.9,53.7,-174.9,67.2,-169.9,66.0,-173.0,64.3,-178.7,66.1, + -180.0,65.0,-180.0,69.0,-174.9,67.2,-178.7,70.9,-180.0,71.5,-177.6,71.3, + -178.7,70.9,33.4,46.0,36.5,45.5,33.9,44.4,32.5,45.3,33.4,46.0, + 30.4,-1.1,29.0,-2.8,30.4,-1.1,30.8,3.5,23.9,8.6,25.8,10.4, + 31.4,9.8,33.2,12.2,33.0,7.8,35.3,5.5,30.8,3.5,35.0,29.4, + 39.2,32.2,47.5,29.0,52.0,23.0,55.2,22.7,55.0,20.0,47.0,16.9, + 43.4,17.6,42.8,16.3,35.0,29.4,-16.7,13.6,-17.6,14.7,-14.6,16.6, + -11.5,12.4,-16.7,12.4,-13.8,13.5,-16.7,13.6,18.8,45.9,22.7,44.6, + 22.5,42.5,19.2,43.5,18.8,45.9,-13.2,8.9,-11.1,10.0,-10.2,8.4, + -11.4,6.8,-13.2,8.9,22.6,49.1,16.9,48.5,22.6,49.1,13.8,46.5, + 16.6,46.5,15.3,45.5,13.8,46.5,159.6,-8.0,158.2,-7.4,159.6,-8.0, + 41.6,-1.7,41.0,2.8,45.0,5.0,48.9,9.5,48.9,11.4,51.1,12.0, + 48.6,5.3,41.6,-1.7,48.9,11.4,47.8,8.0,42.6,10.6,48.9,11.4, + 16.3,-28.6,19.9,-28.5,19.9,-24.8,21.6,-26.7,25.7,-25.5,29.4,-22.1, + 31.2,-22.3,31.9,-24.4,30.7,-26.7,32.8,-26.7,28.2,-32.8,20.1,-34.8, + 18.4,-34.1,16.3,-28.6,126.2,37.7,128.3,38.6,129.1,35.1,126.5,34.4, + 126.2,37.7,-7.5,37.1,-6.4,41.4,-9.4,43.0,3.0,42.5,-2.1,36.7, + -7.5,37.1,81.8,7.5,80.3,6.0,80.1,9.8,81.8,7.5,24.6,8.2, + 21.9,12.6,25.0,22.0,36.9,22.0,38.4,18.0,34.0,8.7,32.7,12.2, + 31.4,9.8,25.1,10.3,24.6,8.2,-54.5,2.3,-56.5,1.9,-57.6,3.3, + -57.1,6.0,-54.0,5.8,-54.5,2.3,11.0,58.9,12.6,61.3,11.9,63.1, + 16.8,68.0,20.6,69.1,23.5,67.9,23.9,66.0,17.8,62.7,17.1,61.3, + 18.8,60.1,15.9,56.1,12.9,55.4,11.0,58.9,9.6,47.5,10.4,46.5, + 6.0,46.3,9.6,47.5,35.7,32.7,36.7,36.8,42.3,37.2,41.0,34.4, + 35.7,32.7,121.8,24.4,120.7,22.0,120.1,23.6,121.8,24.4,67.8,37.1, + 67.7,39.6,70.7,41.0,69.5,39.5,73.7,39.4,75.0,37.4,71.8,36.7, + 70.8,38.5,67.8,37.1,33.9,-0.9,39.2,-4.7,39.5,-10.9,34.6,-11.5, + 29.6,-6.5,30.4,-1.1,33.9,-0.9,105.2,14.3,103.0,14.2,102.6,12.2, + 100.1,13.4,99.2,9.2,102.1,6.2,101.2,5.7,98.2,8.4,99.6,11.9, + 97.4,18.4,100.1,20.4,101.1,17.5,104.7,17.4,105.2,14.3,125.0,-8.9, + 127.3,-8.4,125.0,-8.9,0.9,11.0,1.1,5.9,0.9,11.0,9.5,30.3, + 7.5,34.1,9.5,37.3,11.0,37.1,10.1,34.3,11.5,33.1,9.5,30.3, + 44.8,37.2,29.7,36.1,27.6,36.7,26.2,39.5,33.5,42.0,42.6,41.6, + 44.8,39.7,44.8,37.2,26.1,41.8,29.0,41.3,26.4,40.2,26.1,41.8, + 52.5,41.8,57.1,41.3,58.6,42.8,66.5,37.4,62.2,35.3,57.3,38.0, + 53.9,37.2,52.7,40.0,54.7,41.0,52.5,41.8,33.9,-0.9,29.6,-1.3, + 31.2,3.8,34.5,3.6,33.9,-0.9,31.8,52.1,40.1,49.6,39.7,47.9, + 35.0,45.7,31.7,46.7,28.7,45.3,30.0,46.4,28.7,48.1,22.1,48.4, + 23.5,51.6,31.8,52.1,51.6,24.2,56.3,25.7,55.0,22.5,51.6,24.2, + -6.2,53.9,-7.6,55.1,-6.2,53.9,-3.1,53.4,-6.1,56.8,-5.0,58.6, + -2.0,57.7,-3.1,56.0,1.7,52.7,1.4,51.3,-5.8,50.2,-3.4,51.4, + -5.3,52.0,-4.6,53.5,-3.1,53.4,-122.8,49.0,-88.4,48.3,-82.6,45.3, + -82.7,41.7,-71.5,45.0,-69.2,47.4,-67.0,44.8,-70.1,43.7,-70.0,41.6, + -75.5,39.5,-75.9,37.2,-76.3,39.2,-77.0,38.2,-75.7,35.6,-81.3,31.4, + -80.4,25.2,-83.7,29.9,-86.4,30.4,-94.7,29.5,-97.5,25.8,-101.0,29.4, + -103.9,29.3,-106.5,31.8,-117.1,32.5,-120.6,34.6,-124.4,40.3,-124.7,48.2, + -122.6,47.1,-122.8,49.0,-166.5,60.4,-165.6,59.9,-167.5,60.2,-166.5,60.4, + -153.2,58.0,-152.1,57.6,-154.5,57.0,-153.2,58.0,-141.0,69.7,-141.0,60.3, + -137.5,58.9,-135.5,59.8,-130.0,55.9,-130.5,54.8,-134.1,58.1,-139.9,59.5, + -147.1,60.9,-151.7,59.2,-150.6,61.3,-158.4,56.0,-164.9,54.6,-157.0,58.9, + -162.0,58.7,-165.3,60.5,-165.7,62.1,-160.8,64.8,-168.1,65.7,-161.7,66.1, + -166.2,68.9,-156.6,71.4,-141.0,69.7,-171.7,63.8,-168.7,63.3,-171.7,63.8, + -57.6,-30.2,-53.8,-32.0,-53.8,-34.4,-58.4,-33.9,-57.6,-30.2,56.0,41.3, + 55.9,45.0,58.5,45.6,62.0,43.5,64.9,43.7,68.3,40.7,71.0,42.3, + 73.1,40.9,67.7,39.6,67.8,37.1,58.6,42.8,56.0,41.3,-60.7,5.2, + -64.8,4.1,-63.4,2.2,-66.3,0.7,-67.8,2.8,-67.3,6.1,-72.0,7.0, + -72.9,10.5,-71.3,11.8,-71.3,9.1,-69.9,12.2,-68.2,10.6,-61.9,10.7, + -59.8,8.4,-60.7,5.2,104.3,10.5,107.5,12.3,107.6,15.2,102.2,22.5, + 105.3,23.4,108.1,21.6,105.7,19.1,108.9,15.3,109.2,11.7,105.2,8.6, + 104.3,10.5,-8.7,27.7,-8.7,25.9,-12.0,25.9,-12.9,21.3,-17.1,21.0, + -14.8,21.5,-11.4,26.9,-8.7,27.7,52.0,19.0,52.2,15.6,43.5,12.6, + 43.4,17.6,47.0,16.9,52.0,19.0,30.7,-8.3,33.2,-9.7,33.2,-14.0, + 27.0,-17.9,23.2,-17.5,21.9,-12.9,24.0,-12.9,23.9,-10.9,29.7,-13.3, + 28.4,-9.2,30.7,-8.3,31.2,-22.3,28.0,-21.5,25.3,-17.7,30.3,-15.5, + 32.8,-16.7,31.2,-22.3, +}; + +struct Span { + int at, length; +}; + +const Span RINGS[] = { + {0,20},{20,8},{28,22},{50,24},{74,10},{84,6},{90,10},{100,6}, + {106,6},{112,6},{118,6},{124,122},{246,8},{254,48},{302,8},{310,10}, + {320,48},{368,12},{380,12},{392,6},{398,16},{414,14},{428,8},{436,8}, + {444,8},{452,6},{458,22},{480,8},{488,14},{502,70},{572,10},{582,16}, + {598,8},{606,12},{618,14},{632,116},{748,6},{754,8},{762,6},{768,6}, + {774,8},{782,8},{790,8},{798,8},{806,12},{818,10},{828,42},{870,10}, + {880,6},{886,6},{892,6},{898,6},{904,14},{918,10},{928,20},{948,14}, + {962,6},{968,8},{976,10},{986,26},{1012,6},{1018,6},{1024,6},{1030,6}, + {1036,16},{1052,18},{1070,14},{1084,34},{1118,8},{1126,76},{1202,26},{1228,16}, + {1244,8},{1252,10},{1262,12},{1274,12},{1286,6},{1292,8},{1300,30},{1330,8}, + {1338,8},{1346,6},{1352,8},{1360,10},{1370,14},{1384,8},{1392,8},{1400,8}, + {1408,8},{1416,6},{1422,18},{1440,6},{1446,20},{1466,8},{1474,10},{1484,24}, + {1508,6},{1514,12},{1526,6},{1532,8},{1540,20},{1560,10},{1570,6},{1576,14}, + {1590,74},{1664,14},{1678,14},{1692,8},{1700,14},{1714,8},{1722,10},{1732,10}, + {1742,18},{1760,52},{1812,24},{1836,6},{1842,18},{1860,8},{1868,6},{1874,22}, + {1896,6},{1902,8},{1910,6},{1916,8},{1924,14},{1938,26},{1964,12},{1976,10}, + {1986,8},{1994,22},{2016,8},{2024,8},{2032,6},{2038,22},{2060,10},{2070,6}, + {2076,16},{2092,50},{2142,14},{2156,8},{2164,8},{2172,14},{2186,18},{2204,12}, + {2216,6},{2222,8},{2230,10},{2240,18},{2258,8},{2266,14},{2280,10},{2290,10}, + {2300,16},{2316,18},{2334,24},{2358,42},{2400,8},{2408,26},{2434,6},{2440,24}, + {2464,24},{2488,24},{2512,6},{2518,14},{2532,10},{2542,8},{2550,8},{2558,14}, + {2572,12},{2584,8},{2592,24},{2616,16},{2632,12},{2644,8},{2652,10},{2662,18}, + {2680,6},{2686,6},{2692,16},{2708,24},{2732,12},{2744,14},{2758,8},{2766,8}, + {2774,6},{2780,18},{2798,26},{2824,8},{2832,12},{2844,8},{2852,12},{2864,8}, + {2872,14},{2886,10},{2896,6},{2902,6},{2908,14},{2922,192},{3114,10},{3124,8}, + {3132,8},{3140,8},{3148,6},{3154,6},{3160,6},{3166,16},{3182,14},{3196,14}, + {3210,8},{3218,10},{3228,6},{3234,16},{3250,20},{3270,14},{3284,10},{3294,10}, + {3304,6},{3310,8},{3318,6},{3324,16},{3340,8},{3348,28},{3376,10},{3386,12}, + {3398,8},{3406,20},{3426,12},{3438,26},{3464,8},{3472,10},{3482,8},{3490,18}, + {3508,14},{3522,28},{3550,6},{3556,6},{3562,14},{3576,16},{3592,8},{3600,20}, + {3620,10},{3630,22},{3652,8},{3660,6},{3666,24},{3690,58},{3748,8},{3756,8}, + {3764,46},{3810,6},{3816,10},{3826,24},{3850,30},{3880,22},{3902,16},{3918,12}, + {3930,22},{3952,12}, +}; + +const Span COUNTRIES[] = { + {0,1},{1,1},{2,1},{3,1},{4,8},{12,2},{14,1},{15,2}, + {17,1},{18,1},{19,1},{20,1},{21,1},{22,1},{23,1},{24,1}, + {25,1},{26,1},{27,1},{28,1},{29,1},{30,1},{31,1},{32,1}, + {33,1},{34,1},{35,29},{64,1},{65,1},{66,2},{68,2},{70,1}, + {71,1},{72,1},{73,1},{74,1},{75,1},{76,1},{77,1},{78,1}, + {79,2},{81,1},{82,1},{83,1},{84,1},{85,1},{86,1},{87,1}, + {88,1},{89,1},{90,1},{91,1},{92,1},{93,1},{94,3},{97,1}, + {98,1},{99,1},{100,1},{101,1},{102,2},{104,1},{105,1},{106,1}, + {107,1},{108,1},{109,1},{110,1},{111,1},{112,1},{113,1},{114,11}, + {125,1},{126,1},{127,1},{128,1},{129,3},{132,1},{133,3},{136,1}, + {137,1},{138,1},{139,1},{140,1},{141,1},{142,1},{143,1},{144,1}, + {145,1},{146,1},{147,1},{148,1},{149,1},{150,1},{151,2},{153,1}, + {154,1},{155,1},{156,1},{157,1},{158,1},{159,1},{160,1},{161,1}, + {162,1},{163,1},{164,1},{165,1},{166,1},{167,2},{169,1},{170,1}, + {171,1},{172,1},{173,1},{174,4},{178,1},{179,1},{180,1},{181,4}, + {185,1},{186,1},{187,5},{192,1},{193,1},{194,1},{195,1},{196,1}, + {197,13},{210,1},{211,1},{212,1},{213,1},{214,1},{215,1},{216,1}, + {217,1},{218,1},{219,1},{220,1},{221,1},{222,1},{223,1},{224,1}, + {225,1},{226,1},{227,1},{228,1},{229,1},{230,1},{231,1},{232,1}, + {233,1},{234,1},{235,1},{236,1},{237,2},{239,1},{240,1},{241,1}, + {242,1},{243,2},{245,5},{250,1},{251,1},{252,1},{253,1},{254,1}, + {255,1},{256,1},{257,1}, +}; + +const char *const NAMES[] = { + "Afghanistan","Albania","Algeria","Angola", + "Antarctica","Argentina","Armenia","Australia", + "Austria","Azerbaijan","Bahamas","Bangladesh", + "Belarus","Belgium","Belize","Benin", + "Bhutan","Bolivia","Bosnia and Herz.","Botswana", + "Brazil","Bulgaria","Burkina Faso","Burundi", + "Cambodia","Cameroon","Canada","Central African Rep.", + "Chad","Chile","China","Colombia", + "Congo","Costa Rica","Côte d'Ivoire","Croatia", + "Cuba","Cyprus","Czechia","Dem. Rep. Congo", + "Denmark","Djibouti","Dominican Rep.","Ecuador", + "Egypt","El Salvador","Eq. Guinea","Eritrea", + "Estonia","eSwatini","Ethiopia","Falkland Is.", + "Finland","Fr. S. Antarctic Lands","France","Gabon", + "Gambia","Georgia","Germany","Ghana", + "Greece","Greenland","Guatemala","Guinea", + "Guinea-Bissau","Guyana","Haiti","Honduras", + "Hungary","Iceland","India","Indonesia", + "Iran","Iraq","Ireland","Israel", + "Italy","Jamaica","Japan","Jordan", + "Kazakhstan","Kenya","Kosovo","Kuwait", + "Kyrgyzstan","Laos","Latvia","Lebanon", + "Lesotho","Liberia","Libya","Lithuania", + "Madagascar","Malawi","Malaysia","Mali", + "Mauritania","Mexico","Moldova","Mongolia", + "Montenegro","Morocco","Mozambique","Myanmar", + "N. Cyprus","Namibia","Nepal","Netherlands", + "New Caledonia","New Zealand","Nicaragua","Niger", + "Nigeria","North Korea","North Macedonia","Norway", + "Oman","Pakistan","Panama","Papua New Guinea", + "Paraguay","Peru","Philippines","Poland", + "Portugal","Puerto Rico","Qatar","Romania", + "Russia","Rwanda","S. Sudan","Saudi Arabia", + "Senegal","Serbia","Sierra Leone","Slovakia", + "Slovenia","Solomon Is.","Somalia","Somaliland", + "South Africa","South Korea","Spain","Sri Lanka", + "Sudan","Suriname","Sweden","Switzerland", + "Syria","Taiwan","Tajikistan","Tanzania", + "Thailand","Timor-Leste","Togo","Tunisia", + "Turkey","Turkmenistan","Uganda","Ukraine", + "United Arab Emirates","United Kingdom","United States of America","Uruguay", + "Uzbekistan","Venezuela","Vietnam","W. Sahara", + "Yemen","Zambia","Zimbabwe", +}; + +const char *const ISO[] = { + "AF","AL","DZ","AO","AQ","AR","AM","AU","AT","AZ","BS","BD", + "BY","BE","BZ","BJ","BT","BO","BA","BW","BR","BG","BF","BI", + "KH","CM","CA","CF","TD","CL","CN","CO","CG","CR","CI","HR", + "CU","CY","CZ","CD","DK","DJ","DO","EC","EG","SV","GQ","ER", + "EE","SZ","ET","FK","FI","TF","FR","GA","GM","GE","DE","GH", + "GR","GL","GT","GN","GW","GY","HT","HN","HU","IS","IN","ID", + "IR","IQ","IE","IL","IT","JM","JP","JO","KZ","KE","XK","KW", + "KG","LA","LV","LB","LS","LR","LY","LT","MG","MW","MY","ML", + "MR","MX","MD","MN","ME","MA","MZ","MM","-99","NA","NP","NL", + "NC","NZ","NI","NE","NG","KP","MK","NO","OM","PK","PA","PG", + "PY","PE","PH","PL","PT","PR","QA","RO","RU","RW","SS","SA", + "SN","RS","SL","SK","SI","SB","SO","-99","ZA","KR","ES","LK", + "SD","SR","SE","CH","SY","TW","TJ","TZ","TH","TL","TG","TN", + "TR","TM","UG","UA","AE","GB","US","UY","UZ","VE","VN","EH", + "YE","ZM","ZW", +}; + +} // namespace + +/// Built once, on first use: the pool above is plain static data, and this +/// turns it into the shape the rest of the code wants without a static +/// initialiser that has to run before main. +const std::vector &world_countries() { + static const std::vector countries = [] { + std::vector out; + out.reserve(171); + for (std::size_t i = 0; i < 171; i++) { + CountryOutline c; + c.name = NAMES[i]; + c.iso = ISO[i]; + const Span &cs = COUNTRIES[i]; + for (int r = 0; r < cs.length; r++) { + const Span &rs = RINGS[cs.at + r]; + c.rings.emplace_back(POOL + rs.at, POOL + rs.at + rs.length); + } + out.push_back(std::move(c)); + } + return out; + }(); + return countries; +} + +} // namespace hqtui diff --git a/ports/cpp/tests/conformance_widgets.cpp b/ports/cpp/tests/conformance_widgets.cpp index d76bbc9..6557701 100644 --- a/ports/cpp/tests/conformance_widgets.cpp +++ b/ports/cpp/tests/conformance_widgets.cpp @@ -251,6 +251,23 @@ bool draw_scene(const std::string &name, Surface s) { draw_shadow(s, Rect{2, 1, 6, 2}, sh); return true; } + if (name == "world") { + draw_world_map(s, {}); + return true; + } + if (name == "world-zoom") { + WorldMap m; + m.x = Bounds{112, 156}; + m.y = Bounds{24, 50}; + draw_world_map(s, m); + return true; + } + if (name == "world-highlight") { + WorldMap m; + m.highlight = {"Brazil", "JP"}; + draw_world_map(s, m); + return true; + } if (name == "badge") { { Badge badge; diff --git a/ports/go/conformance_widgets_test.go b/ports/go/conformance_widgets_test.go index ff59e13..cc9c083 100644 --- a/ports/go/conformance_widgets_test.go +++ b/ports/go/conformance_widgets_test.go @@ -129,6 +129,13 @@ func drawWidgetScene(t *testing.T, name string, s Surface) { solid := RGB(0x10, 0x14, 0x18) DrawShadow(s, Rect{X: 2, Y: 1, Width: 6, Height: 2}, ShadowOptions{OffsetX: 1, OffsetY: 1, Color: &solid}) + case "world": + DrawWorldMap(s, WorldMapOptions{}) + case "world-zoom": + zx, zy := Bounds{112, 156}, Bounds{24, 50} + DrawWorldMap(s, WorldMapOptions{X: &zx, Y: &zy}) + case "world-highlight": + DrawWorldMap(s, WorldMapOptions{Highlight: []string{"Brazil", "JP"}}) case "badge": DrawBadge(s, BadgeOptions{Text: "LIVE"}) case "badge-outline": diff --git a/ports/go/examples/world-probe/main.go b/ports/go/examples/world-probe/main.go new file mode 100644 index 0000000..784ec61 --- /dev/null +++ b/ports/go/examples/world-probe/main.go @@ -0,0 +1,56 @@ +// Prints what the country lookup answers for a fixed set of points. +// +// The same probe exists for every port, so "the ports agree about the world" is +// a diff rather than a hope. +package main + +import ( + "fmt" + + hqtui "github.com/profullstack/hqtui/ports/go" +) + +func main() { + places := []struct { + name string + lon, lat float64 + }{ + {"Paris", 2.35, 48.86}, + {"Tokyo", 139.7, 35.7}, + {"Cairo", 31.2, 30.0}, + {"Brasilia", -47.9, -15.8}, + {"Canberra", 149.1, -35.3}, + {"Denver", -105.0, 39.7}, + {"Moscow", 37.6, 55.75}, + {"Delhi", 77.2, 28.6}, + {"Nairobi", 36.8, -1.3}, + {"Pacific", -140.0, 0.0}, + {"Atlantic", -30.0, 0.0}, + {"SouthernOcean", 80.0, -40.0}, + {"NorthPacific", -150.0, 40.0}, + } + for _, p := range places { + name := "-" + if c := hqtui.CountryAt(p.lon, p.lat); c != nil { + name = c.Name + } + fmt.Printf("%s %s\n", p.name, name) + } + + // The cell path, which has to agree with what the canvas drew. + cells := [][2]int{{173, 28}, {74, 2}, {20, 25}, {88, 7}} + for _, cell := range cells { + name := "-" + if c := hqtui.CountryAtCell(cell[0], cell[1], 200, 50, hqtui.WorldMapOptions{}); c != nil { + name = c.Name + } + fmt.Printf("cell:%d,%d %s\n", cell[0], cell[1], name) + } + + // And the projection itself, so a drift shows up as a number rather than as + // a country that happens to still be right. + for _, cell := range [][2]int{{0, 0}, {99, 25}, {50, 13}} { + lon, lat, _ := hqtui.DegreesAt(cell[0], cell[1], 100, 26, hqtui.WorldX, hqtui.WorldY) + fmt.Printf("degrees:%d,%d %.4f %.4f\n", cell[0], cell[1], lon, lat) + } +} diff --git a/ports/go/ui.go b/ports/go/ui.go index da9fc56..d59d074 100644 --- a/ports/go/ui.go +++ b/ports/go/ui.go @@ -523,6 +523,19 @@ func (c *Container) Shapes(o CanvasOptions, layout ...Layout) *Container { return c.add(c.filling(firstLayout(layout)), func(s Surface) { DrawCanvas(s, o) }) } +// WorldMap draws a world map and reports the country under whatever is clicked. +// +// The click is answered by turning the cell back into degrees and testing it +// against the outlines, so the answer is the country actually under the cursor. +// Bounding boxes would be cheaper and wrong: Russia's covers most of the +// northern hemisphere and Chile's covers Argentina. +func (c *Container) WorldMap(o WorldMapOptions, h ScrollHandlers, layout ...Layout) *Container { + return c.add(c.filling(firstLayout(layout)), func(s Surface) { + DrawWorldMap(s, o) + c.attachScroll(s, h, 0) + }) +} + func (c *Container) Sparkline(o SparklineWidgetOptions, layout ...Layout) *Container { return c.add(c.leaf(firstLayout(layout), 1), func(s Surface) { DrawSparkline(s, o) }) } diff --git a/ports/go/widgets_world.go b/ports/go/widgets_world.go new file mode 100644 index 0000000..c367f53 --- /dev/null +++ b/ports/go/widgets_world.go @@ -0,0 +1,71 @@ +package hqtui + +// A world map you can click. +// +// The drawing is the canvas doing what it already does — polylines in the +// caller's own coordinates, which for a map are degrees. What this adds is the +// other direction: turning a click back into a country. + +type WorldMapOptions struct { + // X and Y are the window on the globe. Nil means all of it. + X *Bounds + Y *Bounds + // Color is the coastline colour. + Color *Color + // Highlight names countries to pick out, by name or ISO code. + Highlight []string + HighlightColor *Color + Background *Color + Grid bool +} + +func (o WorldMapOptions) window() (Bounds, Bounds) { + x, y := WorldX, WorldY + if o.X != nil { + x = *o.X + } + if o.Y != nil { + y = *o.Y + } + return x, y +} + +func DrawWorldMap(s Surface, o WorldMapOptions) { + if s.IsEmpty() { + return + } + theme := s.Theme + color := theme.Border + if o.Color != nil { + color = *o.Color + } + highlight := theme.Accent + if o.HighlightColor != nil { + highlight = *o.HighlightColor + } + x, y := o.window() + DrawCanvas(s, CanvasOptions{ + Shapes: WorldShapes(WorldShapeOptions{ + Color: &color, + Highlight: o.Highlight, + HighlightColor: &highlight, + }), + X: &x, + Y: &y, + Background: o.Background, + Grid: o.Grid, + }) +} + +// CountryAtCell is the country under a cell of a map drawn with these bounds. +// +// Exposed so a caller can answer a hover as well as a click, and so the +// arithmetic that has to agree with the drawing lives in one place. +func CountryAtCell(column, row, width, height int, o WorldMapOptions) *CountryOutline { + x, y := o.window() + lon, lat, ok := DegreesAt(column, row, width, height, x, y) + if !ok { + return nil + } + return CountryAt(lon, lat) +} diff --git a/ports/go/world.go b/ports/go/world.go new file mode 100644 index 0000000..a3e5d38 --- /dev/null +++ b/ports/go/world.go @@ -0,0 +1,178 @@ +package hqtui + +import ( + "math" + "strings" +) + +// The world, as shapes for the canvas, and the lookup that makes it clickable. +// +// The canvas already draws in the caller's own coordinates, and longitude and +// latitude are just another pair of axes — so a map is a list of polylines in +// degrees, and nothing here needs a projection of its own beyond deciding which +// window on the globe to show. +// +// The interesting half is the other direction. A click arrives as a terminal +// cell, and a country is a polygon, so answering "what did they click" means +// turning the cell back into degrees and testing it against the outlines. Doing +// it that way rather than with bounding boxes is what makes the answer right: +// Russia's bounding box covers most of the northern hemisphere, and Chile's +// covers Argentina. + +// WorldX and WorldY are the whole globe, which is what a map shows unless told +// otherwise. +var ( + WorldX = Bounds{Min: -180, Max: 180} + WorldY = Bounds{Min: -90, Max: 90} +) + +type WorldShapeOptions struct { + // Color is for countries with nothing special about them. + Color *Color + // Highlight names countries to pick out, by name or ISO code. + Highlight []string + HighlightColor *Color +} + +// countryMatches matches on either the name or the ISO code, case-insensitively. +func countryMatches(c CountryOutline, keys []string) bool { + for _, key := range keys { + if key == "" { + continue + } + if strings.EqualFold(c.Name, key) { + return true + } + if c.ISO != "" && strings.EqualFold(c.ISO, key) { + return true + } + } + return false +} + +// WorldShapes is the world as canvas shapes, one polyline per landmass. +// +// Polylines rather than scattered points: the outlines are closed rings, so +// joining them draws a coastline instead of a dotted suggestion of one, and it +// reads at a fraction of the resolution dots would need. +func WorldShapes(o WorldShapeOptions) []Shape { + shapes := make([]Shape, 0, 300) + for _, country := range WorldCountries { + color := o.Color + if len(o.Highlight) > 0 && countryMatches(country, o.Highlight) && o.HighlightColor != nil { + color = o.HighlightColor + } + for _, ring := range country.Rings { + points := make([]Point, 0, len(ring)/2+1) + for i := 0; i+1 < len(ring); i += 2 { + points = append(points, Point{X: ring[i], Y: ring[i+1]}) + } + // Closed: the last point joins the first, or every country has a gap + // in its coastline where the ring started. + if len(points) > 0 { + points = append(points, points[0]) + } + shapes = append(shapes, Shape{Kind: ShapePolyline, Points: points, Color: color}) + } + } + return shapes +} + +// insideRing reports whether a point is inside a ring, by ray casting. +// +// The ring is a flat list of interleaved coordinates, so this walks it two at a +// time rather than allocating a pair per vertex — it runs once per country per +// click, and there are a couple of thousand vertices. +func insideRing(ring []float64, lon, lat float64) bool { + inside := false + n := len(ring) / 2 + if n == 0 { + return false + } + j := n - 1 + for i := 0; i < n; i++ { + xi, yi := ring[i*2], ring[i*2+1] + xj, yj := ring[j*2], ring[j*2+1] + if (yi > lat) != (yj > lat) && lon < (xj-xi)*(lat-yi)/(yj-yi)+xi { + inside = !inside + } + j = i + } + return inside +} + +// CountryAt is the country containing a point, or nil for open water. +// +// Where outlines overlap — and at this resolution simplified borders do overlap +// — the first match wins, which is stable because the data is sorted by name. +func CountryAt(lon, lat float64) *CountryOutline { + if math.IsNaN(lon) || math.IsNaN(lat) || math.IsInf(lon, 0) || math.IsInf(lat, 0) { + return nil + } + for i := range WorldCountries { + for _, ring := range WorldCountries[i].Rings { + if insideRing(ring, lon, lat) { + return &WorldCountries[i] + } + } + } + return nil +} + +// FindCountry looks a country up by name or ISO code. +func FindCountry(key string) *CountryOutline { + keys := []string{key} + for i := range WorldCountries { + if countryMatches(WorldCountries[i], keys) { + return &WorldCountries[i] + } + } + return nil +} + +// CountryBounds is the window a country fills, with a little room around it. +// +// For zooming a map to a country: the bounding box alone puts the coastline +// flat against the edge of the panel, which reads as though the country has +// been cut off rather than framed. +func CountryBounds(country *CountryOutline, margin float64) (Bounds, Bounds) { + minLon, maxLon := math.Inf(1), math.Inf(-1) + minLat, maxLat := math.Inf(1), math.Inf(-1) + for _, ring := range country.Rings { + for i := 0; i+1 < len(ring); i += 2 { + minLon = math.Min(minLon, ring[i]) + maxLon = math.Max(maxLon, ring[i]) + minLat = math.Min(minLat, ring[i+1]) + maxLat = math.Max(maxLat, ring[i+1]) + } + } + if math.IsInf(minLon, 0) { + return WorldX, WorldY + } + // A single-point country would give a zero-width window, which cannot be + // mapped onto anything. + padX := math.Max((maxLon-minLon)*margin, 1) + padY := math.Max((maxLat-minLat)*margin, 1) + return Bounds{Min: minLon - padX, Max: maxLon + padX}, + Bounds{Min: minLat - padY, Max: maxLat + padY} +} + +// DegreesAt is the degrees under a terminal cell, given the window the map was +// drawn with. +// +// The inverse of what the canvas does on the way in, taken at the centre of the +// cell: a click lands on a whole cell, and the centre is the only point in it +// that is not arbitrarily nearer one neighbour than the other. +func DegreesAt(column, row, width, height int, x, y Bounds) (lon, lat float64, ok bool) { + if width <= 0 || height <= 0 { + return 0, 0, false + } + // The canvas is 2x4 Braille pixels per cell, and it spans its bounds across + // `pixels - 1`, so the inverse has to use the same denominators or a click + // drifts from what was drawn. + px := math.Max(1, float64(width*2-1)) + py := math.Max(1, float64(height*4-1)) + lon = x.Min + (float64(column*2+1)/px)*(x.Max-x.Min) + lat = y.Min + (1-float64(row*4+2)/py)*(y.Max-y.Min) + return lon, lat, true +} diff --git a/ports/go/world_data.go b/ports/go/world_data.go new file mode 100644 index 0000000..853223b --- /dev/null +++ b/ports/go/world_data.go @@ -0,0 +1,1308 @@ +package hqtui + +// Country outlines, flattened for a terminal. +// +// Generated by packages/hqtui/scripts/generate-world.ts from Natural Earth's +// 1:110m Admin 0 countries, which is public domain. Do not edit by hand. +// +// Each ring is longitude and latitude interleaved -- lon, lat, lon, lat -- +// rather than a list of pairs, because at 1982 points the nested form +// costs a container per coordinate for no gain. A country has more than one +// ring when it is more than one landmass. +// +// 171 countries, 1982 points, simplified at 1 degrees. + +type CountryOutline struct { + Name string + // ISO 3166-1 alpha-2, where Natural Earth has one. + ISO string + // Rings are longitude and latitude, interleaved. + Rings [][]float64 +} + +var WorldCountries = []CountryOutline{ + { + Name: "Afghanistan", + ISO: "AF", + Rings: [][]float64{ + {66.5,37.4,70.8,38.5,71.8,36.7,75.2,37.1,71.3,36.1,69.3,31.9,66.3,29.9,60.9,29.8,61.2,35.7,66.5,37.4}, + }, + }, + { + Name: "Albania", + ISO: "AL", + Rings: [][]float64{ + {21,40.8,19.4,40.3,19.7,42.7,21,40.8}, + }, + }, + { + Name: "Algeria", + ISO: "DZ", + Rings: [][]float64{ + {-8.7,27.4,-8.7,28.8,-1.3,32.3,-1.2,35.7,8.4,36.9,7.5,34.1,9.8,29.4,9.3,26.1,12,23.5,3.2,19.1,-8.7,27.4}, + }, + }, + { + Name: "Angola", + ISO: "AO", + Rings: [][]float64{ + {12.3,-6.1,16.3,-5.9,17.5,-8.1,21.7,-7.3,22.2,-11.1,24,-11.2,24,-12.9,21.9,-12.9,23.2,-17.5,11.7,-17.3,13.7,-11.3,12.3,-6.1}, + }, + }, + { + Name: "Antarctica", + ISO: "AQ", + Rings: [][]float64{ + {-48.7,-78,-43.9,-78.5,-43.3,-80,-54.2,-80.6,-48.7,-78}, + {-66.3,-80.3,-59.6,-80,-66.3,-80.3}, + {-73.9,-71.3,-70.3,-68.9,-68.3,-71.4,-75,-72.1,-73.9,-71.3}, + {-102.3,-71.9,-96.2,-72.5,-102.3,-71.9}, + {-122.6,-73.7,-118.7,-73.5,-122.6,-73.7}, + {-127.3,-73.5,-124,-73.9,-127.3,-73.5}, + {-163.7,-78.6,-159.2,-79.5,-163.7,-78.6}, + {180,-84.7,180,-90,-180,-90,-179.1,-84.1,-143.1,-85,-153.6,-83.7,-152.9,-82,-156.8,-81.1,-146.4,-80.3,-155.3,-79.1,-158.4,-76.9,-151.3,-77.4,-146.1,-76.5,-146.2,-75.4,-135.2,-74.3,-100.1,-74.9,-103.7,-72.6,-74.9,-73.9,-67.4,-72.5,-67.7,-67.3,-57.8,-63.3,-65.7,-68,-61.8,-70.7,-60.8,-73.7,-70.6,-76.6,-77.2,-76.7,-73.7,-77.9,-78,-79.2,-58.2,-83.2,-28.5,-80.3,-35.6,-79.5,-35.8,-78.3,-17.5,-75.1,-15.4,-73.1,-6.9,-70.9,27.1,-70.5,33.9,-68.5,38.6,-69.8,54.5,-65.8,61.4,-68,68.9,-67.9,69.7,-69.2,67.9,-71.9,69.9,-72.3,73.9,-69.9,88,-66.2,95.8,-67.4,102.8,-65.6,106.2,-66.9,113.6,-65.9,119.8,-67.3,135.1,-65.3,137.5,-67,145.5,-66.9,171.2,-71.7,163.6,-76.2,167,-78.8,161.8,-79.2,159.8,-80.9,169.4,-83.8,180,-84.7}, + }, + }, + { + Name: "Argentina", + ISO: "AR", + Rings: [][]float64{ + {-68.6,-52.6,-65,-54.7,-68.6,-54.9,-68.6,-52.6}, + {-57.6,-30.2,-58.5,-34.4,-56.8,-36.9,-62.3,-38.8,-62.7,-41,-65.1,-41.1,-63.5,-42.6,-67.3,-45.6,-65.6,-47.2,-69.1,-50.7,-68.1,-52.3,-71.9,-52,-73.4,-49.3,-71.2,-44.8,-72.1,-42.3,-68.4,-24.5,-66.3,-21.8,-62.8,-22,-57.8,-25.2,-58.6,-27.1,-55.7,-27.4,-54.1,-25.5,-53.6,-26.9,-57.6,-30.2}, + }, + }, + { + Name: "Armenia", + ISO: "AM", + Rings: [][]float64{ + {46.5,38.8,43.6,41.1,45.6,40.8,46.5,38.8}, + }, + }, + { + Name: "Australia", + ISO: "AU", + Rings: [][]float64{ + {147.7,-40.8,147.9,-43.2,146,-43.5,144.7,-40.7,147.7,-40.8}, + {126.1,-32.2,118,-35.1,115,-34.2,113.7,-22.5,120.9,-19.7,125.7,-14.2,129.6,-15,132.4,-11.1,136.5,-11.9,135.5,-15,140.2,-17.7,142.5,-10.7,146.4,-19,150.7,-22.4,153.6,-28.1,150,-37.4,146.3,-39,140.6,-38,138.2,-34.4,136.8,-35.3,137.8,-32.9,136,-34.9,131.3,-31.5,126.1,-32.2}, + }, + }, + { + Name: "Austria", + ISO: "AT", + Rings: [][]float64{ + {17,48.1,14.6,46.4,9.5,47.1,12.9,47.5,13.6,48.9,17,48.1}, + }, + }, + { + Name: "Azerbaijan", + ISO: "AZ", + Rings: [][]float64{ + {46.4,41.9,50.4,40.3,48.9,38.3,45.6,39.9,45,41.2,46.4,41.9}, + }, + }, + { + Name: "Bahamas", + ISO: "BS", + Rings: [][]float64{ + {-78.2,25.2,-77.5,23.8,-78.2,25.2}, + }, + }, + { + Name: "Bangladesh", + ISO: "BD", + Rings: [][]float64{ + {92.7,22,92.4,20.7,91.4,22.8,89,22.1,88.6,26.4,92.4,25,91.2,23.5,92.7,22}, + }, + }, + { + Name: "Belarus", + ISO: "BY", + Rings: [][]float64{ + {28.2,56.2,30.9,55.6,32.7,53.4,31.8,52.1,23.5,51.6,23.5,53.9,28.2,56.2}, + }, + }, + { + Name: "Belgium", + ISO: "BE", + Rings: [][]float64{ + {6.2,50.8,5.7,49.5,2.5,51.1,6.2,50.8}, + }, + }, + { + Name: "Belize", + ISO: "BZ", + Rings: [][]float64{ + {-89.1,17.8,-88.1,18.3,-88.9,15.9,-89.1,17.8}, + }, + }, + { + Name: "Benin", + ISO: "BJ", + Rings: [][]float64{ + {2.7,6.3,0.8,10.5,2.8,12.2,2.7,6.3}, + }, + }, + { + Name: "Bhutan", + ISO: "BT", + Rings: [][]float64{ + {91.7,27.8,88.8,27.1,91.7,27.8}, + }, + }, + { + Name: "Bolivia", + ISO: "BO", + Rings: [][]float64{ + {-69.5,-11,-65.3,-9.8,-65.4,-11.6,-60.5,-13.8,-60.2,-16.3,-58.2,-16.3,-57.9,-20,-61.8,-19.6,-62.7,-22.2,-67.8,-22.9,-69.5,-11}, + }, + }, + { + Name: "Bosnia and Herz.", + ISO: "BA", + Rings: [][]float64{ + {18.6,42.7,16,45.2,19.4,44.9,18.6,42.7}, + }, + }, + { + Name: "Botswana", + ISO: "BW", + Rings: [][]float64{ + {29.4,-22.1,25.7,-25.5,21.6,-26.7,19.9,-24.8,20.9,-18.3,25.3,-17.7,29.4,-22.1}, + }, + }, + { + Name: "Brazil", + ISO: "BR", + Rings: [][]float64{ + {-53.4,-33.8,-53.8,-32,-57.6,-30.2,-53.6,-26.1,-55.8,-22.4,-57.9,-22.1,-58.2,-16.3,-60.2,-16.3,-60.5,-13.8,-65.4,-11.6,-65.3,-9.8,-70.5,-11,-70.5,-9.5,-72.2,-10.1,-74,-7.5,-72.9,-5.3,-69.9,-4.3,-69.8,1.7,-65.5,0.8,-63.4,2.2,-64.8,4.1,-60.7,5.2,-59,1.3,-52.9,2.1,-51.3,4.2,-50.4,-0.1,-44.6,-2.7,-40,-2.9,-35.6,-5.1,-34.7,-7.3,-38.7,-13.1,-40.9,-21.9,-47.6,-24.9,-48.9,-28.7,-53.4,-33.8}, + }, + }, + { + Name: "Bulgaria", + ISO: "BG", + Rings: [][]float64{ + {22.7,44.2,28.6,43.7,28,42,23,41.3,22.7,44.2}, + }, + }, + { + Name: "Burkina Faso", + ISO: "BF", + Rings: [][]float64{ + {-5.4,10.4,-4.3,13.2,-1.1,15,2.2,12.6,0.9,11,-2.9,11,-2.8,9.6,-5.4,10.4}, + }, + }, + { + Name: "Burundi", + ISO: "BI", + Rings: [][]float64{ + {30.5,-2.4,29.3,-4.5,29,-2.8,30.5,-2.4}, + }, + }, + { + Name: "Cambodia", + ISO: "KH", + Rings: [][]float64{ + {102.6,12.2,103,14.2,107.6,13.5,106.2,11,103.5,10.6,102.6,12.2}, + }, + }, + { + Name: "Cameroon", + ISO: "CM", + Rings: [][]float64{ + {14.5,12.9,14.5,4.7,15.9,1.7,9.6,2.3,8.8,5.5,11.7,7,14.5,12.9}, + }, + }, + { + Name: "Canada", + ISO: "CA", + Rings: [][]float64{ + {-122.8,49,-127.4,50.8,-130.5,54.3,-130,55.9,-135.5,59.8,-137.5,58.9,-141,60.3,-141,69.7,-136.5,68.9,-128.1,70.5,-113.5,67.7,-106.1,68.8,-101.5,67.6,-97.7,68.6,-96.1,67.3,-94.2,69.1,-96.5,70.1,-95.2,71.9,-87.4,67.2,-85.5,69.9,-82.6,69.7,-81.4,67.1,-85.8,66.6,-90.7,63.6,-94.7,58.9,-92.3,57.1,-82.3,55.1,-79.9,51.2,-78.6,52.6,-79.8,54.7,-76.5,56.5,-78.5,58.8,-77.3,59.9,-78.1,62.3,-73.8,62.4,-69.6,61.1,-67.6,58.2,-64.6,60.3,-61.8,56.3,-57.3,54.6,-55.7,52.1,-60,50.2,-66.4,50.2,-71.1,46.8,-65.1,49.2,-64.5,46.2,-60.5,47,-59.8,45.9,-65.4,43.5,-66.2,44.5,-64.4,45.3,-67.1,45.1,-69.2,47.4,-71.5,45,-82.4,41.7,-82.6,45.3,-88.4,48.3,-122.8,49}, + {-84,62.5,-81.9,62.9,-84,62.5}, + {-79.8,72.8,-80.8,73.7,-76.3,72.8,-79.8,72.8}, + {-93.6,75,-96.8,74.9,-93.6,75}, + {-93.8,77.5,-96.4,77.8,-93.8,77.5}, + {-96.8,78.8,-95.6,78.4,-98.6,78.9,-96.8,78.8}, + {-88.2,74.4,-97.1,76.8,-79.8,74.9,-88.2,74.4}, + {-111.3,78.2,-109.9,78,-113.5,77.7,-111.3,78.2}, + {-111,78.8,-109.7,78.6,-112.5,78.4,-111,78.8}, + {-55.6,51.3,-56.8,49.8,-53.5,49.2,-53.1,46.7,-59.3,47.6,-55.6,51.3}, + {-83.9,65.1,-80.1,63.7,-87.2,63.5,-85.9,65.7,-83.9,65.1}, + {-78.8,72.4,-68.8,70.5,-67,69.2,-68.8,68.7,-61.9,66.9,-63.9,65,-68,66.3,-64.7,63.4,-68.8,63.7,-66.2,61.9,-68.9,62.3,-78.6,64.6,-74,65.5,-73.3,68.1,-79,70.2,-88.7,70.4,-90.2,72.2,-85.8,73.8,-85.8,72.5,-82.3,73.8,-78.8,72.4}, + {-94.5,74.1,-90.5,73.9,-95.4,72.1,-96,73.4,-94.5,74.1}, + {-122.9,76.1,-116.2,77.6,-122.9,76.1}, + {-132.7,54,-131.2,52.2,-132.7,54}, + {-105.5,79.3,-99.7,77.9,-105.5,79.3}, + {-123.5,48.5,-128.4,50.8,-123.5,48.5}, + {-121.5,74.4,-115.5,73.5,-123.1,70.9,-125.9,71.9,-123.9,73.7,-124.9,74.3,-121.5,74.4}, + {-107.8,75.8,-105.7,75.5,-117.7,75.2,-115.4,76.5,-107.8,75.8}, + {-106.5,73.1,-101.1,69.6,-113.3,68.5,-117.3,70,-112.4,70.4,-119.4,71.6,-115.2,73.3,-108.2,71.7,-108.4,73.1,-106.5,73.1}, + {-100.4,72.7,-101.5,73.4,-97.4,73.8,-96.5,72.6,-98.4,71.3,-102.5,72.5,-100.4,72.7}, + {-106.6,73.6,-104.5,73.4,-106.6,73.6}, + {-98.5,76.7,-98.2,75,-102.5,75.6,-98.5,76.7}, + {-96,80.6,-92.4,81.3,-85.8,79.3,-92.9,78.3,-96,80.6}, + {-91.6,81.9,-61.8,82.6,-76.9,79.3,-75.4,78.5,-80.6,76.2,-89.5,76.5,-88.3,77.9,-85,77.5,-88,78.4,-85.1,79.3,-86.9,80.3,-81.8,80.5,-91.6,81.9}, + {-75.2,67.4,-77.2,67.6,-75.2,67.4}, + {-96.3,69.5,-99.8,69.4,-96.3,69.5}, + {-64.5,49.9,-61.8,49.1,-64.5,49.9}, + {-64,47,-62,46.4,-64,47}, + }, + }, + { + Name: "Central African Rep.", + ISO: "CF", + Rings: [][]float64{ + {27.4,5.2,22.4,4,19.5,5,16,2.3,14.5,5.5,15.3,7.4,22.9,11.1,27.4,5.2}, + }, + }, + { + Name: "Chad", + ISO: "TD", + Rings: [][]float64{ + {23.8,19.6,23.9,15.6,21.9,12.6,22.9,11.1,15.3,7.4,13.5,14.4,15.9,20.4,14.9,22.9,23.8,19.6}, + }, + }, + { + Name: "Chile", + ISO: "CL", + Rings: [][]float64{ + {-68.6,-52.6,-68.6,-54.9,-67,-54.9,-68.1,-55.6,-74.7,-52.8,-71.1,-54.1,-68.6,-52.6}, + {-69.6,-17.6,-67,-23,-70.5,-31.4,-69.8,-34.2,-72.1,-42.3,-71.2,-44.8,-73.4,-49.3,-71.9,-52,-68.6,-52.3,-71.4,-53.9,-74.9,-52.3,-75.6,-48.7,-74.1,-46.9,-75.6,-46.6,-72.7,-42.4,-74.3,-43.2,-69.6,-17.6}, + }, + }, + { + Name: "China", + ISO: "CN", + Rings: [][]float64{ + {109.5,18.2,108.6,19.4,110.8,20.1,109.5,18.2}, + {80.3,42.3,80,44.9,87.8,49.3,91,46.9,90.9,45.3,96.3,42.7,109.2,42.5,111.9,45.1,119.7,46.7,115.5,48.1,122.2,53.4,125.9,52.8,131,47.8,135,48.5,133.1,45.1,131,45,130.6,42.4,121.1,38.9,121.6,40.9,117.5,38.7,122.4,37.5,119.2,34.9,121.9,31.7,121.7,28.2,118.7,24.5,110.4,20.3,105.3,23.4,101.7,22.3,101.8,21.2,99.2,22.1,97.6,23.9,98.7,27.5,96.1,29.5,88.8,27.3,78.7,31.5,78.9,34.3,73.7,39.4,80.3,42.3}, + }, + }, + { + Name: "Colombia", + ISO: "CO", + Rings: [][]float64{ + {-66.9,1.3,-69.8,1.7,-69.9,-4.3,-70,-2.7,-77.4,0.4,-79,1.7,-77.1,3.8,-77.5,8.5,-71.4,12.4,-73.3,9.2,-72,7,-67.3,6.1,-66.9,1.3}, + }, + }, + { + Name: "Congo", + ISO: "CG", + Rings: [][]float64{ + {18.5,3.5,16,-3.5,11.9,-5,11.5,-2.8,14.4,-1.3,13.1,2.3,15.9,1.7,18.5,3.5}, + }, + }, + { + Name: "Costa Rica", + ISO: "CR", + Rings: [][]float64{ + {-82.5,9.6,-83,8.2,-85.9,10.9,-82.5,9.6}, + }, + }, + { + Name: "Côte d'Ivoire", + ISO: "CI", + Rings: [][]float64{ + {-8,10.2,-2.8,9.6,-2.9,5,-7.7,4.4,-8,10.2}, + }, + }, + { + Name: "Croatia", + ISO: "HR", + Rings: [][]float64{ + {16.6,46.5,19.4,45.2,15.8,44.8,18.5,42.5,13.7,45.1,16.6,46.5}, + }, + }, + { + Name: "Cuba", + ISO: "CU", + Rings: [][]float64{ + {-82.3,23.2,-74.2,20.3,-77.8,19.9,-81.8,22.6,-85,21.9,-82.3,23.2}, + }, + }, + { + Name: "Cyprus", + ISO: "CY", + Rings: [][]float64{ + {32.7,35.1,34,35,32.7,35.1}, + }, + }, + { + Name: "Czechia", + ISO: "CZ", + Rings: [][]float64{ + {15,51.1,18.9,49.5,12.5,49.5,15,51.1}, + }, + }, + { + Name: "Dem. Rep. Congo", + ISO: "CD", + Rings: [][]float64{ + {29.3,-4.5,30.7,-8.3,28.7,-8.5,28.4,-11.8,29.7,-13.3,22.2,-11.1,21.7,-7.3,17.5,-8.1,16.3,-5.9,12.2,-5.8,16,-3.5,19.5,5,29.7,4.6,31.2,2.2,29.3,-4.5}, + }, + }, + { + Name: "Denmark", + ISO: "DK", + Rings: [][]float64{ + {9.9,55,8.1,56.5,10.6,57.7,9.9,55}, + {12.4,56.1,12.1,54.8,11,55.4,12.4,56.1}, + }, + }, + { + Name: "Djibouti", + ISO: "DJ", + Rings: [][]float64{ + {42.4,12.5,42.8,10.9,42.4,12.5}, + }, + }, + { + Name: "Dominican Rep.", + ISO: "DO", + Rings: [][]float64{ + {-71.7,18,-71.6,19.9,-68.3,18.6,-71.7,18}, + }, + }, + { + Name: "Ecuador", + ISO: "EC", + Rings: [][]float64{ + {-75.4,-0.2,-78.6,-4.5,-80.4,-4.4,-80.1,0.8,-75.4,-0.2}, + }, + }, + { + Name: "Egypt", + ISO: "EG", + Rings: [][]float64{ + {36.9,22,25,22,25.2,31.6,34.3,31.2,34.2,27.8,32.3,29.8,36.9,22}, + }, + }, + { + Name: "El Salvador", + ISO: "SV", + Rings: [][]float64{ + {-89.4,14.4,-87.9,13.1,-90.1,13.7,-89.4,14.4}, + }, + }, + { + Name: "Eq. Guinea", + ISO: "GQ", + Rings: [][]float64{ + {9.6,2.3,11.3,1.1,9.5,1,9.6,2.3}, + }, + }, + { + Name: "Eritrea", + ISO: "ER", + Rings: [][]float64{ + {36.4,14.4,38.4,18,43.1,12.7,36.4,14.4}, + }, + }, + { + Name: "Estonia", + ISO: "EE", + Rings: [][]float64{ + {28,59.5,27.3,57.5,23.3,59.2,28,59.5}, + }, + }, + { + Name: "eSwatini", + ISO: "SZ", + Rings: [][]float64{ + {32.1,-26.7,31,-25.7,32.1,-26.7}, + }, + }, + { + Name: "Ethiopia", + ISO: "ET", + Rings: [][]float64{ + {47.8,8,45,5,39.6,3.4,36.2,4.4,33,7.8,37.9,15,41.6,13.5,43.7,9.2,47.8,8}, + }, + }, + { + Name: "Falkland Is.", + ISO: "FK", + Rings: [][]float64{ + {-61.2,-51.8,-57.7,-51.5,-61.2,-51.8}, + }, + }, + { + Name: "Finland", + ISO: "FI", + Rings: [][]float64{ + {28.6,69.1,31.1,62.4,28.1,60.5,21.3,60.7,21.5,63.2,25.4,65.1,20.6,69.1,24.7,68.6,27.7,70.2,28.6,69.1}, + }, + }, + { + Name: "Fr. S. Antarctic Lands", + ISO: "TF", + Rings: [][]float64{ + {68.9,-48.6,70.6,-49.3,68.7,-49.8,68.9,-48.6}, + }, + }, + { + Name: "France", + ISO: "FR", + Rings: [][]float64{ + {-51.7,4.2,-52.9,2.1,-54.5,2.3,-54,5.8,-51.7,4.2}, + {6.2,49.5,8.1,49,6,46.7,7.4,43.7,1.8,42.3,-1.9,43.4,-1.2,46,-4.6,48.7,-1.6,48.6,-1.9,49.8,2.5,51.1,6.2,49.5}, + {8.7,42.6,9.2,41.4,8.7,42.6}, + }, + }, + { + Name: "Gabon", + ISO: "GA", + Rings: [][]float64{ + {11.3,2.3,14.3,1.2,14.4,-1.3,11.1,-4,8.8,-1.1,11.3,2.3}, + }, + }, + { + Name: "Gambia", + ISO: "GM", + Rings: [][]float64{ + {-16.7,13.6,-13.8,13.5,-16.7,13.6}, + }, + }, + { + Name: "Georgia", + ISO: "GE", + Rings: [][]float64{ + {40,43.4,46.6,41.2,41.6,41.5,40,43.4}, + }, + }, + { + Name: "Germany", + ISO: "DE", + Rings: [][]float64{ + {14.1,53.8,15,51.1,12.2,50.3,12.9,47.5,7.5,47.6,8.1,49,6,50.1,7.1,53.7,9.9,55,14.1,53.8}, + }, + }, + { + Name: "Ghana", + ISO: "GH", + Rings: [][]float64{ + {0,11,1.1,5.9,-2.9,5,-2.9,11,0,11}, + }, + }, + { + Name: "Greece", + ISO: "GR", + Rings: [][]float64{ + {26.3,35.3,23.5,35.3,26.3,35.3}, + {23,41.3,26.6,41.6,22.6,40.3,24,37.7,22.5,36.4,20.2,39.6,23,41.3}, + }, + }, + { + Name: "Greenland", + ISO: "GL", + Rings: [][]float64{ + {-46.8,82.6,-27.1,83.5,-20.8,82.7,-31.9,82.2,-12.2,81.3,-20,80.2,-17.7,80.1,-19.7,78.8,-18.5,77,-21.7,76.6,-19.4,74.3,-24.8,72.3,-21.8,70.7,-25.5,71.4,-26.4,70.2,-22.3,70.1,-39.8,65.5,-43.4,60.1,-48.3,60.9,-51.6,63.6,-54,67.2,-50.9,69.9,-54.7,69.6,-54.4,70.8,-51.4,70.6,-55.8,71.7,-54.7,72.6,-58.6,75.5,-68.5,76.1,-71.4,77,-66.8,77.4,-73.3,78,-65.7,79.4,-68,80.1,-62.7,81.8,-44.5,81.7,-46.8,82.6}, + }, + }, + { + Name: "Guatemala", + ISO: "GT", + Rings: [][]float64{ + {-92.2,14.5,-90.5,16.1,-91,17.8,-89.1,17.8,-88.2,15.7,-89.4,14.4,-92.2,14.5}, + }, + }, + { + Name: "Guinea", + ISO: "GN", + Rings: [][]float64{ + {-13.7,12.6,-9.1,12.3,-8.3,7.7,-11.1,10,-13.2,8.9,-15.1,11,-13.7,12.6}, + }, + }, + { + Name: "Guinea-Bissau", + ISO: "GW", + Rings: [][]float64{ + {-16.7,12.4,-13.7,11.8,-15.1,11,-16.7,12.4}, + }, + }, + { + Name: "Guyana", + ISO: "GY", + Rings: [][]float64{ + {-56.5,1.9,-59.6,1.8,-61.4,6,-59.8,8.4,-57.1,6,-58,4.1,-56.5,1.9}, + }, + }, + { + Name: "Haiti", + ISO: "HT", + Rings: [][]float64{ + {-71.7,19.7,-71.7,18,-74.5,18.3,-71.7,19.7}, + }, + }, + { + Name: "Honduras", + ISO: "HN", + Rings: [][]float64{ + {-83.1,15,-87.3,13,-89.4,14.4,-87.9,15.9,-83.1,15}, + }, + }, + { + Name: "Hungary", + ISO: "HU", + Rings: [][]float64{ + {22.1,48.4,21,46.3,16.2,46.9,17,48.1,22.1,48.4}, + }, + }, + { + Name: "Iceland", + ISO: "IS", + Rings: [][]float64{ + {-14.5,66.5,-13.6,65.1,-18.7,63.5,-22.8,64,-21.8,64.4,-24,64.9,-22.2,65.4,-24.3,65.6,-14.5,66.5}, + }, + }, + { + Name: "India", + ISO: "IN", + Rings: [][]float64{ + {97.3,28.3,92.7,22,91.2,23.5,92.4,25,88.6,26.4,88.9,21.7,80.3,15.9,79.9,10.4,77.5,8,72.6,21.4,70.5,20.9,68.2,23.7,71,24.4,69.5,26.9,75.3,32.3,73.7,34.3,77.8,35.5,78.7,31.5,81.1,30.2,80.1,28.8,83.3,27.4,88.1,26.4,88.7,28.1,92,26.8,96.1,29.5,97.3,28.3}, + }, + }, + { + Name: "Indonesia", + ISO: "ID", + Rings: [][]float64{ + {141,-2.6,141,-9.1,137.6,-8.4,137.9,-5.4,133,-4.1,132,-2.8,133.7,-2.2,130.5,-0.9,134,-0.8,135.5,-3.4,137.4,-1.7,141,-2.6}, + {125,-8.9,123.5,-10.2,125,-8.9}, + {117.9,4.1,119,0.9,116.1,-4,110.2,-2.9,109.7,2,110.5,0.8,113.8,1.2,115.9,4.3,117.9,4.1}, + {129.4,-2.8,130.8,-3.9,127.9,-3.4,129.4,-2.8}, + {127.9,2.2,128.1,-0.9,127.9,2.2}, + {122.9,0.9,125.2,1.4,120,-0.5,123.3,-0.6,121.5,-1.9,123.2,-5.3,121,-2.6,119.8,-5.7,118.8,-2.8,119.8,0.2,122.9,0.9}, + {120.3,-10.3,119,-9.6,120.3,-10.3}, + {121.3,-8.5,122.9,-8.1,119.9,-8.8,121.3,-8.5}, + {118.3,-8.4,116.7,-9,118.3,-8.4}, + {108.5,-6.4,115.7,-8.4,105.4,-6.9,108.5,-6.4}, + {104.4,-1.1,106.1,-3.1,105.8,-5.9,102.6,-4.2,95.3,5.5,97.5,5.2,104.4,-1.1}, + }, + }, + { + Name: "Iran", + ISO: "IR", + Rings: [][]float64{ + {48.6,29.9,45.4,34,46.1,35.7,44.1,39.4,48.1,39.6,50.8,36.9,56.6,38.1,61.1,36.5,60.9,29.8,63.3,26.8,61.5,25.1,57.4,25.7,48.6,29.9}, + }, + }, + { + Name: "Iraq", + ISO: "IQ", + Rings: [][]float64{ + {39.2,32.2,41.3,36.4,44.8,37.2,48.6,29.9,44.7,29.2,39.2,32.2}, + }, + }, + { + Name: "Ireland", + ISO: "IE", + Rings: [][]float64{ + {-6.2,53.9,-6.8,52.3,-10,51.8,-7.6,55.1,-6.2,53.9}, + }, + }, + { + Name: "Israel", + ISO: "IL", + Rings: [][]float64{ + {35.7,32.7,34.9,29.5,34.3,31.2,35.7,32.7}, + }, + }, + { + Name: "Italy", + ISO: "IT", + Rings: [][]float64{ + {10.4,46.9,13.8,46.5,12.6,44.1,18.3,39.8,16.9,40.4,15.7,37.9,15.4,40,10.2,43.9,7.4,43.7,6.8,46,10.4,46.9}, + {14.8,38.1,15.1,36.6,12.4,37.6,14.8,38.1}, + {8.7,40.9,9.8,40.5,8.8,38.9,8.7,40.9}, + }, + }, + { + Name: "Jamaica", + ISO: "JM", + Rings: [][]float64{ + {-77.6,18.5,-76.2,17.9,-77.6,18.5}, + }, + }, + { + Name: "Japan", + ISO: "JP", + Rings: [][]float64{ + {141.9,39.2,140.3,35.1,135.8,33.5,135.1,34.6,131,33.9,132,33.1,130.2,31.4,129.4,33.3,139.4,38.2,140.3,41.2,141.9,39.2}, + {144.6,44,145.5,43.3,140,41.6,142,45.6,144.6,44}, + {132.4,33.5,134.8,33.8,132.4,33.5}, + }, + }, + { + Name: "Jordan", + ISO: "JO", + Rings: [][]float64{ + {35.5,32.4,38.8,33.4,39.2,32.2,37,31.5,38,30.5,36.1,29.2,34.9,29.5,35.5,32.4}, + }, + }, + { + Name: "Kazakhstan", + ISO: "KZ", + Rings: [][]float64{ + {87.4,49.2,80,44.9,80.3,42.3,74.2,43.3,68.6,40.7,64.9,43.7,62,43.5,58.5,45.6,55.9,45,56,41.3,52.5,41.8,50.3,44.6,53,45.3,53,46.9,49.1,46.4,46.5,48.4,50.8,51.7,61.3,50.8,60,52,61.4,54,69.1,55.4,73.4,53.5,76.9,54.5,80,50.9,87.4,49.2}, + }, + }, + { + Name: "Kenya", + ISO: "KE", + Rings: [][]float64{ + {39.2,-4.7,33.9,-0.9,35.3,5.5,38.1,3.6,41.9,3.9,41.6,-1.7,39.2,-4.7}, + }, + }, + { + Name: "Kosovo", + ISO: "XK", + Rings: [][]float64{ + {20.6,41.9,20.6,43.2,21.8,42.7,20.6,41.9}, + }, + }, + { + Name: "Kuwait", + ISO: "KW", + Rings: [][]float64{ + {48,30,48.4,28.6,46.6,29.1,48,30}, + }, + }, + { + Name: "Kyrgyzstan", + ISO: "KG", + Rings: [][]float64{ + {71,42.3,74.2,43.3,80.3,42.3,73.7,39.4,69.5,39.5,73.1,40.9,71,42.3}, + }, + }, + { + Name: "Laos", + ISO: "LA", + Rings: [][]float64{ + {107.4,14.2,105.2,14.3,104,18.2,101.1,17.5,100.1,20.4,101.7,22.3,104.4,20.8,103.9,19.3,107.4,14.2}, + }, + }, + { + Name: "Latvia", + ISO: "LV", + Rings: [][]float64{ + {27.3,57.5,28.2,56.2,26.5,55.6,21.1,56,22.5,57.8,27.3,57.5}, + }, + }, + { + Name: "Lebanon", + ISO: "LB", + Rings: [][]float64{ + {35.8,33.3,36.4,34.6,35.8,33.3}, + }, + }, + { + Name: "Lesotho", + ISO: "LS", + Rings: [][]float64{ + {29,-29,28.1,-30.5,27,-29.9,29,-29}, + }, + }, + { + Name: "Liberia", + ISO: "LR", + Rings: [][]float64{ + {-8.4,7.7,-7.7,4.4,-11.4,6.8,-10.2,8.4,-8.4,7.7}, + }, + }, + { + Name: "Libya", + ISO: "LY", + Rings: [][]float64{ + {25,22,23.8,19.6,10.3,24.4,10,31.4,11.5,33.1,19.1,30.3,20.9,32.7,24.9,31.9,25,22}, + }, + }, + { + Name: "Lithuania", + ISO: "LT", + Rings: [][]float64{ + {26.5,55.6,23.5,53.9,21.1,56,26.5,55.6}, + }, + }, + { + Name: "Madagascar", + ISO: "MG", + Rings: [][]float64{ + {49.5,-12.5,50.4,-15.7,47.1,-24.9,45.4,-25.6,43.3,-22.8,44,-17.4,49.5,-12.5}, + }, + }, + { + Name: "Malawi", + ISO: "MW", + Rings: [][]float64{ + {32.8,-9.2,35.7,-14.6,35,-16.8,32.7,-13.7,32.8,-9.2}, + }, + }, + { + Name: "Malaysia", + ISO: "MY", + Rings: [][]float64{ + {100.1,6.5,103,5.5,104.2,1.3,101.4,2.8,100.1,6.5}, + {117.9,4.1,115.9,4.3,114.6,1.4,109.8,1.3,115.3,4.3,116.7,6.9,119.2,5.4,117.9,4.1}, + }, + }, + { + Name: "Mali", + ISO: "ML", + Rings: [][]float64{ + {-11.5,12.4,-11.7,15.4,-5.5,15.5,-6.5,25,4.3,19.2,3.6,15.6,-4,13.5,-5.4,10.4,-11.5,12.4}, + }, + }, + { + Name: "Mauritania", + ISO: "MR", + Rings: [][]float64{ + {-17.1,21,-12.9,21.3,-12,25.9,-8.7,25.9,-8.7,27.4,-4.9,25,-6.5,25,-5.5,15.5,-12.2,14.6,-14.6,16.6,-16.5,16.1,-17.1,21}, + }, + }, + { + Name: "Mexico", + ISO: "MX", + Rings: [][]float64{ + {-117.1,32.5,-106.5,31.8,-103.9,29.3,-101.7,29.8,-97.1,25.9,-97.9,22.4,-95.9,18.8,-91.4,18.9,-90.3,21,-87.1,21.5,-87.8,18.3,-91,17.8,-90.5,16.1,-92.2,14.5,-103.5,18.3,-113.1,31.2,-114.9,31.4,-109.9,22.8,-115.1,27.7,-114.2,28.6,-117.1,32.5}, + }, + }, + { + Name: "Moldova", + ISO: "MD", + Rings: [][]float64{ + {26.6,48.2,30,46.4,28.2,45.5,26.6,48.2}, + }, + }, + { + Name: "Mongolia", + ISO: "MN", + Rings: [][]float64{ + {87.8,49.3,92.2,50.8,97.3,49.7,98.9,52,108.5,49.3,116.7,49.9,115.7,47.7,119.8,47,105,41.6,96.3,42.7,90.9,45.3,91,46.9,87.8,49.3}, + }, + }, + { + Name: "Montenegro", + ISO: "ME", + Rings: [][]float64{ + {20.1,42.6,18.5,42.5,20.1,42.6}, + }, + }, + { + Name: "Morocco", + ISO: "MA", + Rings: [][]float64{ + {-2.2,35.2,-1.3,32.3,-8.7,28.8,-8.8,27.1,-11.4,26.9,-14.8,21.5,-17,21.4,-14.4,26.3,-9.6,29.9,-8.7,33.2,-5.9,35.8,-2.2,35.2}, + }, + }, + { + Name: "Mozambique", + ISO: "MZ", + Rings: [][]float64{ + {34.6,-11.5,40.3,-10.3,40.8,-14.7,34.8,-19.8,35.5,-24.1,32.1,-26.7,31.2,-22.3,32.8,-16.7,30.2,-14.8,33.2,-14,35,-16.8,34.6,-11.5}, + }, + }, + { + Name: "Myanmar", + ISO: "MM", + Rings: [][]float64{ + {100.1,20.4,97.4,18.4,99.6,11.9,98.6,9.9,97.2,16.9,94.2,16,92.3,21.5,97.3,28.3,98.7,27.5,97.6,23.9,101.2,21.8,100.1,20.4}, + }, + }, + { + Name: "N. Cyprus", + ISO: "-99", + Rings: [][]float64{ + {32.7,35.1,34.6,35.7,32.7,35.1}, + }, + }, + { + Name: "Namibia", + ISO: "NA", + Rings: [][]float64{ + {19.9,-24.8,19.9,-28.5,16.3,-28.6,11.7,-17.3,25.1,-17.6,20.9,-18.3,19.9,-24.8}, + }, + }, + { + Name: "Nepal", + ISO: "NP", + Rings: [][]float64{ + {88.1,27.9,87.2,26.4,80.1,28.8,81.5,30.4,88.1,27.9}, + }, + }, + { + Name: "Netherlands", + ISO: "NL", + Rings: [][]float64{ + {6.9,53.5,6.2,50.8,3.3,51.3,6.9,53.5}, + }, + }, + { + Name: "New Caledonia", + ISO: "NC", + Rings: [][]float64{ + {165.8,-21.1,167.1,-22.2,164,-20.1,165.8,-21.1}, + }, + }, + { + Name: "New Zealand", + ISO: "NZ", + Rings: [][]float64{ + {176.9,-40.1,174.7,-41.3,174.7,-37.4,172.6,-34.5,176,-37.6,178.5,-37.7,176.9,-40.1}, + {169.7,-43.6,172.8,-40.5,174.2,-41.3,170.6,-45.9,166.7,-46.2,169.7,-43.6}, + }, + }, + { + Name: "Nicaragua", + ISO: "NI", + Rings: [][]float64{ + {-83.7,10.9,-87.7,12.9,-83.1,15,-83.7,10.9}, + }, + }, + { + Name: "Niger", + ISO: "NE", + Rings: [][]float64{ + {14.9,22.9,15.9,20.4,13.5,14.4,14.2,12.5,5.4,13.9,3.6,11.7,1,12.9,0.4,14.9,3.6,15.6,4.3,19.2,12,23.5,14.9,22.9}, + }, + }, + { + Name: "Nigeria", + ISO: "NG", + Rings: [][]float64{ + {2.7,6.3,4.4,13.7,13.1,13.6,14.6,12.1,11.7,7,8.5,4.8,5.9,4.3,2.7,6.3}, + }, + }, + { + Name: "North Korea", + ISO: "KP", + Rings: [][]float64{ + {130.6,42.4,127.5,39.8,128.2,38.4,124.7,38.1,125.1,40.6,130.6,42.4}, + }, + }, + { + Name: "North Macedonia", + ISO: "MK", + Rings: [][]float64{ + {22.4,42.3,23,41.3,20.6,41.1,22.4,42.3}, + }, + }, + { + Name: "Norway", + ISO: "NO", + Rings: [][]float64{ + {15.1,79.7,21.5,79,15.9,76.8,10.4,79.7,15.1,79.7}, + {31.1,69.6,18,68.6,12.6,64.1,11,58.9,5.7,58.6,5,62,19.2,69.8,28.2,71.2,31.1,69.6}, + {27.4,80.1,17.4,80.3,27.4,80.1}, + {24.7,77.9,20.7,77.7,24.7,77.9}, + }, + }, + { + Name: "Oman", + ISO: "OM", + Rings: [][]float64{ + {55.2,22.7,56.4,24.9,59.8,22.3,57.7,18.9,53.1,16.7,52,19,55,20,55.2,22.7}, + }, + }, + { + Name: "Pakistan", + ISO: "PK", + Rings: [][]float64{ + {77.8,35.5,73.7,34.3,75.3,32.3,69.5,26.9,71,24.4,61.5,25.1,63.3,26.8,60.9,29.8,66.3,29.9,71.8,36.5,75.2,37.1,77.8,35.5}, + }, + }, + { + Name: "Panama", + ISO: "PA", + Rings: [][]float64{ + {-77.4,8.7,-77.9,7.2,-79.1,9,-80.9,7.2,-82.9,9.5,-77.4,8.7}, + }, + }, + { + Name: "Papua New Guinea", + ISO: "PG", + Rings: [][]float64{ + {141,-2.6,147.6,-6.1,147.2,-7.4,150.7,-10.6,144.7,-7.6,141,-9.1,141,-2.6}, + {152.6,-3.7,152.8,-4.8,150.7,-2.7,152.6,-3.7}, + {151.3,-5.8,148.3,-5.7,152.1,-4.1,151.3,-5.8}, + {154.8,-5.3,155.9,-6.8,154.8,-5.3}, + }, + }, + { + Name: "Paraguay", + ISO: "PY", + Rings: [][]float64{ + {-58.2,-20.2,-57.9,-22.1,-54.3,-24,-55.7,-27.4,-58.6,-27.1,-57.8,-25.2,-62.7,-22.2,-61.8,-19.6,-58.2,-20.2}, + }, + }, + { + Name: "Peru", + ISO: "PE", + Rings: [][]float64{ + {-69.9,-4.3,-72.9,-5.3,-74,-7.5,-68.7,-12.6,-70.4,-18.3,-76,-14.6,-81.4,-4.7,-80.3,-3.4,-78.6,-4.5,-75.1,-0.1,-73.1,-2.3,-70,-2.7,-69.9,-4.3}, + }, + }, + { + Name: "Philippines", + ISO: "PH", + Rings: [][]float64{ + {122.6,10,124.1,11.2,123,9,122.6,10}, + {126.4,8.4,125.4,5.6,123.6,7.8,121.9,7.2,125.4,9.8,126.4,8.4}, + {118.5,9.3,117.2,8.4,119.5,11.4,118.5,9.3}, + {122.3,18.2,121.7,14.3,124.1,12.5,119.9,15.4,120.7,18.5,122.3,18.2}, + {125.5,12.2,124.8,10.1,124.3,12.6,125.5,12.2}, + }, + }, + { + Name: "Poland", + ISO: "PL", + Rings: [][]float64{ + {23.5,53.9,24,50.7,22.8,49,16.2,50.4,14.1,53,17.6,54.9,23.5,53.9}, + }, + }, + { + Name: "Portugal", + ISO: "PT", + Rings: [][]float64{ + {-9,41.9,-6.4,41.4,-7.9,36.8,-9.5,38.7,-9,41.9}, + }, + }, + { + Name: "Puerto Rico", + ISO: "PR", + Rings: [][]float64{ + {-66.3,18.5,-67.2,17.9,-66.3,18.5}, + }, + }, + { + Name: "Qatar", + ISO: "QA", + Rings: [][]float64{ + {50.8,24.8,51.3,26.1,50.8,24.8}, + }, + }, + { + Name: "Romania", + ISO: "RO", + Rings: [][]float64{ + {28.2,45.5,29.6,45.3,28.6,43.7,22.9,43.8,20.2,46.1,26.6,48.2,28.2,45.5}, + }, + }, + { + Name: "Russia", + ISO: "RU", + Rings: [][]float64{ + {49.1,46.4,46.7,44.6,47.8,41.2,36.7,45.2,40.1,49.6,31.8,52.1,32.7,53.4,30.9,55.6,27.3,57.5,29.1,60,28.1,60.5,31.5,62.9,30,63.6,28.6,69.1,32.1,69.9,41.1,67.5,38.4,66,33.2,66.6,37,63.8,37.2,65.1,43.9,66.1,43.5,68.6,46.3,68.3,46.3,66.7,53.7,68.9,59.9,68.3,60.6,69.9,68.5,68.1,66.7,71,69.9,73,72.8,72.2,71.8,71.4,73.7,68.4,71.3,66.3,72.4,66.2,75.1,67.8,73.1,71.4,74.7,72.8,76.4,71.2,81.5,71.8,80.5,73.6,104.4,77.7,114.1,75.8,109.4,74.2,127,73.6,131.3,70.8,139.9,71.5,139.1,72.4,140.5,72.8,159,70.9,160.9,69.4,180,69,180,65,177.4,64.6,179.2,62.3,170.3,59.9,163.5,59.9,162,58.2,163.2,57.6,162.1,54.9,156.8,51,155.9,56.8,164.5,62.6,160.1,60.5,156.7,61.4,154.2,59.8,155,59.1,142.2,59,135.1,54.7,141.3,53.1,140.1,48.4,134.9,43.4,130.8,42.2,131,45,133.1,45.1,135,48.5,131,47.8,123.6,53.5,120.2,52.8,117.9,49.5,108.5,49.3,98.9,52,97.3,49.7,92.2,50.8,87.4,49.2,80,50.9,76.9,54.5,73.4,53.5,69.1,55.4,61.4,54,60,52,61.3,50.8,50.8,51.7,47.5,50.5,46.5,48.4,49.1,46.4}, + {93.8,81,100.2,79.8,97.8,78.8,91.2,80.3,93.8,81}, + {102.8,79.3,105.4,78.7,99.4,77.9,102.8,79.3}, + {138.8,76.1,145.1,75.6,137,75.3,138.8,76.1}, + {148.2,75.3,150.7,75.1,146.1,75.2,148.2,75.3}, + {139.9,73.4,143.6,73.2,139.9,73.4}, + {44.8,80.6,51.5,80.7,44.8,80.6}, + {22.7,54.3,19.7,54.4,22.7,54.3}, + {53.5,73.7,61.2,76.3,68.9,76.5,58.5,74.3,55.4,72.4,57.5,70.7,51.6,71.5,53.5,73.7}, + {142.9,53.7,144.7,49,143.2,49.3,143.5,46.1,142.1,46,141.7,53.3,142.9,53.7}, + {-174.9,67.2,-169.9,66,-173,64.3,-178.7,66.1,-180,65,-180,69,-174.9,67.2}, + {-178.7,70.9,-180,71.5,-177.6,71.3,-178.7,70.9}, + {33.4,46,36.5,45.5,33.9,44.4,32.5,45.3,33.4,46}, + }, + }, + { + Name: "Rwanda", + ISO: "RW", + Rings: [][]float64{ + {30.4,-1.1,29,-2.8,30.4,-1.1}, + }, + }, + { + Name: "S. Sudan", + ISO: "SS", + Rings: [][]float64{ + {30.8,3.5,23.9,8.6,25.8,10.4,31.4,9.8,33.2,12.2,33,7.8,35.3,5.5,30.8,3.5}, + }, + }, + { + Name: "Saudi Arabia", + ISO: "SA", + Rings: [][]float64{ + {35,29.4,39.2,32.2,47.5,29,52,23,55.2,22.7,55,20,47,16.9,43.4,17.6,42.8,16.3,35,29.4}, + }, + }, + { + Name: "Senegal", + ISO: "SN", + Rings: [][]float64{ + {-16.7,13.6,-17.6,14.7,-14.6,16.6,-11.5,12.4,-16.7,12.4,-13.8,13.5,-16.7,13.6}, + }, + }, + { + Name: "Serbia", + ISO: "RS", + Rings: [][]float64{ + {18.8,45.9,22.7,44.6,22.5,42.5,19.2,43.5,18.8,45.9}, + }, + }, + { + Name: "Sierra Leone", + ISO: "SL", + Rings: [][]float64{ + {-13.2,8.9,-11.1,10,-10.2,8.4,-11.4,6.8,-13.2,8.9}, + }, + }, + { + Name: "Slovakia", + ISO: "SK", + Rings: [][]float64{ + {22.6,49.1,16.9,48.5,22.6,49.1}, + }, + }, + { + Name: "Slovenia", + ISO: "SI", + Rings: [][]float64{ + {13.8,46.5,16.6,46.5,15.3,45.5,13.8,46.5}, + }, + }, + { + Name: "Solomon Is.", + ISO: "SB", + Rings: [][]float64{ + {159.6,-8,158.2,-7.4,159.6,-8}, + }, + }, + { + Name: "Somalia", + ISO: "SO", + Rings: [][]float64{ + {41.6,-1.7,41,2.8,45,5,48.9,9.5,48.9,11.4,51.1,12,48.6,5.3,41.6,-1.7}, + }, + }, + { + Name: "Somaliland", + ISO: "-99", + Rings: [][]float64{ + {48.9,11.4,47.8,8,42.6,10.6,48.9,11.4}, + }, + }, + { + Name: "South Africa", + ISO: "ZA", + Rings: [][]float64{ + {16.3,-28.6,19.9,-28.5,19.9,-24.8,21.6,-26.7,25.7,-25.5,29.4,-22.1,31.2,-22.3,31.9,-24.4,30.7,-26.7,32.8,-26.7,28.2,-32.8,20.1,-34.8,18.4,-34.1,16.3,-28.6}, + }, + }, + { + Name: "South Korea", + ISO: "KR", + Rings: [][]float64{ + {126.2,37.7,128.3,38.6,129.1,35.1,126.5,34.4,126.2,37.7}, + }, + }, + { + Name: "Spain", + ISO: "ES", + Rings: [][]float64{ + {-7.5,37.1,-6.4,41.4,-9.4,43,3,42.5,-2.1,36.7,-7.5,37.1}, + }, + }, + { + Name: "Sri Lanka", + ISO: "LK", + Rings: [][]float64{ + {81.8,7.5,80.3,6,80.1,9.8,81.8,7.5}, + }, + }, + { + Name: "Sudan", + ISO: "SD", + Rings: [][]float64{ + {24.6,8.2,21.9,12.6,25,22,36.9,22,38.4,18,34,8.7,32.7,12.2,31.4,9.8,25.1,10.3,24.6,8.2}, + }, + }, + { + Name: "Suriname", + ISO: "SR", + Rings: [][]float64{ + {-54.5,2.3,-56.5,1.9,-57.6,3.3,-57.1,6,-54,5.8,-54.5,2.3}, + }, + }, + { + Name: "Sweden", + ISO: "SE", + Rings: [][]float64{ + {11,58.9,12.6,61.3,11.9,63.1,16.8,68,20.6,69.1,23.5,67.9,23.9,66,17.8,62.7,17.1,61.3,18.8,60.1,15.9,56.1,12.9,55.4,11,58.9}, + }, + }, + { + Name: "Switzerland", + ISO: "CH", + Rings: [][]float64{ + {9.6,47.5,10.4,46.5,6,46.3,9.6,47.5}, + }, + }, + { + Name: "Syria", + ISO: "SY", + Rings: [][]float64{ + {35.7,32.7,36.7,36.8,42.3,37.2,41,34.4,35.7,32.7}, + }, + }, + { + Name: "Taiwan", + ISO: "TW", + Rings: [][]float64{ + {121.8,24.4,120.7,22,120.1,23.6,121.8,24.4}, + }, + }, + { + Name: "Tajikistan", + ISO: "TJ", + Rings: [][]float64{ + {67.8,37.1,67.7,39.6,70.7,41,69.5,39.5,73.7,39.4,75,37.4,71.8,36.7,70.8,38.5,67.8,37.1}, + }, + }, + { + Name: "Tanzania", + ISO: "TZ", + Rings: [][]float64{ + {33.9,-0.9,39.2,-4.7,39.5,-10.9,34.6,-11.5,29.6,-6.5,30.4,-1.1,33.9,-0.9}, + }, + }, + { + Name: "Thailand", + ISO: "TH", + Rings: [][]float64{ + {105.2,14.3,103,14.2,102.6,12.2,100.1,13.4,99.2,9.2,102.1,6.2,101.2,5.7,98.2,8.4,99.6,11.9,97.4,18.4,100.1,20.4,101.1,17.5,104.7,17.4,105.2,14.3}, + }, + }, + { + Name: "Timor-Leste", + ISO: "TL", + Rings: [][]float64{ + {125,-8.9,127.3,-8.4,125,-8.9}, + }, + }, + { + Name: "Togo", + ISO: "TG", + Rings: [][]float64{ + {0.9,11,1.1,5.9,0.9,11}, + }, + }, + { + Name: "Tunisia", + ISO: "TN", + Rings: [][]float64{ + {9.5,30.3,7.5,34.1,9.5,37.3,11,37.1,10.1,34.3,11.5,33.1,9.5,30.3}, + }, + }, + { + Name: "Turkey", + ISO: "TR", + Rings: [][]float64{ + {44.8,37.2,29.7,36.1,27.6,36.7,26.2,39.5,33.5,42,42.6,41.6,44.8,39.7,44.8,37.2}, + {26.1,41.8,29,41.3,26.4,40.2,26.1,41.8}, + }, + }, + { + Name: "Turkmenistan", + ISO: "TM", + Rings: [][]float64{ + {52.5,41.8,57.1,41.3,58.6,42.8,66.5,37.4,62.2,35.3,57.3,38,53.9,37.2,52.7,40,54.7,41,52.5,41.8}, + }, + }, + { + Name: "Uganda", + ISO: "UG", + Rings: [][]float64{ + {33.9,-0.9,29.6,-1.3,31.2,3.8,34.5,3.6,33.9,-0.9}, + }, + }, + { + Name: "Ukraine", + ISO: "UA", + Rings: [][]float64{ + {31.8,52.1,40.1,49.6,39.7,47.9,35,45.7,31.7,46.7,28.7,45.3,30,46.4,28.7,48.1,22.1,48.4,23.5,51.6,31.8,52.1}, + }, + }, + { + Name: "United Arab Emirates", + ISO: "AE", + Rings: [][]float64{ + {51.6,24.2,56.3,25.7,55,22.5,51.6,24.2}, + }, + }, + { + Name: "United Kingdom", + ISO: "GB", + Rings: [][]float64{ + {-6.2,53.9,-7.6,55.1,-6.2,53.9}, + {-3.1,53.4,-6.1,56.8,-5,58.6,-2,57.7,-3.1,56,1.7,52.7,1.4,51.3,-5.8,50.2,-3.4,51.4,-5.3,52,-4.6,53.5,-3.1,53.4}, + }, + }, + { + Name: "United States of America", + ISO: "US", + Rings: [][]float64{ + {-122.8,49,-88.4,48.3,-82.6,45.3,-82.7,41.7,-71.5,45,-69.2,47.4,-67,44.8,-70.1,43.7,-70,41.6,-75.5,39.5,-75.9,37.2,-76.3,39.2,-77,38.2,-75.7,35.6,-81.3,31.4,-80.4,25.2,-83.7,29.9,-86.4,30.4,-94.7,29.5,-97.5,25.8,-101,29.4,-103.9,29.3,-106.5,31.8,-117.1,32.5,-120.6,34.6,-124.4,40.3,-124.7,48.2,-122.6,47.1,-122.8,49}, + {-166.5,60.4,-165.6,59.9,-167.5,60.2,-166.5,60.4}, + {-153.2,58,-152.1,57.6,-154.5,57,-153.2,58}, + {-141,69.7,-141,60.3,-137.5,58.9,-135.5,59.8,-130,55.9,-130.5,54.8,-134.1,58.1,-139.9,59.5,-147.1,60.9,-151.7,59.2,-150.6,61.3,-158.4,56,-164.9,54.6,-157,58.9,-162,58.7,-165.3,60.5,-165.7,62.1,-160.8,64.8,-168.1,65.7,-161.7,66.1,-166.2,68.9,-156.6,71.4,-141,69.7}, + {-171.7,63.8,-168.7,63.3,-171.7,63.8}, + }, + }, + { + Name: "Uruguay", + ISO: "UY", + Rings: [][]float64{ + {-57.6,-30.2,-53.8,-32,-53.8,-34.4,-58.4,-33.9,-57.6,-30.2}, + }, + }, + { + Name: "Uzbekistan", + ISO: "UZ", + Rings: [][]float64{ + {56,41.3,55.9,45,58.5,45.6,62,43.5,64.9,43.7,68.3,40.7,71,42.3,73.1,40.9,67.7,39.6,67.8,37.1,58.6,42.8,56,41.3}, + }, + }, + { + Name: "Venezuela", + ISO: "VE", + Rings: [][]float64{ + {-60.7,5.2,-64.8,4.1,-63.4,2.2,-66.3,0.7,-67.8,2.8,-67.3,6.1,-72,7,-72.9,10.5,-71.3,11.8,-71.3,9.1,-69.9,12.2,-68.2,10.6,-61.9,10.7,-59.8,8.4,-60.7,5.2}, + }, + }, + { + Name: "Vietnam", + ISO: "VN", + Rings: [][]float64{ + {104.3,10.5,107.5,12.3,107.6,15.2,102.2,22.5,105.3,23.4,108.1,21.6,105.7,19.1,108.9,15.3,109.2,11.7,105.2,8.6,104.3,10.5}, + }, + }, + { + Name: "W. Sahara", + ISO: "EH", + Rings: [][]float64{ + {-8.7,27.7,-8.7,25.9,-12,25.9,-12.9,21.3,-17.1,21,-14.8,21.5,-11.4,26.9,-8.7,27.7}, + }, + }, + { + Name: "Yemen", + ISO: "YE", + Rings: [][]float64{ + {52,19,52.2,15.6,43.5,12.6,43.4,17.6,47,16.9,52,19}, + }, + }, + { + Name: "Zambia", + ISO: "ZM", + Rings: [][]float64{ + {30.7,-8.3,33.2,-9.7,33.2,-14,27,-17.9,23.2,-17.5,21.9,-12.9,24,-12.9,23.9,-10.9,29.7,-13.3,28.4,-9.2,30.7,-8.3}, + }, + }, + { + Name: "Zimbabwe", + ISO: "ZW", + Rings: [][]float64{ + {31.2,-22.3,28,-21.5,25.3,-17.7,30.3,-15.5,32.8,-16.7,31.2,-22.3}, + }, + }, +} diff --git a/ports/python/examples/world_probe.py b/ports/python/examples/world_probe.py new file mode 100644 index 0000000..f5b3d88 --- /dev/null +++ b/ports/python/examples/world_probe.py @@ -0,0 +1,41 @@ +"""Prints what the country lookup answers for a fixed set of points. + +The same probe exists for every port, so "the ports agree about the world" is a +diff rather than a hope. +""" + +from __future__ import annotations + +from hqtui.graphics.world import WORLD_X, WORLD_Y, country_at, degrees_at +from hqtui.widgets.world import WorldMapOptions, country_at_cell + +PLACES = [ + ("Paris", 2.35, 48.86), + ("Tokyo", 139.7, 35.7), + ("Cairo", 31.2, 30.0), + ("Brasilia", -47.9, -15.8), + ("Canberra", 149.1, -35.3), + ("Denver", -105.0, 39.7), + ("Moscow", 37.6, 55.75), + ("Delhi", 77.2, 28.6), + ("Nairobi", 36.8, -1.3), + ("Pacific", -140.0, 0.0), + ("Atlantic", -30.0, 0.0), + ("SouthernOcean", 80.0, -40.0), + ("NorthPacific", -150.0, 40.0), +] + +for name, lon, lat in PLACES: + found = country_at(lon, lat) + print(f"{name} {found.name if found else '-'}") + +# The cell path, which has to agree with what the canvas drew. +for column, row in [(173, 28), (74, 2), (20, 25), (88, 7)]: + found = country_at_cell(column, row, 200, 50, WorldMapOptions()) + print(f"cell:{column},{row} {found.name if found else '-'}") + +# And the projection itself, so a drift shows up as a number rather than as a +# country that happens to still be right. +for column, row in [(0, 0), (99, 25), (50, 13)]: + lon, lat = degrees_at(column, row, 100, 26, WORLD_X, WORLD_Y) + print(f"degrees:{column},{row} {lon:.4f} {lat:.4f}") diff --git a/ports/python/hqtui/graphics/__init__.py b/ports/python/hqtui/graphics/__init__.py index 9bf5a80..36801d7 100644 --- a/ports/python/hqtui/graphics/__init__.py +++ b/ports/python/hqtui/graphics/__init__.py @@ -15,6 +15,18 @@ vertical_glyph, ) from .braille import BrailleCanvas +from .world import ( + WORLD_COUNTRIES, + WORLD_X, + WORLD_Y, + CountryOutline, + WorldShapeOptions, + country_at, + country_bounds, + degrees_at, + find_country, + world_shapes, +) from .canvas import ( Bounds, CanvasOptions, @@ -54,6 +66,16 @@ ) __all__ = [ + "WORLD_COUNTRIES", + "WORLD_X", + "WORLD_Y", + "CountryOutline", + "WorldShapeOptions", + "country_at", + "country_bounds", + "degrees_at", + "find_country", + "world_shapes", "Bounds", "CanvasOptions", "Projection", diff --git a/ports/python/hqtui/graphics/world.py b/ports/python/hqtui/graphics/world.py new file mode 100644 index 0000000..d4f3de0 --- /dev/null +++ b/ports/python/hqtui/graphics/world.py @@ -0,0 +1,183 @@ +"""The world, as shapes for the canvas, and the lookup that makes it clickable. + +The canvas already draws in the caller's own coordinates, and longitude and +latitude are just another pair of axes — so a map is a list of polylines in +degrees, and nothing here needs a projection of its own beyond deciding which +window on the globe to show. + +The interesting half is the other direction. A click arrives as a terminal cell, +and a country is a polygon, so answering "what did they click" means turning the +cell back into degrees and testing it against the outlines. Doing it that way +rather than with bounding boxes is what makes the answer right: Russia's +bounding box covers most of the northern hemisphere, and Chile's covers +Argentina. +""" + +from __future__ import annotations + +import math +from dataclasses import dataclass, field +from typing import Sequence + +from ..color import Color +from .canvas import Bounds, Shape +from .world_data import CountryOutline, WORLD_COUNTRIES + +__all__ = [ + "WORLD_COUNTRIES", + "WORLD_X", + "WORLD_Y", + "CountryOutline", + "WorldShapeOptions", + "country_at", + "country_bounds", + "degrees_at", + "find_country", + "world_shapes", +] + +#: The whole globe, which is what a map shows unless told otherwise. +WORLD_X = Bounds(-180.0, 180.0) +WORLD_Y = Bounds(-90.0, 90.0) + + +@dataclass(frozen=True, slots=True) +class WorldShapeOptions: + #: Colour for countries with nothing special about them. + color: Color | None = None + #: Countries to pick out, by name or ISO code. + highlight: Sequence[str] = () + highlight_color: Color | None = None + + +def _matches(country: CountryOutline, keys: Sequence[str]) -> bool: + """Match on either the name or the ISO code, case-insensitively.""" + for key in keys: + if not key: + continue + if country.name.lower() == key.lower(): + return True + if country.iso and country.iso.lower() == key.lower(): + return True + return False + + +def world_shapes(options: WorldShapeOptions = WorldShapeOptions()) -> list[Shape]: + """The world as canvas shapes, one polyline per landmass. + + Polylines rather than scattered points: the outlines are closed rings, so + joining them draws a coastline instead of a dotted suggestion of one, and it + reads at a fraction of the resolution dots would need. + """ + shapes: list[Shape] = [] + for country in WORLD_COUNTRIES: + picked = bool(options.highlight) and _matches(country, options.highlight) + color = options.highlight_color if picked else options.color + if picked and color is None: + color = options.color + for ring in country.rings: + points = [(ring[i], ring[i + 1]) for i in range(0, len(ring) - 1, 2)] + # Closed: the last point joins the first, or every country has a gap + # in its coastline where the ring started. + if points: + points.append(points[0]) + shapes.append(Shape(kind="polyline", points=tuple(points), color=color)) + return shapes + + +def _inside_ring(ring: Sequence[float], lon: float, lat: float) -> bool: + """Whether a point is inside a ring, by ray casting. + + The ring is a flat list of interleaved coordinates, so this walks it two at + a time rather than allocating a pair per vertex — it runs once per country + per click, and there are a couple of thousand vertices. + """ + inside = False + n = len(ring) // 2 + if n == 0: + return False + j = n - 1 + for i in range(n): + xi, yi = ring[i * 2], ring[i * 2 + 1] + xj, yj = ring[j * 2], ring[j * 2 + 1] + if (yi > lat) != (yj > lat) and lon < (xj - xi) * (lat - yi) / (yj - yi) + xi: + inside = not inside + j = i + return inside + + +def country_at(lon: float, lat: float) -> CountryOutline | None: + """The country containing a point, or None for open water. + + Where outlines overlap — and at this resolution simplified borders do + overlap — the first match wins, which is stable because the data is sorted + by name. + """ + if not math.isfinite(lon) or not math.isfinite(lat): + return None + for country in WORLD_COUNTRIES: + for ring in country.rings: + if _inside_ring(ring, lon, lat): + return country + return None + + +def find_country(key: str) -> CountryOutline | None: + """Look a country up by name or ISO code.""" + for country in WORLD_COUNTRIES: + if _matches(country, (key,)): + return country + return None + + +def country_bounds(country: CountryOutline, margin: float = 0.08) -> tuple[Bounds, Bounds]: + """The window a country fills, with a little room around it. + + For zooming a map to a country: the bounding box alone puts the coastline + flat against the edge of the panel, which reads as though the country has + been cut off rather than framed. + """ + min_lon = min_lat = math.inf + max_lon = max_lat = -math.inf + for ring in country.rings: + for i in range(0, len(ring) - 1, 2): + min_lon = min(min_lon, ring[i]) + max_lon = max(max_lon, ring[i]) + min_lat = min(min_lat, ring[i + 1]) + max_lat = max(max_lat, ring[i + 1]) + if not math.isfinite(min_lon): + return WORLD_X, WORLD_Y + # A single-point country would give a zero-width window, which cannot be + # mapped onto anything. + pad_x = max((max_lon - min_lon) * margin, 1.0) + pad_y = max((max_lat - min_lat) * margin, 1.0) + return ( + Bounds(min_lon - pad_x, max_lon + pad_x), + Bounds(min_lat - pad_y, max_lat + pad_y), + ) + + +def degrees_at( + column: int, + row: int, + width: int, + height: int, + x: Bounds = WORLD_X, + y: Bounds = WORLD_Y, +) -> tuple[float, float] | None: + """The degrees under a terminal cell, given the window the map was drawn with. + + The inverse of what the canvas does on the way in, taken at the centre of + the cell: a click lands on a whole cell, and the centre is the only point in + it that is not arbitrarily nearer one neighbour than the other. + """ + if width <= 0 or height <= 0: + return None + # The canvas is 2x4 Braille pixels per cell, and it spans its bounds across + # ``pixels - 1``, so the inverse has to use the same denominators or a click + # drifts from what was drawn. + px = max(1, width * 2 - 1) + py = max(1, height * 4 - 1) + lon = x.min + ((column * 2 + 1) / px) * (x.max - x.min) + lat = y.min + (1 - (row * 4 + 2) / py) * (y.max - y.min) + return lon, lat diff --git a/ports/python/hqtui/graphics/world_data.py b/ports/python/hqtui/graphics/world_data.py new file mode 100644 index 0000000..21d2fca --- /dev/null +++ b/ports/python/hqtui/graphics/world_data.py @@ -0,0 +1,1314 @@ +""" + Country outlines, flattened for a terminal. + + Generated by packages/hqtui/scripts/generate-world.ts from Natural Earth's + 1:110m Admin 0 countries, which is public domain. Do not edit by hand. + + Each ring is longitude and latitude interleaved -- lon, lat, lon, lat -- + rather than a list of pairs, because at 1982 points the nested form + costs a container per coordinate for no gain. A country has more than one + ring when it is more than one landmass. + + 171 countries, 1982 points, simplified at 1 degrees. +""" + +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass(frozen=True, slots=True) +class CountryOutline: + name: str + #: ISO 3166-1 alpha-2, where Natural Earth has one. + iso: str + #: Longitude and latitude, interleaved. + rings: tuple[tuple[float, ...], ...] + + +WORLD_COUNTRIES: tuple[CountryOutline, ...] = ( + CountryOutline( + "Afghanistan", + "AF", + ( + (66.5,37.4,70.8,38.5,71.8,36.7,75.2,37.1,71.3,36.1,69.3,31.9,66.3,29.9,60.9,29.8,61.2,35.7,66.5,37.4), + ), + ), + CountryOutline( + "Albania", + "AL", + ( + (21,40.8,19.4,40.3,19.7,42.7,21,40.8), + ), + ), + CountryOutline( + "Algeria", + "DZ", + ( + (-8.7,27.4,-8.7,28.8,-1.3,32.3,-1.2,35.7,8.4,36.9,7.5,34.1,9.8,29.4,9.3,26.1,12,23.5,3.2,19.1,-8.7,27.4), + ), + ), + CountryOutline( + "Angola", + "AO", + ( + (12.3,-6.1,16.3,-5.9,17.5,-8.1,21.7,-7.3,22.2,-11.1,24,-11.2,24,-12.9,21.9,-12.9,23.2,-17.5,11.7,-17.3,13.7,-11.3,12.3,-6.1), + ), + ), + CountryOutline( + "Antarctica", + "AQ", + ( + (-48.7,-78,-43.9,-78.5,-43.3,-80,-54.2,-80.6,-48.7,-78), + (-66.3,-80.3,-59.6,-80,-66.3,-80.3), + (-73.9,-71.3,-70.3,-68.9,-68.3,-71.4,-75,-72.1,-73.9,-71.3), + (-102.3,-71.9,-96.2,-72.5,-102.3,-71.9), + (-122.6,-73.7,-118.7,-73.5,-122.6,-73.7), + (-127.3,-73.5,-124,-73.9,-127.3,-73.5), + (-163.7,-78.6,-159.2,-79.5,-163.7,-78.6), + (180,-84.7,180,-90,-180,-90,-179.1,-84.1,-143.1,-85,-153.6,-83.7,-152.9,-82,-156.8,-81.1,-146.4,-80.3,-155.3,-79.1,-158.4,-76.9,-151.3,-77.4,-146.1,-76.5,-146.2,-75.4,-135.2,-74.3,-100.1,-74.9,-103.7,-72.6,-74.9,-73.9,-67.4,-72.5,-67.7,-67.3,-57.8,-63.3,-65.7,-68,-61.8,-70.7,-60.8,-73.7,-70.6,-76.6,-77.2,-76.7,-73.7,-77.9,-78,-79.2,-58.2,-83.2,-28.5,-80.3,-35.6,-79.5,-35.8,-78.3,-17.5,-75.1,-15.4,-73.1,-6.9,-70.9,27.1,-70.5,33.9,-68.5,38.6,-69.8,54.5,-65.8,61.4,-68,68.9,-67.9,69.7,-69.2,67.9,-71.9,69.9,-72.3,73.9,-69.9,88,-66.2,95.8,-67.4,102.8,-65.6,106.2,-66.9,113.6,-65.9,119.8,-67.3,135.1,-65.3,137.5,-67,145.5,-66.9,171.2,-71.7,163.6,-76.2,167,-78.8,161.8,-79.2,159.8,-80.9,169.4,-83.8,180,-84.7), + ), + ), + CountryOutline( + "Argentina", + "AR", + ( + (-68.6,-52.6,-65,-54.7,-68.6,-54.9,-68.6,-52.6), + (-57.6,-30.2,-58.5,-34.4,-56.8,-36.9,-62.3,-38.8,-62.7,-41,-65.1,-41.1,-63.5,-42.6,-67.3,-45.6,-65.6,-47.2,-69.1,-50.7,-68.1,-52.3,-71.9,-52,-73.4,-49.3,-71.2,-44.8,-72.1,-42.3,-68.4,-24.5,-66.3,-21.8,-62.8,-22,-57.8,-25.2,-58.6,-27.1,-55.7,-27.4,-54.1,-25.5,-53.6,-26.9,-57.6,-30.2), + ), + ), + CountryOutline( + "Armenia", + "AM", + ( + (46.5,38.8,43.6,41.1,45.6,40.8,46.5,38.8), + ), + ), + CountryOutline( + "Australia", + "AU", + ( + (147.7,-40.8,147.9,-43.2,146,-43.5,144.7,-40.7,147.7,-40.8), + (126.1,-32.2,118,-35.1,115,-34.2,113.7,-22.5,120.9,-19.7,125.7,-14.2,129.6,-15,132.4,-11.1,136.5,-11.9,135.5,-15,140.2,-17.7,142.5,-10.7,146.4,-19,150.7,-22.4,153.6,-28.1,150,-37.4,146.3,-39,140.6,-38,138.2,-34.4,136.8,-35.3,137.8,-32.9,136,-34.9,131.3,-31.5,126.1,-32.2), + ), + ), + CountryOutline( + "Austria", + "AT", + ( + (17,48.1,14.6,46.4,9.5,47.1,12.9,47.5,13.6,48.9,17,48.1), + ), + ), + CountryOutline( + "Azerbaijan", + "AZ", + ( + (46.4,41.9,50.4,40.3,48.9,38.3,45.6,39.9,45,41.2,46.4,41.9), + ), + ), + CountryOutline( + "Bahamas", + "BS", + ( + (-78.2,25.2,-77.5,23.8,-78.2,25.2), + ), + ), + CountryOutline( + "Bangladesh", + "BD", + ( + (92.7,22,92.4,20.7,91.4,22.8,89,22.1,88.6,26.4,92.4,25,91.2,23.5,92.7,22), + ), + ), + CountryOutline( + "Belarus", + "BY", + ( + (28.2,56.2,30.9,55.6,32.7,53.4,31.8,52.1,23.5,51.6,23.5,53.9,28.2,56.2), + ), + ), + CountryOutline( + "Belgium", + "BE", + ( + (6.2,50.8,5.7,49.5,2.5,51.1,6.2,50.8), + ), + ), + CountryOutline( + "Belize", + "BZ", + ( + (-89.1,17.8,-88.1,18.3,-88.9,15.9,-89.1,17.8), + ), + ), + CountryOutline( + "Benin", + "BJ", + ( + (2.7,6.3,0.8,10.5,2.8,12.2,2.7,6.3), + ), + ), + CountryOutline( + "Bhutan", + "BT", + ( + (91.7,27.8,88.8,27.1,91.7,27.8), + ), + ), + CountryOutline( + "Bolivia", + "BO", + ( + (-69.5,-11,-65.3,-9.8,-65.4,-11.6,-60.5,-13.8,-60.2,-16.3,-58.2,-16.3,-57.9,-20,-61.8,-19.6,-62.7,-22.2,-67.8,-22.9,-69.5,-11), + ), + ), + CountryOutline( + "Bosnia and Herz.", + "BA", + ( + (18.6,42.7,16,45.2,19.4,44.9,18.6,42.7), + ), + ), + CountryOutline( + "Botswana", + "BW", + ( + (29.4,-22.1,25.7,-25.5,21.6,-26.7,19.9,-24.8,20.9,-18.3,25.3,-17.7,29.4,-22.1), + ), + ), + CountryOutline( + "Brazil", + "BR", + ( + (-53.4,-33.8,-53.8,-32,-57.6,-30.2,-53.6,-26.1,-55.8,-22.4,-57.9,-22.1,-58.2,-16.3,-60.2,-16.3,-60.5,-13.8,-65.4,-11.6,-65.3,-9.8,-70.5,-11,-70.5,-9.5,-72.2,-10.1,-74,-7.5,-72.9,-5.3,-69.9,-4.3,-69.8,1.7,-65.5,0.8,-63.4,2.2,-64.8,4.1,-60.7,5.2,-59,1.3,-52.9,2.1,-51.3,4.2,-50.4,-0.1,-44.6,-2.7,-40,-2.9,-35.6,-5.1,-34.7,-7.3,-38.7,-13.1,-40.9,-21.9,-47.6,-24.9,-48.9,-28.7,-53.4,-33.8), + ), + ), + CountryOutline( + "Bulgaria", + "BG", + ( + (22.7,44.2,28.6,43.7,28,42,23,41.3,22.7,44.2), + ), + ), + CountryOutline( + "Burkina Faso", + "BF", + ( + (-5.4,10.4,-4.3,13.2,-1.1,15,2.2,12.6,0.9,11,-2.9,11,-2.8,9.6,-5.4,10.4), + ), + ), + CountryOutline( + "Burundi", + "BI", + ( + (30.5,-2.4,29.3,-4.5,29,-2.8,30.5,-2.4), + ), + ), + CountryOutline( + "Cambodia", + "KH", + ( + (102.6,12.2,103,14.2,107.6,13.5,106.2,11,103.5,10.6,102.6,12.2), + ), + ), + CountryOutline( + "Cameroon", + "CM", + ( + (14.5,12.9,14.5,4.7,15.9,1.7,9.6,2.3,8.8,5.5,11.7,7,14.5,12.9), + ), + ), + CountryOutline( + "Canada", + "CA", + ( + (-122.8,49,-127.4,50.8,-130.5,54.3,-130,55.9,-135.5,59.8,-137.5,58.9,-141,60.3,-141,69.7,-136.5,68.9,-128.1,70.5,-113.5,67.7,-106.1,68.8,-101.5,67.6,-97.7,68.6,-96.1,67.3,-94.2,69.1,-96.5,70.1,-95.2,71.9,-87.4,67.2,-85.5,69.9,-82.6,69.7,-81.4,67.1,-85.8,66.6,-90.7,63.6,-94.7,58.9,-92.3,57.1,-82.3,55.1,-79.9,51.2,-78.6,52.6,-79.8,54.7,-76.5,56.5,-78.5,58.8,-77.3,59.9,-78.1,62.3,-73.8,62.4,-69.6,61.1,-67.6,58.2,-64.6,60.3,-61.8,56.3,-57.3,54.6,-55.7,52.1,-60,50.2,-66.4,50.2,-71.1,46.8,-65.1,49.2,-64.5,46.2,-60.5,47,-59.8,45.9,-65.4,43.5,-66.2,44.5,-64.4,45.3,-67.1,45.1,-69.2,47.4,-71.5,45,-82.4,41.7,-82.6,45.3,-88.4,48.3,-122.8,49), + (-84,62.5,-81.9,62.9,-84,62.5), + (-79.8,72.8,-80.8,73.7,-76.3,72.8,-79.8,72.8), + (-93.6,75,-96.8,74.9,-93.6,75), + (-93.8,77.5,-96.4,77.8,-93.8,77.5), + (-96.8,78.8,-95.6,78.4,-98.6,78.9,-96.8,78.8), + (-88.2,74.4,-97.1,76.8,-79.8,74.9,-88.2,74.4), + (-111.3,78.2,-109.9,78,-113.5,77.7,-111.3,78.2), + (-111,78.8,-109.7,78.6,-112.5,78.4,-111,78.8), + (-55.6,51.3,-56.8,49.8,-53.5,49.2,-53.1,46.7,-59.3,47.6,-55.6,51.3), + (-83.9,65.1,-80.1,63.7,-87.2,63.5,-85.9,65.7,-83.9,65.1), + (-78.8,72.4,-68.8,70.5,-67,69.2,-68.8,68.7,-61.9,66.9,-63.9,65,-68,66.3,-64.7,63.4,-68.8,63.7,-66.2,61.9,-68.9,62.3,-78.6,64.6,-74,65.5,-73.3,68.1,-79,70.2,-88.7,70.4,-90.2,72.2,-85.8,73.8,-85.8,72.5,-82.3,73.8,-78.8,72.4), + (-94.5,74.1,-90.5,73.9,-95.4,72.1,-96,73.4,-94.5,74.1), + (-122.9,76.1,-116.2,77.6,-122.9,76.1), + (-132.7,54,-131.2,52.2,-132.7,54), + (-105.5,79.3,-99.7,77.9,-105.5,79.3), + (-123.5,48.5,-128.4,50.8,-123.5,48.5), + (-121.5,74.4,-115.5,73.5,-123.1,70.9,-125.9,71.9,-123.9,73.7,-124.9,74.3,-121.5,74.4), + (-107.8,75.8,-105.7,75.5,-117.7,75.2,-115.4,76.5,-107.8,75.8), + (-106.5,73.1,-101.1,69.6,-113.3,68.5,-117.3,70,-112.4,70.4,-119.4,71.6,-115.2,73.3,-108.2,71.7,-108.4,73.1,-106.5,73.1), + (-100.4,72.7,-101.5,73.4,-97.4,73.8,-96.5,72.6,-98.4,71.3,-102.5,72.5,-100.4,72.7), + (-106.6,73.6,-104.5,73.4,-106.6,73.6), + (-98.5,76.7,-98.2,75,-102.5,75.6,-98.5,76.7), + (-96,80.6,-92.4,81.3,-85.8,79.3,-92.9,78.3,-96,80.6), + (-91.6,81.9,-61.8,82.6,-76.9,79.3,-75.4,78.5,-80.6,76.2,-89.5,76.5,-88.3,77.9,-85,77.5,-88,78.4,-85.1,79.3,-86.9,80.3,-81.8,80.5,-91.6,81.9), + (-75.2,67.4,-77.2,67.6,-75.2,67.4), + (-96.3,69.5,-99.8,69.4,-96.3,69.5), + (-64.5,49.9,-61.8,49.1,-64.5,49.9), + (-64,47,-62,46.4,-64,47), + ), + ), + CountryOutline( + "Central African Rep.", + "CF", + ( + (27.4,5.2,22.4,4,19.5,5,16,2.3,14.5,5.5,15.3,7.4,22.9,11.1,27.4,5.2), + ), + ), + CountryOutline( + "Chad", + "TD", + ( + (23.8,19.6,23.9,15.6,21.9,12.6,22.9,11.1,15.3,7.4,13.5,14.4,15.9,20.4,14.9,22.9,23.8,19.6), + ), + ), + CountryOutline( + "Chile", + "CL", + ( + (-68.6,-52.6,-68.6,-54.9,-67,-54.9,-68.1,-55.6,-74.7,-52.8,-71.1,-54.1,-68.6,-52.6), + (-69.6,-17.6,-67,-23,-70.5,-31.4,-69.8,-34.2,-72.1,-42.3,-71.2,-44.8,-73.4,-49.3,-71.9,-52,-68.6,-52.3,-71.4,-53.9,-74.9,-52.3,-75.6,-48.7,-74.1,-46.9,-75.6,-46.6,-72.7,-42.4,-74.3,-43.2,-69.6,-17.6), + ), + ), + CountryOutline( + "China", + "CN", + ( + (109.5,18.2,108.6,19.4,110.8,20.1,109.5,18.2), + (80.3,42.3,80,44.9,87.8,49.3,91,46.9,90.9,45.3,96.3,42.7,109.2,42.5,111.9,45.1,119.7,46.7,115.5,48.1,122.2,53.4,125.9,52.8,131,47.8,135,48.5,133.1,45.1,131,45,130.6,42.4,121.1,38.9,121.6,40.9,117.5,38.7,122.4,37.5,119.2,34.9,121.9,31.7,121.7,28.2,118.7,24.5,110.4,20.3,105.3,23.4,101.7,22.3,101.8,21.2,99.2,22.1,97.6,23.9,98.7,27.5,96.1,29.5,88.8,27.3,78.7,31.5,78.9,34.3,73.7,39.4,80.3,42.3), + ), + ), + CountryOutline( + "Colombia", + "CO", + ( + (-66.9,1.3,-69.8,1.7,-69.9,-4.3,-70,-2.7,-77.4,0.4,-79,1.7,-77.1,3.8,-77.5,8.5,-71.4,12.4,-73.3,9.2,-72,7,-67.3,6.1,-66.9,1.3), + ), + ), + CountryOutline( + "Congo", + "CG", + ( + (18.5,3.5,16,-3.5,11.9,-5,11.5,-2.8,14.4,-1.3,13.1,2.3,15.9,1.7,18.5,3.5), + ), + ), + CountryOutline( + "Costa Rica", + "CR", + ( + (-82.5,9.6,-83,8.2,-85.9,10.9,-82.5,9.6), + ), + ), + CountryOutline( + "Côte d'Ivoire", + "CI", + ( + (-8,10.2,-2.8,9.6,-2.9,5,-7.7,4.4,-8,10.2), + ), + ), + CountryOutline( + "Croatia", + "HR", + ( + (16.6,46.5,19.4,45.2,15.8,44.8,18.5,42.5,13.7,45.1,16.6,46.5), + ), + ), + CountryOutline( + "Cuba", + "CU", + ( + (-82.3,23.2,-74.2,20.3,-77.8,19.9,-81.8,22.6,-85,21.9,-82.3,23.2), + ), + ), + CountryOutline( + "Cyprus", + "CY", + ( + (32.7,35.1,34,35,32.7,35.1), + ), + ), + CountryOutline( + "Czechia", + "CZ", + ( + (15,51.1,18.9,49.5,12.5,49.5,15,51.1), + ), + ), + CountryOutline( + "Dem. Rep. Congo", + "CD", + ( + (29.3,-4.5,30.7,-8.3,28.7,-8.5,28.4,-11.8,29.7,-13.3,22.2,-11.1,21.7,-7.3,17.5,-8.1,16.3,-5.9,12.2,-5.8,16,-3.5,19.5,5,29.7,4.6,31.2,2.2,29.3,-4.5), + ), + ), + CountryOutline( + "Denmark", + "DK", + ( + (9.9,55,8.1,56.5,10.6,57.7,9.9,55), + (12.4,56.1,12.1,54.8,11,55.4,12.4,56.1), + ), + ), + CountryOutline( + "Djibouti", + "DJ", + ( + (42.4,12.5,42.8,10.9,42.4,12.5), + ), + ), + CountryOutline( + "Dominican Rep.", + "DO", + ( + (-71.7,18,-71.6,19.9,-68.3,18.6,-71.7,18), + ), + ), + CountryOutline( + "Ecuador", + "EC", + ( + (-75.4,-0.2,-78.6,-4.5,-80.4,-4.4,-80.1,0.8,-75.4,-0.2), + ), + ), + CountryOutline( + "Egypt", + "EG", + ( + (36.9,22,25,22,25.2,31.6,34.3,31.2,34.2,27.8,32.3,29.8,36.9,22), + ), + ), + CountryOutline( + "El Salvador", + "SV", + ( + (-89.4,14.4,-87.9,13.1,-90.1,13.7,-89.4,14.4), + ), + ), + CountryOutline( + "Eq. Guinea", + "GQ", + ( + (9.6,2.3,11.3,1.1,9.5,1,9.6,2.3), + ), + ), + CountryOutline( + "Eritrea", + "ER", + ( + (36.4,14.4,38.4,18,43.1,12.7,36.4,14.4), + ), + ), + CountryOutline( + "Estonia", + "EE", + ( + (28,59.5,27.3,57.5,23.3,59.2,28,59.5), + ), + ), + CountryOutline( + "eSwatini", + "SZ", + ( + (32.1,-26.7,31,-25.7,32.1,-26.7), + ), + ), + CountryOutline( + "Ethiopia", + "ET", + ( + (47.8,8,45,5,39.6,3.4,36.2,4.4,33,7.8,37.9,15,41.6,13.5,43.7,9.2,47.8,8), + ), + ), + CountryOutline( + "Falkland Is.", + "FK", + ( + (-61.2,-51.8,-57.7,-51.5,-61.2,-51.8), + ), + ), + CountryOutline( + "Finland", + "FI", + ( + (28.6,69.1,31.1,62.4,28.1,60.5,21.3,60.7,21.5,63.2,25.4,65.1,20.6,69.1,24.7,68.6,27.7,70.2,28.6,69.1), + ), + ), + CountryOutline( + "Fr. S. Antarctic Lands", + "TF", + ( + (68.9,-48.6,70.6,-49.3,68.7,-49.8,68.9,-48.6), + ), + ), + CountryOutline( + "France", + "FR", + ( + (-51.7,4.2,-52.9,2.1,-54.5,2.3,-54,5.8,-51.7,4.2), + (6.2,49.5,8.1,49,6,46.7,7.4,43.7,1.8,42.3,-1.9,43.4,-1.2,46,-4.6,48.7,-1.6,48.6,-1.9,49.8,2.5,51.1,6.2,49.5), + (8.7,42.6,9.2,41.4,8.7,42.6), + ), + ), + CountryOutline( + "Gabon", + "GA", + ( + (11.3,2.3,14.3,1.2,14.4,-1.3,11.1,-4,8.8,-1.1,11.3,2.3), + ), + ), + CountryOutline( + "Gambia", + "GM", + ( + (-16.7,13.6,-13.8,13.5,-16.7,13.6), + ), + ), + CountryOutline( + "Georgia", + "GE", + ( + (40,43.4,46.6,41.2,41.6,41.5,40,43.4), + ), + ), + CountryOutline( + "Germany", + "DE", + ( + (14.1,53.8,15,51.1,12.2,50.3,12.9,47.5,7.5,47.6,8.1,49,6,50.1,7.1,53.7,9.9,55,14.1,53.8), + ), + ), + CountryOutline( + "Ghana", + "GH", + ( + (0,11,1.1,5.9,-2.9,5,-2.9,11,0,11), + ), + ), + CountryOutline( + "Greece", + "GR", + ( + (26.3,35.3,23.5,35.3,26.3,35.3), + (23,41.3,26.6,41.6,22.6,40.3,24,37.7,22.5,36.4,20.2,39.6,23,41.3), + ), + ), + CountryOutline( + "Greenland", + "GL", + ( + (-46.8,82.6,-27.1,83.5,-20.8,82.7,-31.9,82.2,-12.2,81.3,-20,80.2,-17.7,80.1,-19.7,78.8,-18.5,77,-21.7,76.6,-19.4,74.3,-24.8,72.3,-21.8,70.7,-25.5,71.4,-26.4,70.2,-22.3,70.1,-39.8,65.5,-43.4,60.1,-48.3,60.9,-51.6,63.6,-54,67.2,-50.9,69.9,-54.7,69.6,-54.4,70.8,-51.4,70.6,-55.8,71.7,-54.7,72.6,-58.6,75.5,-68.5,76.1,-71.4,77,-66.8,77.4,-73.3,78,-65.7,79.4,-68,80.1,-62.7,81.8,-44.5,81.7,-46.8,82.6), + ), + ), + CountryOutline( + "Guatemala", + "GT", + ( + (-92.2,14.5,-90.5,16.1,-91,17.8,-89.1,17.8,-88.2,15.7,-89.4,14.4,-92.2,14.5), + ), + ), + CountryOutline( + "Guinea", + "GN", + ( + (-13.7,12.6,-9.1,12.3,-8.3,7.7,-11.1,10,-13.2,8.9,-15.1,11,-13.7,12.6), + ), + ), + CountryOutline( + "Guinea-Bissau", + "GW", + ( + (-16.7,12.4,-13.7,11.8,-15.1,11,-16.7,12.4), + ), + ), + CountryOutline( + "Guyana", + "GY", + ( + (-56.5,1.9,-59.6,1.8,-61.4,6,-59.8,8.4,-57.1,6,-58,4.1,-56.5,1.9), + ), + ), + CountryOutline( + "Haiti", + "HT", + ( + (-71.7,19.7,-71.7,18,-74.5,18.3,-71.7,19.7), + ), + ), + CountryOutline( + "Honduras", + "HN", + ( + (-83.1,15,-87.3,13,-89.4,14.4,-87.9,15.9,-83.1,15), + ), + ), + CountryOutline( + "Hungary", + "HU", + ( + (22.1,48.4,21,46.3,16.2,46.9,17,48.1,22.1,48.4), + ), + ), + CountryOutline( + "Iceland", + "IS", + ( + (-14.5,66.5,-13.6,65.1,-18.7,63.5,-22.8,64,-21.8,64.4,-24,64.9,-22.2,65.4,-24.3,65.6,-14.5,66.5), + ), + ), + CountryOutline( + "India", + "IN", + ( + (97.3,28.3,92.7,22,91.2,23.5,92.4,25,88.6,26.4,88.9,21.7,80.3,15.9,79.9,10.4,77.5,8,72.6,21.4,70.5,20.9,68.2,23.7,71,24.4,69.5,26.9,75.3,32.3,73.7,34.3,77.8,35.5,78.7,31.5,81.1,30.2,80.1,28.8,83.3,27.4,88.1,26.4,88.7,28.1,92,26.8,96.1,29.5,97.3,28.3), + ), + ), + CountryOutline( + "Indonesia", + "ID", + ( + (141,-2.6,141,-9.1,137.6,-8.4,137.9,-5.4,133,-4.1,132,-2.8,133.7,-2.2,130.5,-0.9,134,-0.8,135.5,-3.4,137.4,-1.7,141,-2.6), + (125,-8.9,123.5,-10.2,125,-8.9), + (117.9,4.1,119,0.9,116.1,-4,110.2,-2.9,109.7,2,110.5,0.8,113.8,1.2,115.9,4.3,117.9,4.1), + (129.4,-2.8,130.8,-3.9,127.9,-3.4,129.4,-2.8), + (127.9,2.2,128.1,-0.9,127.9,2.2), + (122.9,0.9,125.2,1.4,120,-0.5,123.3,-0.6,121.5,-1.9,123.2,-5.3,121,-2.6,119.8,-5.7,118.8,-2.8,119.8,0.2,122.9,0.9), + (120.3,-10.3,119,-9.6,120.3,-10.3), + (121.3,-8.5,122.9,-8.1,119.9,-8.8,121.3,-8.5), + (118.3,-8.4,116.7,-9,118.3,-8.4), + (108.5,-6.4,115.7,-8.4,105.4,-6.9,108.5,-6.4), + (104.4,-1.1,106.1,-3.1,105.8,-5.9,102.6,-4.2,95.3,5.5,97.5,5.2,104.4,-1.1), + ), + ), + CountryOutline( + "Iran", + "IR", + ( + (48.6,29.9,45.4,34,46.1,35.7,44.1,39.4,48.1,39.6,50.8,36.9,56.6,38.1,61.1,36.5,60.9,29.8,63.3,26.8,61.5,25.1,57.4,25.7,48.6,29.9), + ), + ), + CountryOutline( + "Iraq", + "IQ", + ( + (39.2,32.2,41.3,36.4,44.8,37.2,48.6,29.9,44.7,29.2,39.2,32.2), + ), + ), + CountryOutline( + "Ireland", + "IE", + ( + (-6.2,53.9,-6.8,52.3,-10,51.8,-7.6,55.1,-6.2,53.9), + ), + ), + CountryOutline( + "Israel", + "IL", + ( + (35.7,32.7,34.9,29.5,34.3,31.2,35.7,32.7), + ), + ), + CountryOutline( + "Italy", + "IT", + ( + (10.4,46.9,13.8,46.5,12.6,44.1,18.3,39.8,16.9,40.4,15.7,37.9,15.4,40,10.2,43.9,7.4,43.7,6.8,46,10.4,46.9), + (14.8,38.1,15.1,36.6,12.4,37.6,14.8,38.1), + (8.7,40.9,9.8,40.5,8.8,38.9,8.7,40.9), + ), + ), + CountryOutline( + "Jamaica", + "JM", + ( + (-77.6,18.5,-76.2,17.9,-77.6,18.5), + ), + ), + CountryOutline( + "Japan", + "JP", + ( + (141.9,39.2,140.3,35.1,135.8,33.5,135.1,34.6,131,33.9,132,33.1,130.2,31.4,129.4,33.3,139.4,38.2,140.3,41.2,141.9,39.2), + (144.6,44,145.5,43.3,140,41.6,142,45.6,144.6,44), + (132.4,33.5,134.8,33.8,132.4,33.5), + ), + ), + CountryOutline( + "Jordan", + "JO", + ( + (35.5,32.4,38.8,33.4,39.2,32.2,37,31.5,38,30.5,36.1,29.2,34.9,29.5,35.5,32.4), + ), + ), + CountryOutline( + "Kazakhstan", + "KZ", + ( + (87.4,49.2,80,44.9,80.3,42.3,74.2,43.3,68.6,40.7,64.9,43.7,62,43.5,58.5,45.6,55.9,45,56,41.3,52.5,41.8,50.3,44.6,53,45.3,53,46.9,49.1,46.4,46.5,48.4,50.8,51.7,61.3,50.8,60,52,61.4,54,69.1,55.4,73.4,53.5,76.9,54.5,80,50.9,87.4,49.2), + ), + ), + CountryOutline( + "Kenya", + "KE", + ( + (39.2,-4.7,33.9,-0.9,35.3,5.5,38.1,3.6,41.9,3.9,41.6,-1.7,39.2,-4.7), + ), + ), + CountryOutline( + "Kosovo", + "XK", + ( + (20.6,41.9,20.6,43.2,21.8,42.7,20.6,41.9), + ), + ), + CountryOutline( + "Kuwait", + "KW", + ( + (48,30,48.4,28.6,46.6,29.1,48,30), + ), + ), + CountryOutline( + "Kyrgyzstan", + "KG", + ( + (71,42.3,74.2,43.3,80.3,42.3,73.7,39.4,69.5,39.5,73.1,40.9,71,42.3), + ), + ), + CountryOutline( + "Laos", + "LA", + ( + (107.4,14.2,105.2,14.3,104,18.2,101.1,17.5,100.1,20.4,101.7,22.3,104.4,20.8,103.9,19.3,107.4,14.2), + ), + ), + CountryOutline( + "Latvia", + "LV", + ( + (27.3,57.5,28.2,56.2,26.5,55.6,21.1,56,22.5,57.8,27.3,57.5), + ), + ), + CountryOutline( + "Lebanon", + "LB", + ( + (35.8,33.3,36.4,34.6,35.8,33.3), + ), + ), + CountryOutline( + "Lesotho", + "LS", + ( + (29,-29,28.1,-30.5,27,-29.9,29,-29), + ), + ), + CountryOutline( + "Liberia", + "LR", + ( + (-8.4,7.7,-7.7,4.4,-11.4,6.8,-10.2,8.4,-8.4,7.7), + ), + ), + CountryOutline( + "Libya", + "LY", + ( + (25,22,23.8,19.6,10.3,24.4,10,31.4,11.5,33.1,19.1,30.3,20.9,32.7,24.9,31.9,25,22), + ), + ), + CountryOutline( + "Lithuania", + "LT", + ( + (26.5,55.6,23.5,53.9,21.1,56,26.5,55.6), + ), + ), + CountryOutline( + "Madagascar", + "MG", + ( + (49.5,-12.5,50.4,-15.7,47.1,-24.9,45.4,-25.6,43.3,-22.8,44,-17.4,49.5,-12.5), + ), + ), + CountryOutline( + "Malawi", + "MW", + ( + (32.8,-9.2,35.7,-14.6,35,-16.8,32.7,-13.7,32.8,-9.2), + ), + ), + CountryOutline( + "Malaysia", + "MY", + ( + (100.1,6.5,103,5.5,104.2,1.3,101.4,2.8,100.1,6.5), + (117.9,4.1,115.9,4.3,114.6,1.4,109.8,1.3,115.3,4.3,116.7,6.9,119.2,5.4,117.9,4.1), + ), + ), + CountryOutline( + "Mali", + "ML", + ( + (-11.5,12.4,-11.7,15.4,-5.5,15.5,-6.5,25,4.3,19.2,3.6,15.6,-4,13.5,-5.4,10.4,-11.5,12.4), + ), + ), + CountryOutline( + "Mauritania", + "MR", + ( + (-17.1,21,-12.9,21.3,-12,25.9,-8.7,25.9,-8.7,27.4,-4.9,25,-6.5,25,-5.5,15.5,-12.2,14.6,-14.6,16.6,-16.5,16.1,-17.1,21), + ), + ), + CountryOutline( + "Mexico", + "MX", + ( + (-117.1,32.5,-106.5,31.8,-103.9,29.3,-101.7,29.8,-97.1,25.9,-97.9,22.4,-95.9,18.8,-91.4,18.9,-90.3,21,-87.1,21.5,-87.8,18.3,-91,17.8,-90.5,16.1,-92.2,14.5,-103.5,18.3,-113.1,31.2,-114.9,31.4,-109.9,22.8,-115.1,27.7,-114.2,28.6,-117.1,32.5), + ), + ), + CountryOutline( + "Moldova", + "MD", + ( + (26.6,48.2,30,46.4,28.2,45.5,26.6,48.2), + ), + ), + CountryOutline( + "Mongolia", + "MN", + ( + (87.8,49.3,92.2,50.8,97.3,49.7,98.9,52,108.5,49.3,116.7,49.9,115.7,47.7,119.8,47,105,41.6,96.3,42.7,90.9,45.3,91,46.9,87.8,49.3), + ), + ), + CountryOutline( + "Montenegro", + "ME", + ( + (20.1,42.6,18.5,42.5,20.1,42.6), + ), + ), + CountryOutline( + "Morocco", + "MA", + ( + (-2.2,35.2,-1.3,32.3,-8.7,28.8,-8.8,27.1,-11.4,26.9,-14.8,21.5,-17,21.4,-14.4,26.3,-9.6,29.9,-8.7,33.2,-5.9,35.8,-2.2,35.2), + ), + ), + CountryOutline( + "Mozambique", + "MZ", + ( + (34.6,-11.5,40.3,-10.3,40.8,-14.7,34.8,-19.8,35.5,-24.1,32.1,-26.7,31.2,-22.3,32.8,-16.7,30.2,-14.8,33.2,-14,35,-16.8,34.6,-11.5), + ), + ), + CountryOutline( + "Myanmar", + "MM", + ( + (100.1,20.4,97.4,18.4,99.6,11.9,98.6,9.9,97.2,16.9,94.2,16,92.3,21.5,97.3,28.3,98.7,27.5,97.6,23.9,101.2,21.8,100.1,20.4), + ), + ), + CountryOutline( + "N. Cyprus", + "-99", + ( + (32.7,35.1,34.6,35.7,32.7,35.1), + ), + ), + CountryOutline( + "Namibia", + "NA", + ( + (19.9,-24.8,19.9,-28.5,16.3,-28.6,11.7,-17.3,25.1,-17.6,20.9,-18.3,19.9,-24.8), + ), + ), + CountryOutline( + "Nepal", + "NP", + ( + (88.1,27.9,87.2,26.4,80.1,28.8,81.5,30.4,88.1,27.9), + ), + ), + CountryOutline( + "Netherlands", + "NL", + ( + (6.9,53.5,6.2,50.8,3.3,51.3,6.9,53.5), + ), + ), + CountryOutline( + "New Caledonia", + "NC", + ( + (165.8,-21.1,167.1,-22.2,164,-20.1,165.8,-21.1), + ), + ), + CountryOutline( + "New Zealand", + "NZ", + ( + (176.9,-40.1,174.7,-41.3,174.7,-37.4,172.6,-34.5,176,-37.6,178.5,-37.7,176.9,-40.1), + (169.7,-43.6,172.8,-40.5,174.2,-41.3,170.6,-45.9,166.7,-46.2,169.7,-43.6), + ), + ), + CountryOutline( + "Nicaragua", + "NI", + ( + (-83.7,10.9,-87.7,12.9,-83.1,15,-83.7,10.9), + ), + ), + CountryOutline( + "Niger", + "NE", + ( + (14.9,22.9,15.9,20.4,13.5,14.4,14.2,12.5,5.4,13.9,3.6,11.7,1,12.9,0.4,14.9,3.6,15.6,4.3,19.2,12,23.5,14.9,22.9), + ), + ), + CountryOutline( + "Nigeria", + "NG", + ( + (2.7,6.3,4.4,13.7,13.1,13.6,14.6,12.1,11.7,7,8.5,4.8,5.9,4.3,2.7,6.3), + ), + ), + CountryOutline( + "North Korea", + "KP", + ( + (130.6,42.4,127.5,39.8,128.2,38.4,124.7,38.1,125.1,40.6,130.6,42.4), + ), + ), + CountryOutline( + "North Macedonia", + "MK", + ( + (22.4,42.3,23,41.3,20.6,41.1,22.4,42.3), + ), + ), + CountryOutline( + "Norway", + "NO", + ( + (15.1,79.7,21.5,79,15.9,76.8,10.4,79.7,15.1,79.7), + (31.1,69.6,18,68.6,12.6,64.1,11,58.9,5.7,58.6,5,62,19.2,69.8,28.2,71.2,31.1,69.6), + (27.4,80.1,17.4,80.3,27.4,80.1), + (24.7,77.9,20.7,77.7,24.7,77.9), + ), + ), + CountryOutline( + "Oman", + "OM", + ( + (55.2,22.7,56.4,24.9,59.8,22.3,57.7,18.9,53.1,16.7,52,19,55,20,55.2,22.7), + ), + ), + CountryOutline( + "Pakistan", + "PK", + ( + (77.8,35.5,73.7,34.3,75.3,32.3,69.5,26.9,71,24.4,61.5,25.1,63.3,26.8,60.9,29.8,66.3,29.9,71.8,36.5,75.2,37.1,77.8,35.5), + ), + ), + CountryOutline( + "Panama", + "PA", + ( + (-77.4,8.7,-77.9,7.2,-79.1,9,-80.9,7.2,-82.9,9.5,-77.4,8.7), + ), + ), + CountryOutline( + "Papua New Guinea", + "PG", + ( + (141,-2.6,147.6,-6.1,147.2,-7.4,150.7,-10.6,144.7,-7.6,141,-9.1,141,-2.6), + (152.6,-3.7,152.8,-4.8,150.7,-2.7,152.6,-3.7), + (151.3,-5.8,148.3,-5.7,152.1,-4.1,151.3,-5.8), + (154.8,-5.3,155.9,-6.8,154.8,-5.3), + ), + ), + CountryOutline( + "Paraguay", + "PY", + ( + (-58.2,-20.2,-57.9,-22.1,-54.3,-24,-55.7,-27.4,-58.6,-27.1,-57.8,-25.2,-62.7,-22.2,-61.8,-19.6,-58.2,-20.2), + ), + ), + CountryOutline( + "Peru", + "PE", + ( + (-69.9,-4.3,-72.9,-5.3,-74,-7.5,-68.7,-12.6,-70.4,-18.3,-76,-14.6,-81.4,-4.7,-80.3,-3.4,-78.6,-4.5,-75.1,-0.1,-73.1,-2.3,-70,-2.7,-69.9,-4.3), + ), + ), + CountryOutline( + "Philippines", + "PH", + ( + (122.6,10,124.1,11.2,123,9,122.6,10), + (126.4,8.4,125.4,5.6,123.6,7.8,121.9,7.2,125.4,9.8,126.4,8.4), + (118.5,9.3,117.2,8.4,119.5,11.4,118.5,9.3), + (122.3,18.2,121.7,14.3,124.1,12.5,119.9,15.4,120.7,18.5,122.3,18.2), + (125.5,12.2,124.8,10.1,124.3,12.6,125.5,12.2), + ), + ), + CountryOutline( + "Poland", + "PL", + ( + (23.5,53.9,24,50.7,22.8,49,16.2,50.4,14.1,53,17.6,54.9,23.5,53.9), + ), + ), + CountryOutline( + "Portugal", + "PT", + ( + (-9,41.9,-6.4,41.4,-7.9,36.8,-9.5,38.7,-9,41.9), + ), + ), + CountryOutline( + "Puerto Rico", + "PR", + ( + (-66.3,18.5,-67.2,17.9,-66.3,18.5), + ), + ), + CountryOutline( + "Qatar", + "QA", + ( + (50.8,24.8,51.3,26.1,50.8,24.8), + ), + ), + CountryOutline( + "Romania", + "RO", + ( + (28.2,45.5,29.6,45.3,28.6,43.7,22.9,43.8,20.2,46.1,26.6,48.2,28.2,45.5), + ), + ), + CountryOutline( + "Russia", + "RU", + ( + (49.1,46.4,46.7,44.6,47.8,41.2,36.7,45.2,40.1,49.6,31.8,52.1,32.7,53.4,30.9,55.6,27.3,57.5,29.1,60,28.1,60.5,31.5,62.9,30,63.6,28.6,69.1,32.1,69.9,41.1,67.5,38.4,66,33.2,66.6,37,63.8,37.2,65.1,43.9,66.1,43.5,68.6,46.3,68.3,46.3,66.7,53.7,68.9,59.9,68.3,60.6,69.9,68.5,68.1,66.7,71,69.9,73,72.8,72.2,71.8,71.4,73.7,68.4,71.3,66.3,72.4,66.2,75.1,67.8,73.1,71.4,74.7,72.8,76.4,71.2,81.5,71.8,80.5,73.6,104.4,77.7,114.1,75.8,109.4,74.2,127,73.6,131.3,70.8,139.9,71.5,139.1,72.4,140.5,72.8,159,70.9,160.9,69.4,180,69,180,65,177.4,64.6,179.2,62.3,170.3,59.9,163.5,59.9,162,58.2,163.2,57.6,162.1,54.9,156.8,51,155.9,56.8,164.5,62.6,160.1,60.5,156.7,61.4,154.2,59.8,155,59.1,142.2,59,135.1,54.7,141.3,53.1,140.1,48.4,134.9,43.4,130.8,42.2,131,45,133.1,45.1,135,48.5,131,47.8,123.6,53.5,120.2,52.8,117.9,49.5,108.5,49.3,98.9,52,97.3,49.7,92.2,50.8,87.4,49.2,80,50.9,76.9,54.5,73.4,53.5,69.1,55.4,61.4,54,60,52,61.3,50.8,50.8,51.7,47.5,50.5,46.5,48.4,49.1,46.4), + (93.8,81,100.2,79.8,97.8,78.8,91.2,80.3,93.8,81), + (102.8,79.3,105.4,78.7,99.4,77.9,102.8,79.3), + (138.8,76.1,145.1,75.6,137,75.3,138.8,76.1), + (148.2,75.3,150.7,75.1,146.1,75.2,148.2,75.3), + (139.9,73.4,143.6,73.2,139.9,73.4), + (44.8,80.6,51.5,80.7,44.8,80.6), + (22.7,54.3,19.7,54.4,22.7,54.3), + (53.5,73.7,61.2,76.3,68.9,76.5,58.5,74.3,55.4,72.4,57.5,70.7,51.6,71.5,53.5,73.7), + (142.9,53.7,144.7,49,143.2,49.3,143.5,46.1,142.1,46,141.7,53.3,142.9,53.7), + (-174.9,67.2,-169.9,66,-173,64.3,-178.7,66.1,-180,65,-180,69,-174.9,67.2), + (-178.7,70.9,-180,71.5,-177.6,71.3,-178.7,70.9), + (33.4,46,36.5,45.5,33.9,44.4,32.5,45.3,33.4,46), + ), + ), + CountryOutline( + "Rwanda", + "RW", + ( + (30.4,-1.1,29,-2.8,30.4,-1.1), + ), + ), + CountryOutline( + "S. Sudan", + "SS", + ( + (30.8,3.5,23.9,8.6,25.8,10.4,31.4,9.8,33.2,12.2,33,7.8,35.3,5.5,30.8,3.5), + ), + ), + CountryOutline( + "Saudi Arabia", + "SA", + ( + (35,29.4,39.2,32.2,47.5,29,52,23,55.2,22.7,55,20,47,16.9,43.4,17.6,42.8,16.3,35,29.4), + ), + ), + CountryOutline( + "Senegal", + "SN", + ( + (-16.7,13.6,-17.6,14.7,-14.6,16.6,-11.5,12.4,-16.7,12.4,-13.8,13.5,-16.7,13.6), + ), + ), + CountryOutline( + "Serbia", + "RS", + ( + (18.8,45.9,22.7,44.6,22.5,42.5,19.2,43.5,18.8,45.9), + ), + ), + CountryOutline( + "Sierra Leone", + "SL", + ( + (-13.2,8.9,-11.1,10,-10.2,8.4,-11.4,6.8,-13.2,8.9), + ), + ), + CountryOutline( + "Slovakia", + "SK", + ( + (22.6,49.1,16.9,48.5,22.6,49.1), + ), + ), + CountryOutline( + "Slovenia", + "SI", + ( + (13.8,46.5,16.6,46.5,15.3,45.5,13.8,46.5), + ), + ), + CountryOutline( + "Solomon Is.", + "SB", + ( + (159.6,-8,158.2,-7.4,159.6,-8), + ), + ), + CountryOutline( + "Somalia", + "SO", + ( + (41.6,-1.7,41,2.8,45,5,48.9,9.5,48.9,11.4,51.1,12,48.6,5.3,41.6,-1.7), + ), + ), + CountryOutline( + "Somaliland", + "-99", + ( + (48.9,11.4,47.8,8,42.6,10.6,48.9,11.4), + ), + ), + CountryOutline( + "South Africa", + "ZA", + ( + (16.3,-28.6,19.9,-28.5,19.9,-24.8,21.6,-26.7,25.7,-25.5,29.4,-22.1,31.2,-22.3,31.9,-24.4,30.7,-26.7,32.8,-26.7,28.2,-32.8,20.1,-34.8,18.4,-34.1,16.3,-28.6), + ), + ), + CountryOutline( + "South Korea", + "KR", + ( + (126.2,37.7,128.3,38.6,129.1,35.1,126.5,34.4,126.2,37.7), + ), + ), + CountryOutline( + "Spain", + "ES", + ( + (-7.5,37.1,-6.4,41.4,-9.4,43,3,42.5,-2.1,36.7,-7.5,37.1), + ), + ), + CountryOutline( + "Sri Lanka", + "LK", + ( + (81.8,7.5,80.3,6,80.1,9.8,81.8,7.5), + ), + ), + CountryOutline( + "Sudan", + "SD", + ( + (24.6,8.2,21.9,12.6,25,22,36.9,22,38.4,18,34,8.7,32.7,12.2,31.4,9.8,25.1,10.3,24.6,8.2), + ), + ), + CountryOutline( + "Suriname", + "SR", + ( + (-54.5,2.3,-56.5,1.9,-57.6,3.3,-57.1,6,-54,5.8,-54.5,2.3), + ), + ), + CountryOutline( + "Sweden", + "SE", + ( + (11,58.9,12.6,61.3,11.9,63.1,16.8,68,20.6,69.1,23.5,67.9,23.9,66,17.8,62.7,17.1,61.3,18.8,60.1,15.9,56.1,12.9,55.4,11,58.9), + ), + ), + CountryOutline( + "Switzerland", + "CH", + ( + (9.6,47.5,10.4,46.5,6,46.3,9.6,47.5), + ), + ), + CountryOutline( + "Syria", + "SY", + ( + (35.7,32.7,36.7,36.8,42.3,37.2,41,34.4,35.7,32.7), + ), + ), + CountryOutline( + "Taiwan", + "TW", + ( + (121.8,24.4,120.7,22,120.1,23.6,121.8,24.4), + ), + ), + CountryOutline( + "Tajikistan", + "TJ", + ( + (67.8,37.1,67.7,39.6,70.7,41,69.5,39.5,73.7,39.4,75,37.4,71.8,36.7,70.8,38.5,67.8,37.1), + ), + ), + CountryOutline( + "Tanzania", + "TZ", + ( + (33.9,-0.9,39.2,-4.7,39.5,-10.9,34.6,-11.5,29.6,-6.5,30.4,-1.1,33.9,-0.9), + ), + ), + CountryOutline( + "Thailand", + "TH", + ( + (105.2,14.3,103,14.2,102.6,12.2,100.1,13.4,99.2,9.2,102.1,6.2,101.2,5.7,98.2,8.4,99.6,11.9,97.4,18.4,100.1,20.4,101.1,17.5,104.7,17.4,105.2,14.3), + ), + ), + CountryOutline( + "Timor-Leste", + "TL", + ( + (125,-8.9,127.3,-8.4,125,-8.9), + ), + ), + CountryOutline( + "Togo", + "TG", + ( + (0.9,11,1.1,5.9,0.9,11), + ), + ), + CountryOutline( + "Tunisia", + "TN", + ( + (9.5,30.3,7.5,34.1,9.5,37.3,11,37.1,10.1,34.3,11.5,33.1,9.5,30.3), + ), + ), + CountryOutline( + "Turkey", + "TR", + ( + (44.8,37.2,29.7,36.1,27.6,36.7,26.2,39.5,33.5,42,42.6,41.6,44.8,39.7,44.8,37.2), + (26.1,41.8,29,41.3,26.4,40.2,26.1,41.8), + ), + ), + CountryOutline( + "Turkmenistan", + "TM", + ( + (52.5,41.8,57.1,41.3,58.6,42.8,66.5,37.4,62.2,35.3,57.3,38,53.9,37.2,52.7,40,54.7,41,52.5,41.8), + ), + ), + CountryOutline( + "Uganda", + "UG", + ( + (33.9,-0.9,29.6,-1.3,31.2,3.8,34.5,3.6,33.9,-0.9), + ), + ), + CountryOutline( + "Ukraine", + "UA", + ( + (31.8,52.1,40.1,49.6,39.7,47.9,35,45.7,31.7,46.7,28.7,45.3,30,46.4,28.7,48.1,22.1,48.4,23.5,51.6,31.8,52.1), + ), + ), + CountryOutline( + "United Arab Emirates", + "AE", + ( + (51.6,24.2,56.3,25.7,55,22.5,51.6,24.2), + ), + ), + CountryOutline( + "United Kingdom", + "GB", + ( + (-6.2,53.9,-7.6,55.1,-6.2,53.9), + (-3.1,53.4,-6.1,56.8,-5,58.6,-2,57.7,-3.1,56,1.7,52.7,1.4,51.3,-5.8,50.2,-3.4,51.4,-5.3,52,-4.6,53.5,-3.1,53.4), + ), + ), + CountryOutline( + "United States of America", + "US", + ( + (-122.8,49,-88.4,48.3,-82.6,45.3,-82.7,41.7,-71.5,45,-69.2,47.4,-67,44.8,-70.1,43.7,-70,41.6,-75.5,39.5,-75.9,37.2,-76.3,39.2,-77,38.2,-75.7,35.6,-81.3,31.4,-80.4,25.2,-83.7,29.9,-86.4,30.4,-94.7,29.5,-97.5,25.8,-101,29.4,-103.9,29.3,-106.5,31.8,-117.1,32.5,-120.6,34.6,-124.4,40.3,-124.7,48.2,-122.6,47.1,-122.8,49), + (-166.5,60.4,-165.6,59.9,-167.5,60.2,-166.5,60.4), + (-153.2,58,-152.1,57.6,-154.5,57,-153.2,58), + (-141,69.7,-141,60.3,-137.5,58.9,-135.5,59.8,-130,55.9,-130.5,54.8,-134.1,58.1,-139.9,59.5,-147.1,60.9,-151.7,59.2,-150.6,61.3,-158.4,56,-164.9,54.6,-157,58.9,-162,58.7,-165.3,60.5,-165.7,62.1,-160.8,64.8,-168.1,65.7,-161.7,66.1,-166.2,68.9,-156.6,71.4,-141,69.7), + (-171.7,63.8,-168.7,63.3,-171.7,63.8), + ), + ), + CountryOutline( + "Uruguay", + "UY", + ( + (-57.6,-30.2,-53.8,-32,-53.8,-34.4,-58.4,-33.9,-57.6,-30.2), + ), + ), + CountryOutline( + "Uzbekistan", + "UZ", + ( + (56,41.3,55.9,45,58.5,45.6,62,43.5,64.9,43.7,68.3,40.7,71,42.3,73.1,40.9,67.7,39.6,67.8,37.1,58.6,42.8,56,41.3), + ), + ), + CountryOutline( + "Venezuela", + "VE", + ( + (-60.7,5.2,-64.8,4.1,-63.4,2.2,-66.3,0.7,-67.8,2.8,-67.3,6.1,-72,7,-72.9,10.5,-71.3,11.8,-71.3,9.1,-69.9,12.2,-68.2,10.6,-61.9,10.7,-59.8,8.4,-60.7,5.2), + ), + ), + CountryOutline( + "Vietnam", + "VN", + ( + (104.3,10.5,107.5,12.3,107.6,15.2,102.2,22.5,105.3,23.4,108.1,21.6,105.7,19.1,108.9,15.3,109.2,11.7,105.2,8.6,104.3,10.5), + ), + ), + CountryOutline( + "W. Sahara", + "EH", + ( + (-8.7,27.7,-8.7,25.9,-12,25.9,-12.9,21.3,-17.1,21,-14.8,21.5,-11.4,26.9,-8.7,27.7), + ), + ), + CountryOutline( + "Yemen", + "YE", + ( + (52,19,52.2,15.6,43.5,12.6,43.4,17.6,47,16.9,52,19), + ), + ), + CountryOutline( + "Zambia", + "ZM", + ( + (30.7,-8.3,33.2,-9.7,33.2,-14,27,-17.9,23.2,-17.5,21.9,-12.9,24,-12.9,23.9,-10.9,29.7,-13.3,28.4,-9.2,30.7,-8.3), + ), + ), + CountryOutline( + "Zimbabwe", + "ZW", + ( + (31.2,-22.3,28,-21.5,25.3,-17.7,30.3,-15.5,32.8,-16.7,31.2,-22.3), + ), + ), +) diff --git a/ports/python/hqtui/ui.py b/ports/python/hqtui/ui.py index c4782f1..7bda9a5 100644 --- a/ports/python/hqtui/ui.py +++ b/ports/python/hqtui/ui.py @@ -529,6 +529,19 @@ def shapes(self, options: g.CanvasOptions, layout: Layout | None = None): lambda s: g.draw_canvas(s, options), ) + def world_map(self, options: w.WorldMapOptions, layout: Layout | None = None): + """A world map, and the country under whatever gets clicked. + + The click is answered by turning the cell back into degrees and testing + it against the outlines, so the answer is the country actually under the + cursor. Bounding boxes would be cheaper and wrong: Russia's covers most + of the northern hemisphere and Chile's covers Argentina. + """ + return self._add( + self._constraint(layout or Layout(), "fill"), + lambda s: w.draw_world_map(s, options), + ) + def sparkline(self, options: w.SparklineWidgetOptions, layout: Layout | None = None): return self._add(self._leaf(layout or Layout(), 1), lambda s: w.draw_sparkline(s, options)) diff --git a/ports/python/hqtui/widgets/__init__.py b/ports/python/hqtui/widgets/__init__.py index 1ea8e1f..a82dd1d 100644 --- a/ports/python/hqtui/widgets/__init__.py +++ b/ports/python/hqtui/widgets/__init__.py @@ -77,6 +77,7 @@ ) from .chart import ChartOptions, draw_chart from .shadow import ShadowOptions, dim_rect, draw_shadow +from .world import WorldMapOptions, country_at_cell, draw_world_map from .surface import ClearOptions, FillOptions, draw_clear, draw_fill from .scrollbar import ( ScrollbarOptions, @@ -122,6 +123,7 @@ "draw_calendar", "is_leap_year", "CALENDAR_WIDTH", "ChartOptions", "draw_chart", "ClearOptions", "FillOptions", "draw_clear", "draw_fill", "ShadowOptions", "dim_rect", "draw_shadow", + "WorldMapOptions", "country_at_cell", "draw_world_map", "ScrollbarOptions", "ScrollbarOrientation", "is_vertical", "offset_for_position", "thumb", "draw_scrollbar", "draw_scrollbar_widget", "draw_select", "draw_sparkline", "draw_status_bar", "draw_table", "draw_tabs", "draw_text", "draw_text_input", "draw_tooltip", diff --git a/ports/python/hqtui/widgets/world.py b/ports/python/hqtui/widgets/world.py new file mode 100644 index 0000000..cff21d6 --- /dev/null +++ b/ports/python/hqtui/widgets/world.py @@ -0,0 +1,90 @@ +"""A world map you can click. + +The drawing is the canvas doing what it already does — polylines in the caller's +own coordinates, which for a map are degrees. What this adds is the other +direction: turning a click back into a country. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Sequence + +from ..color import Color +from ..graphics.canvas import Bounds, CanvasOptions, draw_canvas +from ..graphics.world import ( + WORLD_X, + WORLD_Y, + CountryOutline, + WorldShapeOptions, + country_at, + degrees_at, + world_shapes, +) +from ..surface import Surface + +__all__ = ["WorldMapOptions", "country_at_cell", "draw_world_map"] + + +@dataclass(frozen=True, slots=True) +class WorldMapOptions: + #: The window on the globe. None means all of it. + x: Bounds | None = None + y: Bounds | None = None + #: Coastline colour. + color: Color | None = None + #: Countries to pick out, by name or ISO code. + highlight: Sequence[str] = () + highlight_color: Color | None = None + background: Color | None = None + grid: bool = False + + @property + def window(self) -> tuple[Bounds, Bounds]: + return (self.x or WORLD_X, self.y or WORLD_Y) + + +def draw_world_map(surface: Surface, options: WorldMapOptions = WorldMapOptions()) -> None: + if surface.empty: + return + theme = surface.theme + x, y = options.window + draw_canvas( + surface, + CanvasOptions( + shapes=world_shapes( + WorldShapeOptions( + color=options.color if options.color is not None else theme.border, + highlight=options.highlight, + highlight_color=( + options.highlight_color + if options.highlight_color is not None + else theme.accent + ), + ) + ), + x=x, + y=y, + background=options.background, + grid=options.grid, + ), + ) + + +def country_at_cell( + column: int, + row: int, + width: int, + height: int, + options: WorldMapOptions = WorldMapOptions(), +) -> CountryOutline | None: + """The country under a cell of a map drawn with these bounds. + + Exposed so a caller can answer a hover as well as a click, and so the + arithmetic that has to agree with the drawing lives in one place. + """ + x, y = options.window + at = degrees_at(column, row, width, height, x, y) + if at is None: + return None + return country_at(at[0], at[1]) diff --git a/ports/python/tests/test_conformance_widgets.py b/ports/python/tests/test_conformance_widgets.py index acebbe5..69ae2e4 100644 --- a/ports/python/tests/test_conformance_widgets.py +++ b/ports/python/tests/test_conformance_widgets.py @@ -138,6 +138,14 @@ def draw_scene(case, name: str, s: Surface) -> None: elif name == "shadow-solid": w.draw_fill(s, w.FillOptions(symbol="x")) w.draw_shadow(s, Rect(2, 1, 6, 2), w.ShadowOptions(color=rgb(0x10, 0x14, 0x18))) + elif name == "world": + w.draw_world_map(s, w.WorldMapOptions()) + elif name == "world-zoom": + w.draw_world_map(s, w.WorldMapOptions( + x=gc.Bounds(112, 156), y=gc.Bounds(24, 50) + )) + elif name == "world-highlight": + w.draw_world_map(s, w.WorldMapOptions(highlight=("Brazil", "JP"))) elif name == "badge": w.draw_badge(s, w.BadgeOptions(text="LIVE")) elif name == "badge-outline": diff --git a/ports/rust/examples/world-probe.rs b/ports/rust/examples/world-probe.rs new file mode 100644 index 0000000..5befef9 --- /dev/null +++ b/ports/rust/examples/world-probe.rs @@ -0,0 +1,42 @@ +//! Prints what the country lookup answers for a fixed set of points. +//! +//! The same probe exists for every port, so "the ports agree about the world" +//! is a diff rather than a hope. +use hqtui::graphics::{country_at, degrees_at, WORLD_X, WORLD_Y}; +use hqtui::widgets::{country_at_cell, WorldMapOptions}; + +fn main() { + let places: [(&str, f64, f64); 13] = [ + ("Paris", 2.35, 48.86), + ("Tokyo", 139.7, 35.7), + ("Cairo", 31.2, 30.0), + ("Brasilia", -47.9, -15.8), + ("Canberra", 149.1, -35.3), + ("Denver", -105.0, 39.7), + ("Moscow", 37.6, 55.75), + ("Delhi", 77.2, 28.6), + ("Nairobi", 36.8, -1.3), + ("Pacific", -140.0, 0.0), + ("Atlantic", -30.0, 0.0), + ("SouthernOcean", 80.0, -40.0), + ("NorthPacific", -150.0, 40.0), + ]; + for (name, lon, lat) in places { + let found = country_at(lon, lat).map(|c| c.name).unwrap_or("-"); + println!("{name} {found}"); + } + + // The cell path, which has to agree with what the canvas drew. + let options = WorldMapOptions::default(); + for (column, row) in [(173usize, 28usize), (74, 2), (20, 25), (88, 7)] { + let found = country_at_cell(column, row, 200, 50, &options).map(|c| c.name).unwrap_or("-"); + println!("cell:{column},{row} {found}"); + } + + // And the projection itself, so a drift shows up as a number rather than as + // a country that happens to still be right. + for (column, row) in [(0usize, 0usize), (99, 25), (50, 13)] { + let (lon, lat) = degrees_at(column, row, 100, 26, WORLD_X, WORLD_Y).unwrap(); + println!("degrees:{column},{row} {lon:.4} {lat:.4}"); + } +} diff --git a/ports/rust/src/graphics/mod.rs b/ports/rust/src/graphics/mod.rs index cce0075..26ef2f7 100644 --- a/ports/rust/src/graphics/mod.rs +++ b/ports/rust/src/graphics/mod.rs @@ -3,6 +3,8 @@ pub mod blocks; pub mod canvas; +pub mod world; +pub mod world_data; pub mod chart; pub mod braille; pub mod plot; @@ -12,6 +14,11 @@ pub use blocks::{ HORIZONTAL_EIGHTHS, QUADRANTS, SHADES, VERTICAL_EIGHTHS, }; pub use braille::BrailleCanvas; +pub use world::{ + country_at, country_bounds, degrees_at, find_country, world_shapes, WorldShapeOptions, + WORLD_X, WORLD_Y, +}; +pub use world_data::{CountryOutline, WORLD_COUNTRIES}; pub use canvas::{ draw_canvas, projection, Bounds, CanvasOptions, Projection, Shape, }; diff --git a/ports/rust/src/graphics/world.rs b/ports/rust/src/graphics/world.rs new file mode 100644 index 0000000..7ed1897 --- /dev/null +++ b/ports/rust/src/graphics/world.rs @@ -0,0 +1,169 @@ +//! The world, as shapes for the canvas, and the lookup that makes it clickable. +//! +//! The canvas already draws in the caller's own coordinates, and longitude and +//! latitude are just another pair of axes -- so a map is a list of polylines in +//! degrees, and nothing here needs a projection of its own beyond deciding +//! which window on the globe to show. +//! +//! The interesting half is the other direction. A click arrives as a terminal +//! cell, and a country is a polygon, so answering "what did they click" means +//! turning the cell back into degrees and testing it against the outlines. +//! Doing it that way rather than with bounding boxes is what makes the answer +//! right: Russia's bounding box covers most of the northern hemisphere, and +//! Chile's covers Argentina. + +use crate::color::Color; +use crate::graphics::canvas::{Bounds, Shape}; +use crate::graphics::world_data::{CountryOutline, WORLD_COUNTRIES}; + +/// The whole globe, which is what a map shows unless told otherwise. +pub const WORLD_X: Bounds = Bounds { min: -180.0, max: 180.0 }; +pub const WORLD_Y: Bounds = Bounds { min: -90.0, max: 90.0 }; + +#[derive(Clone, Debug, Default)] +pub struct WorldShapeOptions { + /// Colour for countries with nothing special about them. + pub color: Option, + /// Countries to pick out, by name or ISO code. + pub highlight: Vec, + pub highlight_color: Option, +} + +/// Match on either the name or the ISO code, case-insensitively. +fn matches(country: &CountryOutline, keys: &[String]) -> bool { + keys.iter().any(|key| { + !key.is_empty() + && (country.name.eq_ignore_ascii_case(key) + || (!country.iso.is_empty() && country.iso.eq_ignore_ascii_case(key))) + }) +} + +/// The world as canvas shapes, one polyline per landmass. +/// +/// Polylines rather than scattered points: the outlines are closed rings, so +/// joining them draws a coastline instead of a dotted suggestion of one, and it +/// reads at a fraction of the resolution dots would need. +pub fn world_shapes(options: &WorldShapeOptions) -> Vec { + let mut shapes = Vec::new(); + for country in WORLD_COUNTRIES { + let picked = !options.highlight.is_empty() && matches(country, &options.highlight); + let color = if picked { options.highlight_color.or(options.color) } else { options.color }; + for ring in country.rings { + let mut points: Vec<(f64, f64)> = Vec::with_capacity(ring.len() / 2 + 1); + let mut i = 0; + while i + 1 < ring.len() { + points.push((ring[i], ring[i + 1])); + i += 2; + } + // Closed: the last point joins the first, or every country has a + // gap in its coastline where the ring started. + if let Some(first) = points.first().copied() { + points.push(first); + } + shapes.push(Shape::Polyline { points, color }); + } + } + shapes +} + +/// Whether a point is inside a ring, by ray casting. +/// +/// The ring is a flat list of interleaved coordinates, so this walks it two at +/// a time rather than allocating a pair per vertex -- it runs once per country +/// per click, and there are a couple of thousand vertices. +fn inside_ring(ring: &[f64], lon: f64, lat: f64) -> bool { + let mut inside = false; + let n = ring.len() / 2; + if n == 0 { + return false; + } + let mut j = n - 1; + for i in 0..n { + let (xi, yi) = (ring[i * 2], ring[i * 2 + 1]); + let (xj, yj) = (ring[j * 2], ring[j * 2 + 1]); + if (yi > lat) != (yj > lat) && lon < (xj - xi) * (lat - yi) / (yj - yi) + xi { + inside = !inside; + } + j = i; + } + inside +} + +/// The country containing a point, or `None` for open water. +/// +/// Where outlines overlap -- and at this resolution simplified borders do +/// overlap -- the first match wins, which is stable because the data is sorted +/// by name. +pub fn country_at(lon: f64, lat: f64) -> Option<&'static CountryOutline> { + if !lon.is_finite() || !lat.is_finite() { + return None; + } + WORLD_COUNTRIES + .iter() + .find(|country| country.rings.iter().any(|ring| inside_ring(ring, lon, lat))) +} + +/// Look a country up by name or ISO code. +pub fn find_country(key: &str) -> Option<&'static CountryOutline> { + let keys = [key.to_string()]; + WORLD_COUNTRIES.iter().find(|country| matches(country, &keys)) +} + +/// The window a country fills, with a little room around it. +/// +/// For zooming a map to a country: the bounding box alone puts the coastline +/// flat against the edge of the panel, which reads as though the country has +/// been cut off rather than framed. +pub fn country_bounds(country: &CountryOutline, margin: f64) -> (Bounds, Bounds) { + let mut min_lon = f64::INFINITY; + let mut max_lon = f64::NEG_INFINITY; + let mut min_lat = f64::INFINITY; + let mut max_lat = f64::NEG_INFINITY; + for ring in country.rings { + let mut i = 0; + while i + 1 < ring.len() { + min_lon = min_lon.min(ring[i]); + max_lon = max_lon.max(ring[i]); + min_lat = min_lat.min(ring[i + 1]); + max_lat = max_lat.max(ring[i + 1]); + i += 2; + } + } + if !min_lon.is_finite() { + return (WORLD_X, WORLD_Y); + } + // A single-point country would give a zero-width window, which cannot be + // mapped onto anything. + let pad_x = ((max_lon - min_lon) * margin).max(1.0); + let pad_y = ((max_lat - min_lat) * margin).max(1.0); + ( + Bounds { min: min_lon - pad_x, max: max_lon + pad_x }, + Bounds { min: min_lat - pad_y, max: max_lat + pad_y }, + ) +} + +/// The degrees under a terminal cell, given the window the map was drawn with. +/// +/// The inverse of what the canvas does on the way in, taken at the centre of +/// the cell: a click lands on a whole cell, and the centre is the only point in +/// it that is not arbitrarily nearer one neighbour than the other. +pub fn degrees_at( + column: usize, + row: usize, + width: usize, + height: usize, + x: Bounds, + y: Bounds, +) -> Option<(f64, f64)> { + if width == 0 || height == 0 { + return None; + } + // The canvas is 2x4 Braille pixels per cell, and it spans its bounds across + // `pixels - 1`, so the inverse has to use the same denominators or a click + // drifts from what was drawn. + let px = (width * 2).saturating_sub(1).max(1) as f64; + let py = (height * 4).saturating_sub(1).max(1) as f64; + let lon = x.min + ((column * 2 + 1) as f64 / px) * (x.max - x.min); + let lat = y.min + (1.0 - (row * 4 + 2) as f64 / py) * (y.max - y.min); + Some((lon, lat)) +} diff --git a/ports/rust/src/graphics/world_data.rs b/ports/rust/src/graphics/world_data.rs new file mode 100644 index 0000000..7d25d36 --- /dev/null +++ b/ports/rust/src/graphics/world_data.rs @@ -0,0 +1,1306 @@ +//! Country outlines, flattened for a terminal. +//! +//! Generated by packages/hqtui/scripts/generate-world.ts from Natural Earth's +//! 1:110m Admin 0 countries, which is public domain. Do not edit by hand. +//! +//! Each ring is longitude and latitude interleaved -- lon, lat, lon, lat -- +//! rather than a list of pairs, because at 1982 points the nested form +//! costs a container per coordinate for no gain. A country has more than one +//! ring when it is more than one landmass. +//! +//! 171 countries, 1982 points, simplified at 1 degrees. + +pub struct CountryOutline { + pub name: &'static str, + /// ISO 3166-1 alpha-2, where Natural Earth has one. + pub iso: &'static str, + /// Longitude and latitude, interleaved. + pub rings: &'static [&'static [f64]], +} + +pub static WORLD_COUNTRIES: &[CountryOutline] = &[ + CountryOutline { + name: "Afghanistan", + iso: "AF", + rings: &[ + &[66.5,37.4,70.8,38.5,71.8,36.7,75.2,37.1,71.3,36.1,69.3,31.9,66.3,29.9,60.9,29.8,61.2,35.7,66.5,37.4], + ], + }, + CountryOutline { + name: "Albania", + iso: "AL", + rings: &[ + &[21.0,40.8,19.4,40.3,19.7,42.7,21.0,40.8], + ], + }, + CountryOutline { + name: "Algeria", + iso: "DZ", + rings: &[ + &[-8.7,27.4,-8.7,28.8,-1.3,32.3,-1.2,35.7,8.4,36.9,7.5,34.1,9.8,29.4,9.3,26.1,12.0,23.5,3.2,19.1,-8.7,27.4], + ], + }, + CountryOutline { + name: "Angola", + iso: "AO", + rings: &[ + &[12.3,-6.1,16.3,-5.9,17.5,-8.1,21.7,-7.3,22.2,-11.1,24.0,-11.2,24.0,-12.9,21.9,-12.9,23.2,-17.5,11.7,-17.3,13.7,-11.3,12.3,-6.1], + ], + }, + CountryOutline { + name: "Antarctica", + iso: "AQ", + rings: &[ + &[-48.7,-78.0,-43.9,-78.5,-43.3,-80.0,-54.2,-80.6,-48.7,-78.0], + &[-66.3,-80.3,-59.6,-80.0,-66.3,-80.3], + &[-73.9,-71.3,-70.3,-68.9,-68.3,-71.4,-75.0,-72.1,-73.9,-71.3], + &[-102.3,-71.9,-96.2,-72.5,-102.3,-71.9], + &[-122.6,-73.7,-118.7,-73.5,-122.6,-73.7], + &[-127.3,-73.5,-124.0,-73.9,-127.3,-73.5], + &[-163.7,-78.6,-159.2,-79.5,-163.7,-78.6], + &[180.0,-84.7,180.0,-90.0,-180.0,-90.0,-179.1,-84.1,-143.1,-85.0,-153.6,-83.7,-152.9,-82.0,-156.8,-81.1,-146.4,-80.3,-155.3,-79.1,-158.4,-76.9,-151.3,-77.4,-146.1,-76.5,-146.2,-75.4,-135.2,-74.3,-100.1,-74.9,-103.7,-72.6,-74.9,-73.9,-67.4,-72.5,-67.7,-67.3,-57.8,-63.3,-65.7,-68.0,-61.8,-70.7,-60.8,-73.7,-70.6,-76.6,-77.2,-76.7,-73.7,-77.9,-78.0,-79.2,-58.2,-83.2,-28.5,-80.3,-35.6,-79.5,-35.8,-78.3,-17.5,-75.1,-15.4,-73.1,-6.9,-70.9,27.1,-70.5,33.9,-68.5,38.6,-69.8,54.5,-65.8,61.4,-68.0,68.9,-67.9,69.7,-69.2,67.9,-71.9,69.9,-72.3,73.9,-69.9,88.0,-66.2,95.8,-67.4,102.8,-65.6,106.2,-66.9,113.6,-65.9,119.8,-67.3,135.1,-65.3,137.5,-67.0,145.5,-66.9,171.2,-71.7,163.6,-76.2,167.0,-78.8,161.8,-79.2,159.8,-80.9,169.4,-83.8,180.0,-84.7], + ], + }, + CountryOutline { + name: "Argentina", + iso: "AR", + rings: &[ + &[-68.6,-52.6,-65.0,-54.7,-68.6,-54.9,-68.6,-52.6], + &[-57.6,-30.2,-58.5,-34.4,-56.8,-36.9,-62.3,-38.8,-62.7,-41.0,-65.1,-41.1,-63.5,-42.6,-67.3,-45.6,-65.6,-47.2,-69.1,-50.7,-68.1,-52.3,-71.9,-52.0,-73.4,-49.3,-71.2,-44.8,-72.1,-42.3,-68.4,-24.5,-66.3,-21.8,-62.8,-22.0,-57.8,-25.2,-58.6,-27.1,-55.7,-27.4,-54.1,-25.5,-53.6,-26.9,-57.6,-30.2], + ], + }, + CountryOutline { + name: "Armenia", + iso: "AM", + rings: &[ + &[46.5,38.8,43.6,41.1,45.6,40.8,46.5,38.8], + ], + }, + CountryOutline { + name: "Australia", + iso: "AU", + rings: &[ + &[147.7,-40.8,147.9,-43.2,146.0,-43.5,144.7,-40.7,147.7,-40.8], + &[126.1,-32.2,118.0,-35.1,115.0,-34.2,113.7,-22.5,120.9,-19.7,125.7,-14.2,129.6,-15.0,132.4,-11.1,136.5,-11.9,135.5,-15.0,140.2,-17.7,142.5,-10.7,146.4,-19.0,150.7,-22.4,153.6,-28.1,150.0,-37.4,146.3,-39.0,140.6,-38.0,138.2,-34.4,136.8,-35.3,137.8,-32.9,136.0,-34.9,131.3,-31.5,126.1,-32.2], + ], + }, + CountryOutline { + name: "Austria", + iso: "AT", + rings: &[ + &[17.0,48.1,14.6,46.4,9.5,47.1,12.9,47.5,13.6,48.9,17.0,48.1], + ], + }, + CountryOutline { + name: "Azerbaijan", + iso: "AZ", + rings: &[ + &[46.4,41.9,50.4,40.3,48.9,38.3,45.6,39.9,45.0,41.2,46.4,41.9], + ], + }, + CountryOutline { + name: "Bahamas", + iso: "BS", + rings: &[ + &[-78.2,25.2,-77.5,23.8,-78.2,25.2], + ], + }, + CountryOutline { + name: "Bangladesh", + iso: "BD", + rings: &[ + &[92.7,22.0,92.4,20.7,91.4,22.8,89.0,22.1,88.6,26.4,92.4,25.0,91.2,23.5,92.7,22.0], + ], + }, + CountryOutline { + name: "Belarus", + iso: "BY", + rings: &[ + &[28.2,56.2,30.9,55.6,32.7,53.4,31.8,52.1,23.5,51.6,23.5,53.9,28.2,56.2], + ], + }, + CountryOutline { + name: "Belgium", + iso: "BE", + rings: &[ + &[6.2,50.8,5.7,49.5,2.5,51.1,6.2,50.8], + ], + }, + CountryOutline { + name: "Belize", + iso: "BZ", + rings: &[ + &[-89.1,17.8,-88.1,18.3,-88.9,15.9,-89.1,17.8], + ], + }, + CountryOutline { + name: "Benin", + iso: "BJ", + rings: &[ + &[2.7,6.3,0.8,10.5,2.8,12.2,2.7,6.3], + ], + }, + CountryOutline { + name: "Bhutan", + iso: "BT", + rings: &[ + &[91.7,27.8,88.8,27.1,91.7,27.8], + ], + }, + CountryOutline { + name: "Bolivia", + iso: "BO", + rings: &[ + &[-69.5,-11.0,-65.3,-9.8,-65.4,-11.6,-60.5,-13.8,-60.2,-16.3,-58.2,-16.3,-57.9,-20.0,-61.8,-19.6,-62.7,-22.2,-67.8,-22.9,-69.5,-11.0], + ], + }, + CountryOutline { + name: "Bosnia and Herz.", + iso: "BA", + rings: &[ + &[18.6,42.7,16.0,45.2,19.4,44.9,18.6,42.7], + ], + }, + CountryOutline { + name: "Botswana", + iso: "BW", + rings: &[ + &[29.4,-22.1,25.7,-25.5,21.6,-26.7,19.9,-24.8,20.9,-18.3,25.3,-17.7,29.4,-22.1], + ], + }, + CountryOutline { + name: "Brazil", + iso: "BR", + rings: &[ + &[-53.4,-33.8,-53.8,-32.0,-57.6,-30.2,-53.6,-26.1,-55.8,-22.4,-57.9,-22.1,-58.2,-16.3,-60.2,-16.3,-60.5,-13.8,-65.4,-11.6,-65.3,-9.8,-70.5,-11.0,-70.5,-9.5,-72.2,-10.1,-74.0,-7.5,-72.9,-5.3,-69.9,-4.3,-69.8,1.7,-65.5,0.8,-63.4,2.2,-64.8,4.1,-60.7,5.2,-59.0,1.3,-52.9,2.1,-51.3,4.2,-50.4,-0.1,-44.6,-2.7,-40.0,-2.9,-35.6,-5.1,-34.7,-7.3,-38.7,-13.1,-40.9,-21.9,-47.6,-24.9,-48.9,-28.7,-53.4,-33.8], + ], + }, + CountryOutline { + name: "Bulgaria", + iso: "BG", + rings: &[ + &[22.7,44.2,28.6,43.7,28.0,42.0,23.0,41.3,22.7,44.2], + ], + }, + CountryOutline { + name: "Burkina Faso", + iso: "BF", + rings: &[ + &[-5.4,10.4,-4.3,13.2,-1.1,15.0,2.2,12.6,0.9,11.0,-2.9,11.0,-2.8,9.6,-5.4,10.4], + ], + }, + CountryOutline { + name: "Burundi", + iso: "BI", + rings: &[ + &[30.5,-2.4,29.3,-4.5,29.0,-2.8,30.5,-2.4], + ], + }, + CountryOutline { + name: "Cambodia", + iso: "KH", + rings: &[ + &[102.6,12.2,103.0,14.2,107.6,13.5,106.2,11.0,103.5,10.6,102.6,12.2], + ], + }, + CountryOutline { + name: "Cameroon", + iso: "CM", + rings: &[ + &[14.5,12.9,14.5,4.7,15.9,1.7,9.6,2.3,8.8,5.5,11.7,7.0,14.5,12.9], + ], + }, + CountryOutline { + name: "Canada", + iso: "CA", + rings: &[ + &[-122.8,49.0,-127.4,50.8,-130.5,54.3,-130.0,55.9,-135.5,59.8,-137.5,58.9,-141.0,60.3,-141.0,69.7,-136.5,68.9,-128.1,70.5,-113.5,67.7,-106.1,68.8,-101.5,67.6,-97.7,68.6,-96.1,67.3,-94.2,69.1,-96.5,70.1,-95.2,71.9,-87.4,67.2,-85.5,69.9,-82.6,69.7,-81.4,67.1,-85.8,66.6,-90.7,63.6,-94.7,58.9,-92.3,57.1,-82.3,55.1,-79.9,51.2,-78.6,52.6,-79.8,54.7,-76.5,56.5,-78.5,58.8,-77.3,59.9,-78.1,62.3,-73.8,62.4,-69.6,61.1,-67.6,58.2,-64.6,60.3,-61.8,56.3,-57.3,54.6,-55.7,52.1,-60.0,50.2,-66.4,50.2,-71.1,46.8,-65.1,49.2,-64.5,46.2,-60.5,47.0,-59.8,45.9,-65.4,43.5,-66.2,44.5,-64.4,45.3,-67.1,45.1,-69.2,47.4,-71.5,45.0,-82.4,41.7,-82.6,45.3,-88.4,48.3,-122.8,49.0], + &[-84.0,62.5,-81.9,62.9,-84.0,62.5], + &[-79.8,72.8,-80.8,73.7,-76.3,72.8,-79.8,72.8], + &[-93.6,75.0,-96.8,74.9,-93.6,75.0], + &[-93.8,77.5,-96.4,77.8,-93.8,77.5], + &[-96.8,78.8,-95.6,78.4,-98.6,78.9,-96.8,78.8], + &[-88.2,74.4,-97.1,76.8,-79.8,74.9,-88.2,74.4], + &[-111.3,78.2,-109.9,78.0,-113.5,77.7,-111.3,78.2], + &[-111.0,78.8,-109.7,78.6,-112.5,78.4,-111.0,78.8], + &[-55.6,51.3,-56.8,49.8,-53.5,49.2,-53.1,46.7,-59.3,47.6,-55.6,51.3], + &[-83.9,65.1,-80.1,63.7,-87.2,63.5,-85.9,65.7,-83.9,65.1], + &[-78.8,72.4,-68.8,70.5,-67.0,69.2,-68.8,68.7,-61.9,66.9,-63.9,65.0,-68.0,66.3,-64.7,63.4,-68.8,63.7,-66.2,61.9,-68.9,62.3,-78.6,64.6,-74.0,65.5,-73.3,68.1,-79.0,70.2,-88.7,70.4,-90.2,72.2,-85.8,73.8,-85.8,72.5,-82.3,73.8,-78.8,72.4], + &[-94.5,74.1,-90.5,73.9,-95.4,72.1,-96.0,73.4,-94.5,74.1], + &[-122.9,76.1,-116.2,77.6,-122.9,76.1], + &[-132.7,54.0,-131.2,52.2,-132.7,54.0], + &[-105.5,79.3,-99.7,77.9,-105.5,79.3], + &[-123.5,48.5,-128.4,50.8,-123.5,48.5], + &[-121.5,74.4,-115.5,73.5,-123.1,70.9,-125.9,71.9,-123.9,73.7,-124.9,74.3,-121.5,74.4], + &[-107.8,75.8,-105.7,75.5,-117.7,75.2,-115.4,76.5,-107.8,75.8], + &[-106.5,73.1,-101.1,69.6,-113.3,68.5,-117.3,70.0,-112.4,70.4,-119.4,71.6,-115.2,73.3,-108.2,71.7,-108.4,73.1,-106.5,73.1], + &[-100.4,72.7,-101.5,73.4,-97.4,73.8,-96.5,72.6,-98.4,71.3,-102.5,72.5,-100.4,72.7], + &[-106.6,73.6,-104.5,73.4,-106.6,73.6], + &[-98.5,76.7,-98.2,75.0,-102.5,75.6,-98.5,76.7], + &[-96.0,80.6,-92.4,81.3,-85.8,79.3,-92.9,78.3,-96.0,80.6], + &[-91.6,81.9,-61.8,82.6,-76.9,79.3,-75.4,78.5,-80.6,76.2,-89.5,76.5,-88.3,77.9,-85.0,77.5,-88.0,78.4,-85.1,79.3,-86.9,80.3,-81.8,80.5,-91.6,81.9], + &[-75.2,67.4,-77.2,67.6,-75.2,67.4], + &[-96.3,69.5,-99.8,69.4,-96.3,69.5], + &[-64.5,49.9,-61.8,49.1,-64.5,49.9], + &[-64.0,47.0,-62.0,46.4,-64.0,47.0], + ], + }, + CountryOutline { + name: "Central African Rep.", + iso: "CF", + rings: &[ + &[27.4,5.2,22.4,4.0,19.5,5.0,16.0,2.3,14.5,5.5,15.3,7.4,22.9,11.1,27.4,5.2], + ], + }, + CountryOutline { + name: "Chad", + iso: "TD", + rings: &[ + &[23.8,19.6,23.9,15.6,21.9,12.6,22.9,11.1,15.3,7.4,13.5,14.4,15.9,20.4,14.9,22.9,23.8,19.6], + ], + }, + CountryOutline { + name: "Chile", + iso: "CL", + rings: &[ + &[-68.6,-52.6,-68.6,-54.9,-67.0,-54.9,-68.1,-55.6,-74.7,-52.8,-71.1,-54.1,-68.6,-52.6], + &[-69.6,-17.6,-67.0,-23.0,-70.5,-31.4,-69.8,-34.2,-72.1,-42.3,-71.2,-44.8,-73.4,-49.3,-71.9,-52.0,-68.6,-52.3,-71.4,-53.9,-74.9,-52.3,-75.6,-48.7,-74.1,-46.9,-75.6,-46.6,-72.7,-42.4,-74.3,-43.2,-69.6,-17.6], + ], + }, + CountryOutline { + name: "China", + iso: "CN", + rings: &[ + &[109.5,18.2,108.6,19.4,110.8,20.1,109.5,18.2], + &[80.3,42.3,80.0,44.9,87.8,49.3,91.0,46.9,90.9,45.3,96.3,42.7,109.2,42.5,111.9,45.1,119.7,46.7,115.5,48.1,122.2,53.4,125.9,52.8,131.0,47.8,135.0,48.5,133.1,45.1,131.0,45.0,130.6,42.4,121.1,38.9,121.6,40.9,117.5,38.7,122.4,37.5,119.2,34.9,121.9,31.7,121.7,28.2,118.7,24.5,110.4,20.3,105.3,23.4,101.7,22.3,101.8,21.2,99.2,22.1,97.6,23.9,98.7,27.5,96.1,29.5,88.8,27.3,78.7,31.5,78.9,34.3,73.7,39.4,80.3,42.3], + ], + }, + CountryOutline { + name: "Colombia", + iso: "CO", + rings: &[ + &[-66.9,1.3,-69.8,1.7,-69.9,-4.3,-70.0,-2.7,-77.4,0.4,-79.0,1.7,-77.1,3.8,-77.5,8.5,-71.4,12.4,-73.3,9.2,-72.0,7.0,-67.3,6.1,-66.9,1.3], + ], + }, + CountryOutline { + name: "Congo", + iso: "CG", + rings: &[ + &[18.5,3.5,16.0,-3.5,11.9,-5.0,11.5,-2.8,14.4,-1.3,13.1,2.3,15.9,1.7,18.5,3.5], + ], + }, + CountryOutline { + name: "Costa Rica", + iso: "CR", + rings: &[ + &[-82.5,9.6,-83.0,8.2,-85.9,10.9,-82.5,9.6], + ], + }, + CountryOutline { + name: "Côte d'Ivoire", + iso: "CI", + rings: &[ + &[-8.0,10.2,-2.8,9.6,-2.9,5.0,-7.7,4.4,-8.0,10.2], + ], + }, + CountryOutline { + name: "Croatia", + iso: "HR", + rings: &[ + &[16.6,46.5,19.4,45.2,15.8,44.8,18.5,42.5,13.7,45.1,16.6,46.5], + ], + }, + CountryOutline { + name: "Cuba", + iso: "CU", + rings: &[ + &[-82.3,23.2,-74.2,20.3,-77.8,19.9,-81.8,22.6,-85.0,21.9,-82.3,23.2], + ], + }, + CountryOutline { + name: "Cyprus", + iso: "CY", + rings: &[ + &[32.7,35.1,34.0,35.0,32.7,35.1], + ], + }, + CountryOutline { + name: "Czechia", + iso: "CZ", + rings: &[ + &[15.0,51.1,18.9,49.5,12.5,49.5,15.0,51.1], + ], + }, + CountryOutline { + name: "Dem. Rep. Congo", + iso: "CD", + rings: &[ + &[29.3,-4.5,30.7,-8.3,28.7,-8.5,28.4,-11.8,29.7,-13.3,22.2,-11.1,21.7,-7.3,17.5,-8.1,16.3,-5.9,12.2,-5.8,16.0,-3.5,19.5,5.0,29.7,4.6,31.2,2.2,29.3,-4.5], + ], + }, + CountryOutline { + name: "Denmark", + iso: "DK", + rings: &[ + &[9.9,55.0,8.1,56.5,10.6,57.7,9.9,55.0], + &[12.4,56.1,12.1,54.8,11.0,55.4,12.4,56.1], + ], + }, + CountryOutline { + name: "Djibouti", + iso: "DJ", + rings: &[ + &[42.4,12.5,42.8,10.9,42.4,12.5], + ], + }, + CountryOutline { + name: "Dominican Rep.", + iso: "DO", + rings: &[ + &[-71.7,18.0,-71.6,19.9,-68.3,18.6,-71.7,18.0], + ], + }, + CountryOutline { + name: "Ecuador", + iso: "EC", + rings: &[ + &[-75.4,-0.2,-78.6,-4.5,-80.4,-4.4,-80.1,0.8,-75.4,-0.2], + ], + }, + CountryOutline { + name: "Egypt", + iso: "EG", + rings: &[ + &[36.9,22.0,25.0,22.0,25.2,31.6,34.3,31.2,34.2,27.8,32.3,29.8,36.9,22.0], + ], + }, + CountryOutline { + name: "El Salvador", + iso: "SV", + rings: &[ + &[-89.4,14.4,-87.9,13.1,-90.1,13.7,-89.4,14.4], + ], + }, + CountryOutline { + name: "Eq. Guinea", + iso: "GQ", + rings: &[ + &[9.6,2.3,11.3,1.1,9.5,1.0,9.6,2.3], + ], + }, + CountryOutline { + name: "Eritrea", + iso: "ER", + rings: &[ + &[36.4,14.4,38.4,18.0,43.1,12.7,36.4,14.4], + ], + }, + CountryOutline { + name: "Estonia", + iso: "EE", + rings: &[ + &[28.0,59.5,27.3,57.5,23.3,59.2,28.0,59.5], + ], + }, + CountryOutline { + name: "eSwatini", + iso: "SZ", + rings: &[ + &[32.1,-26.7,31.0,-25.7,32.1,-26.7], + ], + }, + CountryOutline { + name: "Ethiopia", + iso: "ET", + rings: &[ + &[47.8,8.0,45.0,5.0,39.6,3.4,36.2,4.4,33.0,7.8,37.9,15.0,41.6,13.5,43.7,9.2,47.8,8.0], + ], + }, + CountryOutline { + name: "Falkland Is.", + iso: "FK", + rings: &[ + &[-61.2,-51.8,-57.7,-51.5,-61.2,-51.8], + ], + }, + CountryOutline { + name: "Finland", + iso: "FI", + rings: &[ + &[28.6,69.1,31.1,62.4,28.1,60.5,21.3,60.7,21.5,63.2,25.4,65.1,20.6,69.1,24.7,68.6,27.7,70.2,28.6,69.1], + ], + }, + CountryOutline { + name: "Fr. S. Antarctic Lands", + iso: "TF", + rings: &[ + &[68.9,-48.6,70.6,-49.3,68.7,-49.8,68.9,-48.6], + ], + }, + CountryOutline { + name: "France", + iso: "FR", + rings: &[ + &[-51.7,4.2,-52.9,2.1,-54.5,2.3,-54.0,5.8,-51.7,4.2], + &[6.2,49.5,8.1,49.0,6.0,46.7,7.4,43.7,1.8,42.3,-1.9,43.4,-1.2,46.0,-4.6,48.7,-1.6,48.6,-1.9,49.8,2.5,51.1,6.2,49.5], + &[8.7,42.6,9.2,41.4,8.7,42.6], + ], + }, + CountryOutline { + name: "Gabon", + iso: "GA", + rings: &[ + &[11.3,2.3,14.3,1.2,14.4,-1.3,11.1,-4.0,8.8,-1.1,11.3,2.3], + ], + }, + CountryOutline { + name: "Gambia", + iso: "GM", + rings: &[ + &[-16.7,13.6,-13.8,13.5,-16.7,13.6], + ], + }, + CountryOutline { + name: "Georgia", + iso: "GE", + rings: &[ + &[40.0,43.4,46.6,41.2,41.6,41.5,40.0,43.4], + ], + }, + CountryOutline { + name: "Germany", + iso: "DE", + rings: &[ + &[14.1,53.8,15.0,51.1,12.2,50.3,12.9,47.5,7.5,47.6,8.1,49.0,6.0,50.1,7.1,53.7,9.9,55.0,14.1,53.8], + ], + }, + CountryOutline { + name: "Ghana", + iso: "GH", + rings: &[ + &[0.0,11.0,1.1,5.9,-2.9,5.0,-2.9,11.0,0.0,11.0], + ], + }, + CountryOutline { + name: "Greece", + iso: "GR", + rings: &[ + &[26.3,35.3,23.5,35.3,26.3,35.3], + &[23.0,41.3,26.6,41.6,22.6,40.3,24.0,37.7,22.5,36.4,20.2,39.6,23.0,41.3], + ], + }, + CountryOutline { + name: "Greenland", + iso: "GL", + rings: &[ + &[-46.8,82.6,-27.1,83.5,-20.8,82.7,-31.9,82.2,-12.2,81.3,-20.0,80.2,-17.7,80.1,-19.7,78.8,-18.5,77.0,-21.7,76.6,-19.4,74.3,-24.8,72.3,-21.8,70.7,-25.5,71.4,-26.4,70.2,-22.3,70.1,-39.8,65.5,-43.4,60.1,-48.3,60.9,-51.6,63.6,-54.0,67.2,-50.9,69.9,-54.7,69.6,-54.4,70.8,-51.4,70.6,-55.8,71.7,-54.7,72.6,-58.6,75.5,-68.5,76.1,-71.4,77.0,-66.8,77.4,-73.3,78.0,-65.7,79.4,-68.0,80.1,-62.7,81.8,-44.5,81.7,-46.8,82.6], + ], + }, + CountryOutline { + name: "Guatemala", + iso: "GT", + rings: &[ + &[-92.2,14.5,-90.5,16.1,-91.0,17.8,-89.1,17.8,-88.2,15.7,-89.4,14.4,-92.2,14.5], + ], + }, + CountryOutline { + name: "Guinea", + iso: "GN", + rings: &[ + &[-13.7,12.6,-9.1,12.3,-8.3,7.7,-11.1,10.0,-13.2,8.9,-15.1,11.0,-13.7,12.6], + ], + }, + CountryOutline { + name: "Guinea-Bissau", + iso: "GW", + rings: &[ + &[-16.7,12.4,-13.7,11.8,-15.1,11.0,-16.7,12.4], + ], + }, + CountryOutline { + name: "Guyana", + iso: "GY", + rings: &[ + &[-56.5,1.9,-59.6,1.8,-61.4,6.0,-59.8,8.4,-57.1,6.0,-58.0,4.1,-56.5,1.9], + ], + }, + CountryOutline { + name: "Haiti", + iso: "HT", + rings: &[ + &[-71.7,19.7,-71.7,18.0,-74.5,18.3,-71.7,19.7], + ], + }, + CountryOutline { + name: "Honduras", + iso: "HN", + rings: &[ + &[-83.1,15.0,-87.3,13.0,-89.4,14.4,-87.9,15.9,-83.1,15.0], + ], + }, + CountryOutline { + name: "Hungary", + iso: "HU", + rings: &[ + &[22.1,48.4,21.0,46.3,16.2,46.9,17.0,48.1,22.1,48.4], + ], + }, + CountryOutline { + name: "Iceland", + iso: "IS", + rings: &[ + &[-14.5,66.5,-13.6,65.1,-18.7,63.5,-22.8,64.0,-21.8,64.4,-24.0,64.9,-22.2,65.4,-24.3,65.6,-14.5,66.5], + ], + }, + CountryOutline { + name: "India", + iso: "IN", + rings: &[ + &[97.3,28.3,92.7,22.0,91.2,23.5,92.4,25.0,88.6,26.4,88.9,21.7,80.3,15.9,79.9,10.4,77.5,8.0,72.6,21.4,70.5,20.9,68.2,23.7,71.0,24.4,69.5,26.9,75.3,32.3,73.7,34.3,77.8,35.5,78.7,31.5,81.1,30.2,80.1,28.8,83.3,27.4,88.1,26.4,88.7,28.1,92.0,26.8,96.1,29.5,97.3,28.3], + ], + }, + CountryOutline { + name: "Indonesia", + iso: "ID", + rings: &[ + &[141.0,-2.6,141.0,-9.1,137.6,-8.4,137.9,-5.4,133.0,-4.1,132.0,-2.8,133.7,-2.2,130.5,-0.9,134.0,-0.8,135.5,-3.4,137.4,-1.7,141.0,-2.6], + &[125.0,-8.9,123.5,-10.2,125.0,-8.9], + &[117.9,4.1,119.0,0.9,116.1,-4.0,110.2,-2.9,109.7,2.0,110.5,0.8,113.8,1.2,115.9,4.3,117.9,4.1], + &[129.4,-2.8,130.8,-3.9,127.9,-3.4,129.4,-2.8], + &[127.9,2.2,128.1,-0.9,127.9,2.2], + &[122.9,0.9,125.2,1.4,120.0,-0.5,123.3,-0.6,121.5,-1.9,123.2,-5.3,121.0,-2.6,119.8,-5.7,118.8,-2.8,119.8,0.2,122.9,0.9], + &[120.3,-10.3,119.0,-9.6,120.3,-10.3], + &[121.3,-8.5,122.9,-8.1,119.9,-8.8,121.3,-8.5], + &[118.3,-8.4,116.7,-9.0,118.3,-8.4], + &[108.5,-6.4,115.7,-8.4,105.4,-6.9,108.5,-6.4], + &[104.4,-1.1,106.1,-3.1,105.8,-5.9,102.6,-4.2,95.3,5.5,97.5,5.2,104.4,-1.1], + ], + }, + CountryOutline { + name: "Iran", + iso: "IR", + rings: &[ + &[48.6,29.9,45.4,34.0,46.1,35.7,44.1,39.4,48.1,39.6,50.8,36.9,56.6,38.1,61.1,36.5,60.9,29.8,63.3,26.8,61.5,25.1,57.4,25.7,48.6,29.9], + ], + }, + CountryOutline { + name: "Iraq", + iso: "IQ", + rings: &[ + &[39.2,32.2,41.3,36.4,44.8,37.2,48.6,29.9,44.7,29.2,39.2,32.2], + ], + }, + CountryOutline { + name: "Ireland", + iso: "IE", + rings: &[ + &[-6.2,53.9,-6.8,52.3,-10.0,51.8,-7.6,55.1,-6.2,53.9], + ], + }, + CountryOutline { + name: "Israel", + iso: "IL", + rings: &[ + &[35.7,32.7,34.9,29.5,34.3,31.2,35.7,32.7], + ], + }, + CountryOutline { + name: "Italy", + iso: "IT", + rings: &[ + &[10.4,46.9,13.8,46.5,12.6,44.1,18.3,39.8,16.9,40.4,15.7,37.9,15.4,40.0,10.2,43.9,7.4,43.7,6.8,46.0,10.4,46.9], + &[14.8,38.1,15.1,36.6,12.4,37.6,14.8,38.1], + &[8.7,40.9,9.8,40.5,8.8,38.9,8.7,40.9], + ], + }, + CountryOutline { + name: "Jamaica", + iso: "JM", + rings: &[ + &[-77.6,18.5,-76.2,17.9,-77.6,18.5], + ], + }, + CountryOutline { + name: "Japan", + iso: "JP", + rings: &[ + &[141.9,39.2,140.3,35.1,135.8,33.5,135.1,34.6,131.0,33.9,132.0,33.1,130.2,31.4,129.4,33.3,139.4,38.2,140.3,41.2,141.9,39.2], + &[144.6,44.0,145.5,43.3,140.0,41.6,142.0,45.6,144.6,44.0], + &[132.4,33.5,134.8,33.8,132.4,33.5], + ], + }, + CountryOutline { + name: "Jordan", + iso: "JO", + rings: &[ + &[35.5,32.4,38.8,33.4,39.2,32.2,37.0,31.5,38.0,30.5,36.1,29.2,34.9,29.5,35.5,32.4], + ], + }, + CountryOutline { + name: "Kazakhstan", + iso: "KZ", + rings: &[ + &[87.4,49.2,80.0,44.9,80.3,42.3,74.2,43.3,68.6,40.7,64.9,43.7,62.0,43.5,58.5,45.6,55.9,45.0,56.0,41.3,52.5,41.8,50.3,44.6,53.0,45.3,53.0,46.9,49.1,46.4,46.5,48.4,50.8,51.7,61.3,50.8,60.0,52.0,61.4,54.0,69.1,55.4,73.4,53.5,76.9,54.5,80.0,50.9,87.4,49.2], + ], + }, + CountryOutline { + name: "Kenya", + iso: "KE", + rings: &[ + &[39.2,-4.7,33.9,-0.9,35.3,5.5,38.1,3.6,41.9,3.9,41.6,-1.7,39.2,-4.7], + ], + }, + CountryOutline { + name: "Kosovo", + iso: "XK", + rings: &[ + &[20.6,41.9,20.6,43.2,21.8,42.7,20.6,41.9], + ], + }, + CountryOutline { + name: "Kuwait", + iso: "KW", + rings: &[ + &[48.0,30.0,48.4,28.6,46.6,29.1,48.0,30.0], + ], + }, + CountryOutline { + name: "Kyrgyzstan", + iso: "KG", + rings: &[ + &[71.0,42.3,74.2,43.3,80.3,42.3,73.7,39.4,69.5,39.5,73.1,40.9,71.0,42.3], + ], + }, + CountryOutline { + name: "Laos", + iso: "LA", + rings: &[ + &[107.4,14.2,105.2,14.3,104.0,18.2,101.1,17.5,100.1,20.4,101.7,22.3,104.4,20.8,103.9,19.3,107.4,14.2], + ], + }, + CountryOutline { + name: "Latvia", + iso: "LV", + rings: &[ + &[27.3,57.5,28.2,56.2,26.5,55.6,21.1,56.0,22.5,57.8,27.3,57.5], + ], + }, + CountryOutline { + name: "Lebanon", + iso: "LB", + rings: &[ + &[35.8,33.3,36.4,34.6,35.8,33.3], + ], + }, + CountryOutline { + name: "Lesotho", + iso: "LS", + rings: &[ + &[29.0,-29.0,28.1,-30.5,27.0,-29.9,29.0,-29.0], + ], + }, + CountryOutline { + name: "Liberia", + iso: "LR", + rings: &[ + &[-8.4,7.7,-7.7,4.4,-11.4,6.8,-10.2,8.4,-8.4,7.7], + ], + }, + CountryOutline { + name: "Libya", + iso: "LY", + rings: &[ + &[25.0,22.0,23.8,19.6,10.3,24.4,10.0,31.4,11.5,33.1,19.1,30.3,20.9,32.7,24.9,31.9,25.0,22.0], + ], + }, + CountryOutline { + name: "Lithuania", + iso: "LT", + rings: &[ + &[26.5,55.6,23.5,53.9,21.1,56.0,26.5,55.6], + ], + }, + CountryOutline { + name: "Madagascar", + iso: "MG", + rings: &[ + &[49.5,-12.5,50.4,-15.7,47.1,-24.9,45.4,-25.6,43.3,-22.8,44.0,-17.4,49.5,-12.5], + ], + }, + CountryOutline { + name: "Malawi", + iso: "MW", + rings: &[ + &[32.8,-9.2,35.7,-14.6,35.0,-16.8,32.7,-13.7,32.8,-9.2], + ], + }, + CountryOutline { + name: "Malaysia", + iso: "MY", + rings: &[ + &[100.1,6.5,103.0,5.5,104.2,1.3,101.4,2.8,100.1,6.5], + &[117.9,4.1,115.9,4.3,114.6,1.4,109.8,1.3,115.3,4.3,116.7,6.9,119.2,5.4,117.9,4.1], + ], + }, + CountryOutline { + name: "Mali", + iso: "ML", + rings: &[ + &[-11.5,12.4,-11.7,15.4,-5.5,15.5,-6.5,25.0,4.3,19.2,3.6,15.6,-4.0,13.5,-5.4,10.4,-11.5,12.4], + ], + }, + CountryOutline { + name: "Mauritania", + iso: "MR", + rings: &[ + &[-17.1,21.0,-12.9,21.3,-12.0,25.9,-8.7,25.9,-8.7,27.4,-4.9,25.0,-6.5,25.0,-5.5,15.5,-12.2,14.6,-14.6,16.6,-16.5,16.1,-17.1,21.0], + ], + }, + CountryOutline { + name: "Mexico", + iso: "MX", + rings: &[ + &[-117.1,32.5,-106.5,31.8,-103.9,29.3,-101.7,29.8,-97.1,25.9,-97.9,22.4,-95.9,18.8,-91.4,18.9,-90.3,21.0,-87.1,21.5,-87.8,18.3,-91.0,17.8,-90.5,16.1,-92.2,14.5,-103.5,18.3,-113.1,31.2,-114.9,31.4,-109.9,22.8,-115.1,27.7,-114.2,28.6,-117.1,32.5], + ], + }, + CountryOutline { + name: "Moldova", + iso: "MD", + rings: &[ + &[26.6,48.2,30.0,46.4,28.2,45.5,26.6,48.2], + ], + }, + CountryOutline { + name: "Mongolia", + iso: "MN", + rings: &[ + &[87.8,49.3,92.2,50.8,97.3,49.7,98.9,52.0,108.5,49.3,116.7,49.9,115.7,47.7,119.8,47.0,105.0,41.6,96.3,42.7,90.9,45.3,91.0,46.9,87.8,49.3], + ], + }, + CountryOutline { + name: "Montenegro", + iso: "ME", + rings: &[ + &[20.1,42.6,18.5,42.5,20.1,42.6], + ], + }, + CountryOutline { + name: "Morocco", + iso: "MA", + rings: &[ + &[-2.2,35.2,-1.3,32.3,-8.7,28.8,-8.8,27.1,-11.4,26.9,-14.8,21.5,-17.0,21.4,-14.4,26.3,-9.6,29.9,-8.7,33.2,-5.9,35.8,-2.2,35.2], + ], + }, + CountryOutline { + name: "Mozambique", + iso: "MZ", + rings: &[ + &[34.6,-11.5,40.3,-10.3,40.8,-14.7,34.8,-19.8,35.5,-24.1,32.1,-26.7,31.2,-22.3,32.8,-16.7,30.2,-14.8,33.2,-14.0,35.0,-16.8,34.6,-11.5], + ], + }, + CountryOutline { + name: "Myanmar", + iso: "MM", + rings: &[ + &[100.1,20.4,97.4,18.4,99.6,11.9,98.6,9.9,97.2,16.9,94.2,16.0,92.3,21.5,97.3,28.3,98.7,27.5,97.6,23.9,101.2,21.8,100.1,20.4], + ], + }, + CountryOutline { + name: "N. Cyprus", + iso: "-99", + rings: &[ + &[32.7,35.1,34.6,35.7,32.7,35.1], + ], + }, + CountryOutline { + name: "Namibia", + iso: "NA", + rings: &[ + &[19.9,-24.8,19.9,-28.5,16.3,-28.6,11.7,-17.3,25.1,-17.6,20.9,-18.3,19.9,-24.8], + ], + }, + CountryOutline { + name: "Nepal", + iso: "NP", + rings: &[ + &[88.1,27.9,87.2,26.4,80.1,28.8,81.5,30.4,88.1,27.9], + ], + }, + CountryOutline { + name: "Netherlands", + iso: "NL", + rings: &[ + &[6.9,53.5,6.2,50.8,3.3,51.3,6.9,53.5], + ], + }, + CountryOutline { + name: "New Caledonia", + iso: "NC", + rings: &[ + &[165.8,-21.1,167.1,-22.2,164.0,-20.1,165.8,-21.1], + ], + }, + CountryOutline { + name: "New Zealand", + iso: "NZ", + rings: &[ + &[176.9,-40.1,174.7,-41.3,174.7,-37.4,172.6,-34.5,176.0,-37.6,178.5,-37.7,176.9,-40.1], + &[169.7,-43.6,172.8,-40.5,174.2,-41.3,170.6,-45.9,166.7,-46.2,169.7,-43.6], + ], + }, + CountryOutline { + name: "Nicaragua", + iso: "NI", + rings: &[ + &[-83.7,10.9,-87.7,12.9,-83.1,15.0,-83.7,10.9], + ], + }, + CountryOutline { + name: "Niger", + iso: "NE", + rings: &[ + &[14.9,22.9,15.9,20.4,13.5,14.4,14.2,12.5,5.4,13.9,3.6,11.7,1.0,12.9,0.4,14.9,3.6,15.6,4.3,19.2,12.0,23.5,14.9,22.9], + ], + }, + CountryOutline { + name: "Nigeria", + iso: "NG", + rings: &[ + &[2.7,6.3,4.4,13.7,13.1,13.6,14.6,12.1,11.7,7.0,8.5,4.8,5.9,4.3,2.7,6.3], + ], + }, + CountryOutline { + name: "North Korea", + iso: "KP", + rings: &[ + &[130.6,42.4,127.5,39.8,128.2,38.4,124.7,38.1,125.1,40.6,130.6,42.4], + ], + }, + CountryOutline { + name: "North Macedonia", + iso: "MK", + rings: &[ + &[22.4,42.3,23.0,41.3,20.6,41.1,22.4,42.3], + ], + }, + CountryOutline { + name: "Norway", + iso: "NO", + rings: &[ + &[15.1,79.7,21.5,79.0,15.9,76.8,10.4,79.7,15.1,79.7], + &[31.1,69.6,18.0,68.6,12.6,64.1,11.0,58.9,5.7,58.6,5.0,62.0,19.2,69.8,28.2,71.2,31.1,69.6], + &[27.4,80.1,17.4,80.3,27.4,80.1], + &[24.7,77.9,20.7,77.7,24.7,77.9], + ], + }, + CountryOutline { + name: "Oman", + iso: "OM", + rings: &[ + &[55.2,22.7,56.4,24.9,59.8,22.3,57.7,18.9,53.1,16.7,52.0,19.0,55.0,20.0,55.2,22.7], + ], + }, + CountryOutline { + name: "Pakistan", + iso: "PK", + rings: &[ + &[77.8,35.5,73.7,34.3,75.3,32.3,69.5,26.9,71.0,24.4,61.5,25.1,63.3,26.8,60.9,29.8,66.3,29.9,71.8,36.5,75.2,37.1,77.8,35.5], + ], + }, + CountryOutline { + name: "Panama", + iso: "PA", + rings: &[ + &[-77.4,8.7,-77.9,7.2,-79.1,9.0,-80.9,7.2,-82.9,9.5,-77.4,8.7], + ], + }, + CountryOutline { + name: "Papua New Guinea", + iso: "PG", + rings: &[ + &[141.0,-2.6,147.6,-6.1,147.2,-7.4,150.7,-10.6,144.7,-7.6,141.0,-9.1,141.0,-2.6], + &[152.6,-3.7,152.8,-4.8,150.7,-2.7,152.6,-3.7], + &[151.3,-5.8,148.3,-5.7,152.1,-4.1,151.3,-5.8], + &[154.8,-5.3,155.9,-6.8,154.8,-5.3], + ], + }, + CountryOutline { + name: "Paraguay", + iso: "PY", + rings: &[ + &[-58.2,-20.2,-57.9,-22.1,-54.3,-24.0,-55.7,-27.4,-58.6,-27.1,-57.8,-25.2,-62.7,-22.2,-61.8,-19.6,-58.2,-20.2], + ], + }, + CountryOutline { + name: "Peru", + iso: "PE", + rings: &[ + &[-69.9,-4.3,-72.9,-5.3,-74.0,-7.5,-68.7,-12.6,-70.4,-18.3,-76.0,-14.6,-81.4,-4.7,-80.3,-3.4,-78.6,-4.5,-75.1,-0.1,-73.1,-2.3,-70.0,-2.7,-69.9,-4.3], + ], + }, + CountryOutline { + name: "Philippines", + iso: "PH", + rings: &[ + &[122.6,10.0,124.1,11.2,123.0,9.0,122.6,10.0], + &[126.4,8.4,125.4,5.6,123.6,7.8,121.9,7.2,125.4,9.8,126.4,8.4], + &[118.5,9.3,117.2,8.4,119.5,11.4,118.5,9.3], + &[122.3,18.2,121.7,14.3,124.1,12.5,119.9,15.4,120.7,18.5,122.3,18.2], + &[125.5,12.2,124.8,10.1,124.3,12.6,125.5,12.2], + ], + }, + CountryOutline { + name: "Poland", + iso: "PL", + rings: &[ + &[23.5,53.9,24.0,50.7,22.8,49.0,16.2,50.4,14.1,53.0,17.6,54.9,23.5,53.9], + ], + }, + CountryOutline { + name: "Portugal", + iso: "PT", + rings: &[ + &[-9.0,41.9,-6.4,41.4,-7.9,36.8,-9.5,38.7,-9.0,41.9], + ], + }, + CountryOutline { + name: "Puerto Rico", + iso: "PR", + rings: &[ + &[-66.3,18.5,-67.2,17.9,-66.3,18.5], + ], + }, + CountryOutline { + name: "Qatar", + iso: "QA", + rings: &[ + &[50.8,24.8,51.3,26.1,50.8,24.8], + ], + }, + CountryOutline { + name: "Romania", + iso: "RO", + rings: &[ + &[28.2,45.5,29.6,45.3,28.6,43.7,22.9,43.8,20.2,46.1,26.6,48.2,28.2,45.5], + ], + }, + CountryOutline { + name: "Russia", + iso: "RU", + rings: &[ + &[49.1,46.4,46.7,44.6,47.8,41.2,36.7,45.2,40.1,49.6,31.8,52.1,32.7,53.4,30.9,55.6,27.3,57.5,29.1,60.0,28.1,60.5,31.5,62.9,30.0,63.6,28.6,69.1,32.1,69.9,41.1,67.5,38.4,66.0,33.2,66.6,37.0,63.8,37.2,65.1,43.9,66.1,43.5,68.6,46.3,68.3,46.3,66.7,53.7,68.9,59.9,68.3,60.6,69.9,68.5,68.1,66.7,71.0,69.9,73.0,72.8,72.2,71.8,71.4,73.7,68.4,71.3,66.3,72.4,66.2,75.1,67.8,73.1,71.4,74.7,72.8,76.4,71.2,81.5,71.8,80.5,73.6,104.4,77.7,114.1,75.8,109.4,74.2,127.0,73.6,131.3,70.8,139.9,71.5,139.1,72.4,140.5,72.8,159.0,70.9,160.9,69.4,180.0,69.0,180.0,65.0,177.4,64.6,179.2,62.3,170.3,59.9,163.5,59.9,162.0,58.2,163.2,57.6,162.1,54.9,156.8,51.0,155.9,56.8,164.5,62.6,160.1,60.5,156.7,61.4,154.2,59.8,155.0,59.1,142.2,59.0,135.1,54.7,141.3,53.1,140.1,48.4,134.9,43.4,130.8,42.2,131.0,45.0,133.1,45.1,135.0,48.5,131.0,47.8,123.6,53.5,120.2,52.8,117.9,49.5,108.5,49.3,98.9,52.0,97.3,49.7,92.2,50.8,87.4,49.2,80.0,50.9,76.9,54.5,73.4,53.5,69.1,55.4,61.4,54.0,60.0,52.0,61.3,50.8,50.8,51.7,47.5,50.5,46.5,48.4,49.1,46.4], + &[93.8,81.0,100.2,79.8,97.8,78.8,91.2,80.3,93.8,81.0], + &[102.8,79.3,105.4,78.7,99.4,77.9,102.8,79.3], + &[138.8,76.1,145.1,75.6,137.0,75.3,138.8,76.1], + &[148.2,75.3,150.7,75.1,146.1,75.2,148.2,75.3], + &[139.9,73.4,143.6,73.2,139.9,73.4], + &[44.8,80.6,51.5,80.7,44.8,80.6], + &[22.7,54.3,19.7,54.4,22.7,54.3], + &[53.5,73.7,61.2,76.3,68.9,76.5,58.5,74.3,55.4,72.4,57.5,70.7,51.6,71.5,53.5,73.7], + &[142.9,53.7,144.7,49.0,143.2,49.3,143.5,46.1,142.1,46.0,141.7,53.3,142.9,53.7], + &[-174.9,67.2,-169.9,66.0,-173.0,64.3,-178.7,66.1,-180.0,65.0,-180.0,69.0,-174.9,67.2], + &[-178.7,70.9,-180.0,71.5,-177.6,71.3,-178.7,70.9], + &[33.4,46.0,36.5,45.5,33.9,44.4,32.5,45.3,33.4,46.0], + ], + }, + CountryOutline { + name: "Rwanda", + iso: "RW", + rings: &[ + &[30.4,-1.1,29.0,-2.8,30.4,-1.1], + ], + }, + CountryOutline { + name: "S. Sudan", + iso: "SS", + rings: &[ + &[30.8,3.5,23.9,8.6,25.8,10.4,31.4,9.8,33.2,12.2,33.0,7.8,35.3,5.5,30.8,3.5], + ], + }, + CountryOutline { + name: "Saudi Arabia", + iso: "SA", + rings: &[ + &[35.0,29.4,39.2,32.2,47.5,29.0,52.0,23.0,55.2,22.7,55.0,20.0,47.0,16.9,43.4,17.6,42.8,16.3,35.0,29.4], + ], + }, + CountryOutline { + name: "Senegal", + iso: "SN", + rings: &[ + &[-16.7,13.6,-17.6,14.7,-14.6,16.6,-11.5,12.4,-16.7,12.4,-13.8,13.5,-16.7,13.6], + ], + }, + CountryOutline { + name: "Serbia", + iso: "RS", + rings: &[ + &[18.8,45.9,22.7,44.6,22.5,42.5,19.2,43.5,18.8,45.9], + ], + }, + CountryOutline { + name: "Sierra Leone", + iso: "SL", + rings: &[ + &[-13.2,8.9,-11.1,10.0,-10.2,8.4,-11.4,6.8,-13.2,8.9], + ], + }, + CountryOutline { + name: "Slovakia", + iso: "SK", + rings: &[ + &[22.6,49.1,16.9,48.5,22.6,49.1], + ], + }, + CountryOutline { + name: "Slovenia", + iso: "SI", + rings: &[ + &[13.8,46.5,16.6,46.5,15.3,45.5,13.8,46.5], + ], + }, + CountryOutline { + name: "Solomon Is.", + iso: "SB", + rings: &[ + &[159.6,-8.0,158.2,-7.4,159.6,-8.0], + ], + }, + CountryOutline { + name: "Somalia", + iso: "SO", + rings: &[ + &[41.6,-1.7,41.0,2.8,45.0,5.0,48.9,9.5,48.9,11.4,51.1,12.0,48.6,5.3,41.6,-1.7], + ], + }, + CountryOutline { + name: "Somaliland", + iso: "-99", + rings: &[ + &[48.9,11.4,47.8,8.0,42.6,10.6,48.9,11.4], + ], + }, + CountryOutline { + name: "South Africa", + iso: "ZA", + rings: &[ + &[16.3,-28.6,19.9,-28.5,19.9,-24.8,21.6,-26.7,25.7,-25.5,29.4,-22.1,31.2,-22.3,31.9,-24.4,30.7,-26.7,32.8,-26.7,28.2,-32.8,20.1,-34.8,18.4,-34.1,16.3,-28.6], + ], + }, + CountryOutline { + name: "South Korea", + iso: "KR", + rings: &[ + &[126.2,37.7,128.3,38.6,129.1,35.1,126.5,34.4,126.2,37.7], + ], + }, + CountryOutline { + name: "Spain", + iso: "ES", + rings: &[ + &[-7.5,37.1,-6.4,41.4,-9.4,43.0,3.0,42.5,-2.1,36.7,-7.5,37.1], + ], + }, + CountryOutline { + name: "Sri Lanka", + iso: "LK", + rings: &[ + &[81.8,7.5,80.3,6.0,80.1,9.8,81.8,7.5], + ], + }, + CountryOutline { + name: "Sudan", + iso: "SD", + rings: &[ + &[24.6,8.2,21.9,12.6,25.0,22.0,36.9,22.0,38.4,18.0,34.0,8.7,32.7,12.2,31.4,9.8,25.1,10.3,24.6,8.2], + ], + }, + CountryOutline { + name: "Suriname", + iso: "SR", + rings: &[ + &[-54.5,2.3,-56.5,1.9,-57.6,3.3,-57.1,6.0,-54.0,5.8,-54.5,2.3], + ], + }, + CountryOutline { + name: "Sweden", + iso: "SE", + rings: &[ + &[11.0,58.9,12.6,61.3,11.9,63.1,16.8,68.0,20.6,69.1,23.5,67.9,23.9,66.0,17.8,62.7,17.1,61.3,18.8,60.1,15.9,56.1,12.9,55.4,11.0,58.9], + ], + }, + CountryOutline { + name: "Switzerland", + iso: "CH", + rings: &[ + &[9.6,47.5,10.4,46.5,6.0,46.3,9.6,47.5], + ], + }, + CountryOutline { + name: "Syria", + iso: "SY", + rings: &[ + &[35.7,32.7,36.7,36.8,42.3,37.2,41.0,34.4,35.7,32.7], + ], + }, + CountryOutline { + name: "Taiwan", + iso: "TW", + rings: &[ + &[121.8,24.4,120.7,22.0,120.1,23.6,121.8,24.4], + ], + }, + CountryOutline { + name: "Tajikistan", + iso: "TJ", + rings: &[ + &[67.8,37.1,67.7,39.6,70.7,41.0,69.5,39.5,73.7,39.4,75.0,37.4,71.8,36.7,70.8,38.5,67.8,37.1], + ], + }, + CountryOutline { + name: "Tanzania", + iso: "TZ", + rings: &[ + &[33.9,-0.9,39.2,-4.7,39.5,-10.9,34.6,-11.5,29.6,-6.5,30.4,-1.1,33.9,-0.9], + ], + }, + CountryOutline { + name: "Thailand", + iso: "TH", + rings: &[ + &[105.2,14.3,103.0,14.2,102.6,12.2,100.1,13.4,99.2,9.2,102.1,6.2,101.2,5.7,98.2,8.4,99.6,11.9,97.4,18.4,100.1,20.4,101.1,17.5,104.7,17.4,105.2,14.3], + ], + }, + CountryOutline { + name: "Timor-Leste", + iso: "TL", + rings: &[ + &[125.0,-8.9,127.3,-8.4,125.0,-8.9], + ], + }, + CountryOutline { + name: "Togo", + iso: "TG", + rings: &[ + &[0.9,11.0,1.1,5.9,0.9,11.0], + ], + }, + CountryOutline { + name: "Tunisia", + iso: "TN", + rings: &[ + &[9.5,30.3,7.5,34.1,9.5,37.3,11.0,37.1,10.1,34.3,11.5,33.1,9.5,30.3], + ], + }, + CountryOutline { + name: "Turkey", + iso: "TR", + rings: &[ + &[44.8,37.2,29.7,36.1,27.6,36.7,26.2,39.5,33.5,42.0,42.6,41.6,44.8,39.7,44.8,37.2], + &[26.1,41.8,29.0,41.3,26.4,40.2,26.1,41.8], + ], + }, + CountryOutline { + name: "Turkmenistan", + iso: "TM", + rings: &[ + &[52.5,41.8,57.1,41.3,58.6,42.8,66.5,37.4,62.2,35.3,57.3,38.0,53.9,37.2,52.7,40.0,54.7,41.0,52.5,41.8], + ], + }, + CountryOutline { + name: "Uganda", + iso: "UG", + rings: &[ + &[33.9,-0.9,29.6,-1.3,31.2,3.8,34.5,3.6,33.9,-0.9], + ], + }, + CountryOutline { + name: "Ukraine", + iso: "UA", + rings: &[ + &[31.8,52.1,40.1,49.6,39.7,47.9,35.0,45.7,31.7,46.7,28.7,45.3,30.0,46.4,28.7,48.1,22.1,48.4,23.5,51.6,31.8,52.1], + ], + }, + CountryOutline { + name: "United Arab Emirates", + iso: "AE", + rings: &[ + &[51.6,24.2,56.3,25.7,55.0,22.5,51.6,24.2], + ], + }, + CountryOutline { + name: "United Kingdom", + iso: "GB", + rings: &[ + &[-6.2,53.9,-7.6,55.1,-6.2,53.9], + &[-3.1,53.4,-6.1,56.8,-5.0,58.6,-2.0,57.7,-3.1,56.0,1.7,52.7,1.4,51.3,-5.8,50.2,-3.4,51.4,-5.3,52.0,-4.6,53.5,-3.1,53.4], + ], + }, + CountryOutline { + name: "United States of America", + iso: "US", + rings: &[ + &[-122.8,49.0,-88.4,48.3,-82.6,45.3,-82.7,41.7,-71.5,45.0,-69.2,47.4,-67.0,44.8,-70.1,43.7,-70.0,41.6,-75.5,39.5,-75.9,37.2,-76.3,39.2,-77.0,38.2,-75.7,35.6,-81.3,31.4,-80.4,25.2,-83.7,29.9,-86.4,30.4,-94.7,29.5,-97.5,25.8,-101.0,29.4,-103.9,29.3,-106.5,31.8,-117.1,32.5,-120.6,34.6,-124.4,40.3,-124.7,48.2,-122.6,47.1,-122.8,49.0], + &[-166.5,60.4,-165.6,59.9,-167.5,60.2,-166.5,60.4], + &[-153.2,58.0,-152.1,57.6,-154.5,57.0,-153.2,58.0], + &[-141.0,69.7,-141.0,60.3,-137.5,58.9,-135.5,59.8,-130.0,55.9,-130.5,54.8,-134.1,58.1,-139.9,59.5,-147.1,60.9,-151.7,59.2,-150.6,61.3,-158.4,56.0,-164.9,54.6,-157.0,58.9,-162.0,58.7,-165.3,60.5,-165.7,62.1,-160.8,64.8,-168.1,65.7,-161.7,66.1,-166.2,68.9,-156.6,71.4,-141.0,69.7], + &[-171.7,63.8,-168.7,63.3,-171.7,63.8], + ], + }, + CountryOutline { + name: "Uruguay", + iso: "UY", + rings: &[ + &[-57.6,-30.2,-53.8,-32.0,-53.8,-34.4,-58.4,-33.9,-57.6,-30.2], + ], + }, + CountryOutline { + name: "Uzbekistan", + iso: "UZ", + rings: &[ + &[56.0,41.3,55.9,45.0,58.5,45.6,62.0,43.5,64.9,43.7,68.3,40.7,71.0,42.3,73.1,40.9,67.7,39.6,67.8,37.1,58.6,42.8,56.0,41.3], + ], + }, + CountryOutline { + name: "Venezuela", + iso: "VE", + rings: &[ + &[-60.7,5.2,-64.8,4.1,-63.4,2.2,-66.3,0.7,-67.8,2.8,-67.3,6.1,-72.0,7.0,-72.9,10.5,-71.3,11.8,-71.3,9.1,-69.9,12.2,-68.2,10.6,-61.9,10.7,-59.8,8.4,-60.7,5.2], + ], + }, + CountryOutline { + name: "Vietnam", + iso: "VN", + rings: &[ + &[104.3,10.5,107.5,12.3,107.6,15.2,102.2,22.5,105.3,23.4,108.1,21.6,105.7,19.1,108.9,15.3,109.2,11.7,105.2,8.6,104.3,10.5], + ], + }, + CountryOutline { + name: "W. Sahara", + iso: "EH", + rings: &[ + &[-8.7,27.7,-8.7,25.9,-12.0,25.9,-12.9,21.3,-17.1,21.0,-14.8,21.5,-11.4,26.9,-8.7,27.7], + ], + }, + CountryOutline { + name: "Yemen", + iso: "YE", + rings: &[ + &[52.0,19.0,52.2,15.6,43.5,12.6,43.4,17.6,47.0,16.9,52.0,19.0], + ], + }, + CountryOutline { + name: "Zambia", + iso: "ZM", + rings: &[ + &[30.7,-8.3,33.2,-9.7,33.2,-14.0,27.0,-17.9,23.2,-17.5,21.9,-12.9,24.0,-12.9,23.9,-10.9,29.7,-13.3,28.4,-9.2,30.7,-8.3], + ], + }, + CountryOutline { + name: "Zimbabwe", + iso: "ZW", + rings: &[ + &[31.2,-22.3,28.0,-21.5,25.3,-17.7,30.3,-15.5,32.8,-16.7,31.2,-22.3], + ], + }, +]; diff --git a/ports/rust/src/ui.rs b/ports/rust/src/ui.rs index 2d834ef..1374095 100644 --- a/ports/rust/src/ui.rs +++ b/ports/rust/src/ui.rs @@ -795,6 +795,24 @@ impl<'a> Container<'a> { self.add(constraint, move |s| crate::graphics::draw_canvas(&s, &options)) } + /// A world map, and the country under whatever gets clicked. + /// + /// The click is answered by turning the cell back into degrees and testing + /// it against the outlines, so the answer is the country actually under the + /// cursor. Bounding boxes would be cheaper and wrong: Russia's covers most + /// of the northern hemisphere and Chile's covers Argentina. + pub fn world_map(&mut self, options: w::WorldMapOptions, id: &str) -> &mut Self { + let constraint = self.filling(); + let ctx = self.ctx.clone(); + let id = id.to_string(); + self.add(constraint, move |s| { + w::draw_world_map(&s, &options); + if !id.is_empty() { + ctx.hit(&id, s.hit_rect(), 0); + } + }) + } + pub fn sparkline(&mut self, options: w::SparklineWidgetOptions) -> &mut Self { let constraint = self.leaf(1); self.add(constraint, move |s| w::draw_sparkline(&s, &options)) diff --git a/ports/rust/src/widgets/mod.rs b/ports/rust/src/widgets/mod.rs index 0ae7f78..013c4ef 100644 --- a/ports/rust/src/widgets/mod.rs +++ b/ports/rust/src/widgets/mod.rs @@ -9,6 +9,7 @@ pub mod meters; pub mod scrollbar; pub mod shadow; pub mod surface; +pub mod world; pub mod table; pub mod text; @@ -35,6 +36,7 @@ pub use scrollbar::{ }; pub use shadow::{dim_rect, draw_shadow, ShadowOptions}; pub use surface::{draw_clear, draw_fill, ClearOptions, FillOptions}; +pub use world::{country_at_cell, draw_world_map, WorldMapOptions}; pub use table::{ draw_list, draw_log, draw_table, draw_tree, resolve_offset, TableColumn, ListItem, ListOptions, LogEntry, LogOptions, TableOptions, TableRow, TreeNode, TreeOptions, diff --git a/ports/rust/src/widgets/world.rs b/ports/rust/src/widgets/world.rs new file mode 100644 index 0000000..b9b4469 --- /dev/null +++ b/ports/rust/src/widgets/world.rs @@ -0,0 +1,71 @@ +//! A world map you can click. +//! +//! The drawing is the canvas doing what it already does -- polylines in the +//! caller's own coordinates, which for a map are degrees. What this adds is the +//! other direction: turning a click back into a country. + +use crate::color::Color; +use crate::graphics::canvas::{draw_canvas, Bounds, CanvasOptions}; +use crate::graphics::world::{ + country_at, degrees_at, world_shapes, WorldShapeOptions, WORLD_X, WORLD_Y, +}; +use crate::graphics::world_data::CountryOutline; +use crate::surface::Surface; + +#[derive(Clone, Debug, Default)] +pub struct WorldMapOptions { + /// The window on the globe. Defaults to all of it. + pub x: Option, + pub y: Option, + /// Coastline colour. + pub color: Option, + /// Countries to pick out, by name or ISO code. + pub highlight: Vec, + pub highlight_color: Option, + pub background: Option, + pub grid: bool, +} + +pub fn draw_world_map(surface: &Surface, options: &WorldMapOptions) { + if surface.is_empty() { + return; + } + let theme = surface.theme.clone(); + draw_canvas( + surface, + &CanvasOptions { + shapes: world_shapes(&WorldShapeOptions { + color: Some(options.color.unwrap_or(theme.border)), + highlight: options.highlight.clone(), + highlight_color: Some(options.highlight_color.unwrap_or(theme.accent)), + }), + x: Some(options.x.unwrap_or(WORLD_X)), + y: Some(options.y.unwrap_or(WORLD_Y)), + background: options.background, + grid: options.grid, + ..Default::default() + }, + ); +} + +/// The country under a cell of a map drawn with these bounds. +/// +/// Exposed so a caller can answer a hover as well as a click, and so the +/// arithmetic that has to agree with the drawing lives in one place. +pub fn country_at_cell( + column: usize, + row: usize, + width: usize, + height: usize, + options: &WorldMapOptions, +) -> Option<&'static CountryOutline> { + let (lon, lat) = degrees_at( + column, + row, + width, + height, + options.x.unwrap_or(WORLD_X), + options.y.unwrap_or(WORLD_Y), + )?; + country_at(lon, lat) +} diff --git a/ports/rust/tests/conformance_widgets.rs b/ports/rust/tests/conformance_widgets.rs index 6a28c92..40b9f9d 100644 --- a/ports/rust/tests/conformance_widgets.rs +++ b/ports/rust/tests/conformance_widgets.rs @@ -176,6 +176,22 @@ fn draw_scene(name: &str, s: &Surface) { &ShadowOptions { color: Some(Color::rgb(0x10, 0x14, 0x18)), ..Default::default() }, ); } + "world" => draw_world_map(s, &WorldMapOptions::default()), + "world-zoom" => draw_world_map( + s, + &WorldMapOptions { + x: Some(Bounds { min: 112.0, max: 156.0 }), + y: Some(Bounds { min: 24.0, max: 50.0 }), + ..Default::default() + }, + ), + "world-highlight" => draw_world_map( + s, + &WorldMapOptions { + highlight: vec!["Brazil".into(), "JP".into()], + ..Default::default() + }, + ), "badge" => { draw_badge(s, &BadgeOptions::new("LIVE")); } diff --git a/ports/zig/build.zig b/ports/zig/build.zig index 687e773..6785e8a 100644 --- a/ports/zig/build.zig +++ b/ports/zig/build.zig @@ -46,7 +46,7 @@ pub fn build(b: *std.Build) void { // Examples are separate executables, so `zig build run-screenshot` works // without a TTY while `run-dashboard` takes one over. - for ([_][]const u8{ "hello", "dashboard", "screenshot", "widgets", "collapse", "justify_parity" }) |name| { + for ([_][]const u8{ "hello", "dashboard", "screenshot", "widgets", "collapse", "justify_parity", "world-probe" }) |name| { const command_name = if (std.mem.eql(u8, name, "dashboard")) "dashboard-mini" else name; const exe = b.addExecutable(.{ .name = command_name, diff --git a/ports/zig/examples/world-probe.zig b/ports/zig/examples/world-probe.zig new file mode 100644 index 0000000..26043e3 --- /dev/null +++ b/ports/zig/examples/world-probe.zig @@ -0,0 +1,66 @@ +//! Prints what the country lookup answers for a fixed set of points. +//! +//! The same probe exists for every port, so "the ports agree about the world" is +//! a diff rather than a hope. +const std = @import("std"); +const hqtui = @import("hqtui"); + +const world = hqtui.graphics.world; +const widgets = hqtui.widgets; + +pub fn main(init: std.process.Init) !void { + const stdout = std.Io.File.stdout(); + // A line at a time straight out: this prints a few dozen lines once, so a + // buffer of its own would be ceremony. + var line: [256]u8 = undefined; + + const places = [_]struct { name: []const u8, lon: f64, lat: f64 }{ + .{ .name = "Paris", .lon = 2.35, .lat = 48.86 }, + .{ .name = "Tokyo", .lon = 139.7, .lat = 35.7 }, + .{ .name = "Cairo", .lon = 31.2, .lat = 30.0 }, + .{ .name = "Brasilia", .lon = -47.9, .lat = -15.8 }, + .{ .name = "Canberra", .lon = 149.1, .lat = -35.3 }, + .{ .name = "Denver", .lon = -105.0, .lat = 39.7 }, + .{ .name = "Moscow", .lon = 37.6, .lat = 55.75 }, + .{ .name = "Delhi", .lon = 77.2, .lat = 28.6 }, + .{ .name = "Nairobi", .lon = 36.8, .lat = -1.3 }, + .{ .name = "Pacific", .lon = -140.0, .lat = 0.0 }, + .{ .name = "Atlantic", .lon = -30.0, .lat = 0.0 }, + .{ .name = "SouthernOcean", .lon = 80.0, .lat = -40.0 }, + .{ .name = "NorthPacific", .lon = -150.0, .lat = 40.0 }, + }; + for (places) |place| { + const found = world.countryAt(place.lon, place.lat); + const text = try std.fmt.bufPrint(&line, "{s} {s}\n", .{ + place.name, + if (found) |c| c.name else "-", + }); + try stdout.writeStreamingAll(init.io, text); + } + + // The cell path, which has to agree with what the canvas drew. + const cells = [_][2]usize{ .{ 173, 28 }, .{ 74, 2 }, .{ 20, 25 }, .{ 88, 7 } }; + for (cells) |cell| { + const found = widgets.countryAtCell(cell[0], cell[1], 200, 50, .{}); + const text = try std.fmt.bufPrint(&line, "cell:{d},{d} {s}\n", .{ + cell[0], + cell[1], + if (found) |c| c.name else "-", + }); + try stdout.writeStreamingAll(init.io, text); + } + + // And the projection itself, so a drift shows up as a number rather than as + // a country that happens to still be right. + const probes = [_][2]usize{ .{ 0, 0 }, .{ 99, 25 }, .{ 50, 13 } }; + for (probes) |cell| { + const at = world.degreesAt(cell[0], cell[1], 100, 26, world.WORLD_X, world.WORLD_Y).?; + const text = try std.fmt.bufPrint(&line, "degrees:{d},{d} {d:.4} {d:.4}\n", .{ + cell[0], + cell[1], + at.lon, + at.lat, + }); + try stdout.writeStreamingAll(init.io, text); + } +} diff --git a/ports/zig/src/conformance_widgets.zig b/ports/zig/src/conformance_widgets.zig index 35017c8..3d04a3e 100644 --- a/ports/zig/src/conformance_widgets.zig +++ b/ports/zig/src/conformance_widgets.zig @@ -137,6 +137,15 @@ fn drawScene(allocator: std.mem.Allocator, name: []const u8, s: Surface) !void { w.drawShadow(s, .{ .x = 2, .y = 1, .width = 6, .height = 2 }, .{ .color = hqtui_color.Color.rgb(0x10, 0x14, 0x18), }); + } else if (eq(u8, name, "world")) { + try w.drawWorldMap(allocator, s, .{}); + } else if (eq(u8, name, "world-zoom")) { + try w.drawWorldMap(allocator, s, .{ + .x = .{ .min = 112, .max = 156 }, + .y = .{ .min = 24, .max = 50 }, + }); + } else if (eq(u8, name, "world-highlight")) { + try w.drawWorldMap(allocator, s, .{ .highlight = &.{ "Brazil", "JP" } }); } else if (eq(u8, name, "badge")) { _ = w.drawBadge(s, .{ .text = "LIVE" }); } else if (eq(u8, name, "badge-outline")) { diff --git a/ports/zig/src/graphics.zig b/ports/zig/src/graphics.zig index 42904c8..83377c8 100644 --- a/ports/zig/src/graphics.zig +++ b/ports/zig/src/graphics.zig @@ -4,6 +4,8 @@ pub const blocks = @import("graphics/blocks.zig"); pub const braille = @import("graphics/braille.zig"); pub const canvas_mod = @import("graphics/canvas.zig"); +pub const world = @import("graphics/world.zig"); +pub const world_data = @import("graphics/world_data.zig"); pub const chart_mod = @import("graphics/chart.zig"); pub const plot_mod = @import("graphics/plot.zig"); @@ -35,6 +37,13 @@ pub const gauge = plot_mod.gauge; pub const histogram = plot_mod.histogram; pub const plot = plot_mod.plot; pub const Bounds = canvas_mod.Bounds; +pub const CountryOutline = world_data.CountryOutline; +pub const WORLD_COUNTRIES = world_data.WORLD_COUNTRIES; +pub const countryAt = world.countryAt; +pub const findCountry = world.findCountry; +pub const countryBounds = world.countryBounds; +pub const degreesAt = world.degreesAt; +pub const worldShapes = world.worldShapes; pub const CanvasOptions = canvas_mod.CanvasOptions; pub const Shape = canvas_mod.Shape; pub const ShapeKind = canvas_mod.ShapeKind; diff --git a/ports/zig/src/graphics/world.zig b/ports/zig/src/graphics/world.zig new file mode 100644 index 0000000..adc7a10 --- /dev/null +++ b/ports/zig/src/graphics/world.zig @@ -0,0 +1,202 @@ +//! The world, as shapes for the canvas, and the lookup that makes it clickable. +//! +//! The canvas already draws in the caller's own coordinates, and longitude and +//! latitude are just another pair of axes -- so a map is a list of polylines in +//! degrees, and nothing here needs a projection of its own beyond deciding +//! which window on the globe to show. +//! +//! The interesting half is the other direction. A click arrives as a terminal +//! cell, and a country is a polygon, so answering "what did they click" means +//! turning the cell back into degrees and testing it against the outlines. +//! Doing it that way rather than with bounding boxes is what makes the answer +//! right: Russia's bounding box covers most of the northern hemisphere, and +//! Chile's covers Argentina. + +const std = @import("std"); + +const braille_mod = @import("braille.zig"); +const canvas_mod = @import("canvas.zig"); +const color_mod = @import("../color.zig"); +const world_data = @import("world_data.zig"); + +const Bounds = canvas_mod.Bounds; +const Color = color_mod.Color; +const Point = braille_mod.Point; +const Shape = canvas_mod.Shape; + +pub const CountryOutline = world_data.CountryOutline; +pub const WORLD_COUNTRIES = world_data.WORLD_COUNTRIES; + +/// The whole globe, which is what a map shows unless told otherwise. +pub const WORLD_X = Bounds{ .min = -180, .max = 180 }; +pub const WORLD_Y = Bounds{ .min = -90, .max = 90 }; + +pub const WorldShapeOptions = struct { + /// Colour for countries with nothing special about them. + color: ?Color = null, + /// Countries to pick out, by name or ISO code. + highlight: []const []const u8 = &.{}, + highlight_color: ?Color = null, +}; + +/// Match on either the name or the ISO code, case-insensitively. +fn matches(country: CountryOutline, keys: []const []const u8) bool { + for (keys) |key| { + if (key.len == 0) continue; + if (std.ascii.eqlIgnoreCase(country.name, key)) return true; + if (country.iso.len > 0 and std.ascii.eqlIgnoreCase(country.iso, key)) return true; + } + return false; +} + +/// The world as canvas shapes, one polyline per landmass. +/// +/// Polylines rather than scattered points: the outlines are closed rings, so +/// joining them draws a coastline instead of a dotted suggestion of one, and it +/// reads at a fraction of the resolution dots would need. +/// +/// The caller owns the returned shapes and the points inside them, which is why +/// this takes an allocator: a ring becomes a list of pairs, and there is nowhere +/// else to put a couple of thousand of them. +pub fn worldShapes( + allocator: std.mem.Allocator, + options: WorldShapeOptions, +) ![]Shape { + var shapes = try allocator.alloc(Shape, countRings()); + var at: usize = 0; + errdefer freeShapes(allocator, shapes[0..at]); + + for (WORLD_COUNTRIES) |country| { + const picked = options.highlight.len > 0 and matches(country, options.highlight); + const color = if (picked) (options.highlight_color orelse options.color) else options.color; + for (country.rings) |ring| { + const count = ring.len / 2; + // Closed: the last point joins the first, or every country has a + // gap in its coastline where the ring started. + const points = try allocator.alloc(Point, if (count > 0) count + 1 else 0); + for (0..count) |i| { + points[i] = .{ .x = ring[i * 2], .y = ring[i * 2 + 1] }; + } + if (count > 0) points[count] = points[0]; + shapes[at] = .{ .kind = .polyline, .points = points, .color = color }; + at += 1; + } + } + return shapes; +} + +/// Release what `worldShapes` allocated. +pub fn freeShapes(allocator: std.mem.Allocator, shapes: []Shape) void { + for (shapes) |shape| allocator.free(shape.points); + allocator.free(shapes); +} + +fn countRings() usize { + var total: usize = 0; + for (WORLD_COUNTRIES) |country| total += country.rings.len; + return total; +} + +/// Whether a point is inside a ring, by ray casting. +/// +/// The ring is a flat list of interleaved coordinates, so this walks it two at +/// a time rather than allocating a pair per vertex -- it runs once per country +/// per click, and there are a couple of thousand vertices. +fn insideRing(ring: []const f64, lon: f64, lat: f64) bool { + var inside = false; + const n = ring.len / 2; + if (n == 0) return false; + var j = n - 1; + for (0..n) |i| { + const xi = ring[i * 2]; + const yi = ring[i * 2 + 1]; + const xj = ring[j * 2]; + const yj = ring[j * 2 + 1]; + if ((yi > lat) != (yj > lat) and lon < (xj - xi) * (lat - yi) / (yj - yi) + xi) { + inside = !inside; + } + j = i; + } + return inside; +} + +/// The country containing a point, or null for open water. +/// +/// Where outlines overlap -- and at this resolution simplified borders do +/// overlap -- the first match wins, which is stable because the data is sorted +/// by name. +pub fn countryAt(lon: f64, lat: f64) ?CountryOutline { + if (!std.math.isFinite(lon) or !std.math.isFinite(lat)) return null; + for (WORLD_COUNTRIES) |country| { + for (country.rings) |ring| { + if (insideRing(ring, lon, lat)) return country; + } + } + return null; +} + +/// Look a country up by name or ISO code. +pub fn findCountry(key: []const u8) ?CountryOutline { + const keys = [_][]const u8{key}; + for (WORLD_COUNTRIES) |country| { + if (matches(country, &keys)) return country; + } + return null; +} + +/// The window a country fills, with a little room around it. +/// +/// For zooming a map to a country: the bounding box alone puts the coastline +/// flat against the edge of the panel, which reads as though the country has +/// been cut off rather than framed. +pub fn countryBounds(country: CountryOutline, margin: f64) struct { x: Bounds, y: Bounds } { + var min_lon: f64 = std.math.inf(f64); + var max_lon: f64 = -std.math.inf(f64); + var min_lat: f64 = std.math.inf(f64); + var max_lat: f64 = -std.math.inf(f64); + for (country.rings) |ring| { + var i: usize = 0; + while (i + 1 < ring.len) : (i += 2) { + min_lon = @min(min_lon, ring[i]); + max_lon = @max(max_lon, ring[i]); + min_lat = @min(min_lat, ring[i + 1]); + max_lat = @max(max_lat, ring[i + 1]); + } + } + if (!std.math.isFinite(min_lon)) return .{ .x = WORLD_X, .y = WORLD_Y }; + // A single-point country would give a zero-width window, which cannot be + // mapped onto anything. + const pad_x = @max((max_lon - min_lon) * margin, 1.0); + const pad_y = @max((max_lat - min_lat) * margin, 1.0); + return .{ + .x = .{ .min = min_lon - pad_x, .max = max_lon + pad_x }, + .y = .{ .min = min_lat - pad_y, .max = max_lat + pad_y }, + }; +} + +/// The degrees under a terminal cell, given the window the map was drawn with. +/// +/// The inverse of what the canvas does on the way in, taken at the centre of +/// the cell: a click lands on a whole cell, and the centre is the only point in +/// it that is not arbitrarily nearer one neighbour than the other. +pub fn degreesAt( + column: usize, + row: usize, + width: usize, + height: usize, + x: Bounds, + y: Bounds, +) ?struct { lon: f64, lat: f64 } { + if (width == 0 or height == 0) return null; + // The canvas is 2x4 Braille pixels per cell, and it spans its bounds across + // `pixels - 1`, so the inverse has to use the same denominators or a click + // drifts from what was drawn. + const px: f64 = @floatFromInt(@max(1, width * 2 -| 1)); + const py: f64 = @floatFromInt(@max(1, height * 4 -| 1)); + const cx: f64 = @floatFromInt(column * 2 + 1); + const cy: f64 = @floatFromInt(row * 4 + 2); + return .{ + .lon = x.min + (cx / px) * (x.max - x.min), + .lat = y.min + (1 - cy / py) * (y.max - y.min), + }; +} diff --git a/ports/zig/src/graphics/world_data.zig b/ports/zig/src/graphics/world_data.zig new file mode 100644 index 0000000..2b3b984 --- /dev/null +++ b/ports/zig/src/graphics/world_data.zig @@ -0,0 +1,1306 @@ +//! Country outlines, flattened for a terminal. +//! +//! Generated by packages/hqtui/scripts/generate-world.ts from Natural Earth's +//! 1:110m Admin 0 countries, which is public domain. Do not edit by hand. +//! +//! Each ring is longitude and latitude interleaved -- lon, lat, lon, lat -- +//! rather than a list of pairs, because at 1982 points the nested form +//! costs a container per coordinate for no gain. A country has more than one +//! ring when it is more than one landmass. +//! +//! 171 countries, 1982 points, simplified at 1 degrees. + +pub const CountryOutline = struct { + name: []const u8, + /// ISO 3166-1 alpha-2, where Natural Earth has one. + iso: []const u8, + /// Longitude and latitude, interleaved. + rings: []const []const f64, +}; + +pub const WORLD_COUNTRIES = [_]CountryOutline{ + .{ + .name = "Afghanistan", + .iso = "AF", + .rings = &.{ + &.{66.5,37.4,70.8,38.5,71.8,36.7,75.2,37.1,71.3,36.1,69.3,31.9,66.3,29.9,60.9,29.8,61.2,35.7,66.5,37.4}, + }, + }, + .{ + .name = "Albania", + .iso = "AL", + .rings = &.{ + &.{21.0,40.8,19.4,40.3,19.7,42.7,21.0,40.8}, + }, + }, + .{ + .name = "Algeria", + .iso = "DZ", + .rings = &.{ + &.{-8.7,27.4,-8.7,28.8,-1.3,32.3,-1.2,35.7,8.4,36.9,7.5,34.1,9.8,29.4,9.3,26.1,12.0,23.5,3.2,19.1,-8.7,27.4}, + }, + }, + .{ + .name = "Angola", + .iso = "AO", + .rings = &.{ + &.{12.3,-6.1,16.3,-5.9,17.5,-8.1,21.7,-7.3,22.2,-11.1,24.0,-11.2,24.0,-12.9,21.9,-12.9,23.2,-17.5,11.7,-17.3,13.7,-11.3,12.3,-6.1}, + }, + }, + .{ + .name = "Antarctica", + .iso = "AQ", + .rings = &.{ + &.{-48.7,-78.0,-43.9,-78.5,-43.3,-80.0,-54.2,-80.6,-48.7,-78.0}, + &.{-66.3,-80.3,-59.6,-80.0,-66.3,-80.3}, + &.{-73.9,-71.3,-70.3,-68.9,-68.3,-71.4,-75.0,-72.1,-73.9,-71.3}, + &.{-102.3,-71.9,-96.2,-72.5,-102.3,-71.9}, + &.{-122.6,-73.7,-118.7,-73.5,-122.6,-73.7}, + &.{-127.3,-73.5,-124.0,-73.9,-127.3,-73.5}, + &.{-163.7,-78.6,-159.2,-79.5,-163.7,-78.6}, + &.{180.0,-84.7,180.0,-90.0,-180.0,-90.0,-179.1,-84.1,-143.1,-85.0,-153.6,-83.7,-152.9,-82.0,-156.8,-81.1,-146.4,-80.3,-155.3,-79.1,-158.4,-76.9,-151.3,-77.4,-146.1,-76.5,-146.2,-75.4,-135.2,-74.3,-100.1,-74.9,-103.7,-72.6,-74.9,-73.9,-67.4,-72.5,-67.7,-67.3,-57.8,-63.3,-65.7,-68.0,-61.8,-70.7,-60.8,-73.7,-70.6,-76.6,-77.2,-76.7,-73.7,-77.9,-78.0,-79.2,-58.2,-83.2,-28.5,-80.3,-35.6,-79.5,-35.8,-78.3,-17.5,-75.1,-15.4,-73.1,-6.9,-70.9,27.1,-70.5,33.9,-68.5,38.6,-69.8,54.5,-65.8,61.4,-68.0,68.9,-67.9,69.7,-69.2,67.9,-71.9,69.9,-72.3,73.9,-69.9,88.0,-66.2,95.8,-67.4,102.8,-65.6,106.2,-66.9,113.6,-65.9,119.8,-67.3,135.1,-65.3,137.5,-67.0,145.5,-66.9,171.2,-71.7,163.6,-76.2,167.0,-78.8,161.8,-79.2,159.8,-80.9,169.4,-83.8,180.0,-84.7}, + }, + }, + .{ + .name = "Argentina", + .iso = "AR", + .rings = &.{ + &.{-68.6,-52.6,-65.0,-54.7,-68.6,-54.9,-68.6,-52.6}, + &.{-57.6,-30.2,-58.5,-34.4,-56.8,-36.9,-62.3,-38.8,-62.7,-41.0,-65.1,-41.1,-63.5,-42.6,-67.3,-45.6,-65.6,-47.2,-69.1,-50.7,-68.1,-52.3,-71.9,-52.0,-73.4,-49.3,-71.2,-44.8,-72.1,-42.3,-68.4,-24.5,-66.3,-21.8,-62.8,-22.0,-57.8,-25.2,-58.6,-27.1,-55.7,-27.4,-54.1,-25.5,-53.6,-26.9,-57.6,-30.2}, + }, + }, + .{ + .name = "Armenia", + .iso = "AM", + .rings = &.{ + &.{46.5,38.8,43.6,41.1,45.6,40.8,46.5,38.8}, + }, + }, + .{ + .name = "Australia", + .iso = "AU", + .rings = &.{ + &.{147.7,-40.8,147.9,-43.2,146.0,-43.5,144.7,-40.7,147.7,-40.8}, + &.{126.1,-32.2,118.0,-35.1,115.0,-34.2,113.7,-22.5,120.9,-19.7,125.7,-14.2,129.6,-15.0,132.4,-11.1,136.5,-11.9,135.5,-15.0,140.2,-17.7,142.5,-10.7,146.4,-19.0,150.7,-22.4,153.6,-28.1,150.0,-37.4,146.3,-39.0,140.6,-38.0,138.2,-34.4,136.8,-35.3,137.8,-32.9,136.0,-34.9,131.3,-31.5,126.1,-32.2}, + }, + }, + .{ + .name = "Austria", + .iso = "AT", + .rings = &.{ + &.{17.0,48.1,14.6,46.4,9.5,47.1,12.9,47.5,13.6,48.9,17.0,48.1}, + }, + }, + .{ + .name = "Azerbaijan", + .iso = "AZ", + .rings = &.{ + &.{46.4,41.9,50.4,40.3,48.9,38.3,45.6,39.9,45.0,41.2,46.4,41.9}, + }, + }, + .{ + .name = "Bahamas", + .iso = "BS", + .rings = &.{ + &.{-78.2,25.2,-77.5,23.8,-78.2,25.2}, + }, + }, + .{ + .name = "Bangladesh", + .iso = "BD", + .rings = &.{ + &.{92.7,22.0,92.4,20.7,91.4,22.8,89.0,22.1,88.6,26.4,92.4,25.0,91.2,23.5,92.7,22.0}, + }, + }, + .{ + .name = "Belarus", + .iso = "BY", + .rings = &.{ + &.{28.2,56.2,30.9,55.6,32.7,53.4,31.8,52.1,23.5,51.6,23.5,53.9,28.2,56.2}, + }, + }, + .{ + .name = "Belgium", + .iso = "BE", + .rings = &.{ + &.{6.2,50.8,5.7,49.5,2.5,51.1,6.2,50.8}, + }, + }, + .{ + .name = "Belize", + .iso = "BZ", + .rings = &.{ + &.{-89.1,17.8,-88.1,18.3,-88.9,15.9,-89.1,17.8}, + }, + }, + .{ + .name = "Benin", + .iso = "BJ", + .rings = &.{ + &.{2.7,6.3,0.8,10.5,2.8,12.2,2.7,6.3}, + }, + }, + .{ + .name = "Bhutan", + .iso = "BT", + .rings = &.{ + &.{91.7,27.8,88.8,27.1,91.7,27.8}, + }, + }, + .{ + .name = "Bolivia", + .iso = "BO", + .rings = &.{ + &.{-69.5,-11.0,-65.3,-9.8,-65.4,-11.6,-60.5,-13.8,-60.2,-16.3,-58.2,-16.3,-57.9,-20.0,-61.8,-19.6,-62.7,-22.2,-67.8,-22.9,-69.5,-11.0}, + }, + }, + .{ + .name = "Bosnia and Herz.", + .iso = "BA", + .rings = &.{ + &.{18.6,42.7,16.0,45.2,19.4,44.9,18.6,42.7}, + }, + }, + .{ + .name = "Botswana", + .iso = "BW", + .rings = &.{ + &.{29.4,-22.1,25.7,-25.5,21.6,-26.7,19.9,-24.8,20.9,-18.3,25.3,-17.7,29.4,-22.1}, + }, + }, + .{ + .name = "Brazil", + .iso = "BR", + .rings = &.{ + &.{-53.4,-33.8,-53.8,-32.0,-57.6,-30.2,-53.6,-26.1,-55.8,-22.4,-57.9,-22.1,-58.2,-16.3,-60.2,-16.3,-60.5,-13.8,-65.4,-11.6,-65.3,-9.8,-70.5,-11.0,-70.5,-9.5,-72.2,-10.1,-74.0,-7.5,-72.9,-5.3,-69.9,-4.3,-69.8,1.7,-65.5,0.8,-63.4,2.2,-64.8,4.1,-60.7,5.2,-59.0,1.3,-52.9,2.1,-51.3,4.2,-50.4,-0.1,-44.6,-2.7,-40.0,-2.9,-35.6,-5.1,-34.7,-7.3,-38.7,-13.1,-40.9,-21.9,-47.6,-24.9,-48.9,-28.7,-53.4,-33.8}, + }, + }, + .{ + .name = "Bulgaria", + .iso = "BG", + .rings = &.{ + &.{22.7,44.2,28.6,43.7,28.0,42.0,23.0,41.3,22.7,44.2}, + }, + }, + .{ + .name = "Burkina Faso", + .iso = "BF", + .rings = &.{ + &.{-5.4,10.4,-4.3,13.2,-1.1,15.0,2.2,12.6,0.9,11.0,-2.9,11.0,-2.8,9.6,-5.4,10.4}, + }, + }, + .{ + .name = "Burundi", + .iso = "BI", + .rings = &.{ + &.{30.5,-2.4,29.3,-4.5,29.0,-2.8,30.5,-2.4}, + }, + }, + .{ + .name = "Cambodia", + .iso = "KH", + .rings = &.{ + &.{102.6,12.2,103.0,14.2,107.6,13.5,106.2,11.0,103.5,10.6,102.6,12.2}, + }, + }, + .{ + .name = "Cameroon", + .iso = "CM", + .rings = &.{ + &.{14.5,12.9,14.5,4.7,15.9,1.7,9.6,2.3,8.8,5.5,11.7,7.0,14.5,12.9}, + }, + }, + .{ + .name = "Canada", + .iso = "CA", + .rings = &.{ + &.{-122.8,49.0,-127.4,50.8,-130.5,54.3,-130.0,55.9,-135.5,59.8,-137.5,58.9,-141.0,60.3,-141.0,69.7,-136.5,68.9,-128.1,70.5,-113.5,67.7,-106.1,68.8,-101.5,67.6,-97.7,68.6,-96.1,67.3,-94.2,69.1,-96.5,70.1,-95.2,71.9,-87.4,67.2,-85.5,69.9,-82.6,69.7,-81.4,67.1,-85.8,66.6,-90.7,63.6,-94.7,58.9,-92.3,57.1,-82.3,55.1,-79.9,51.2,-78.6,52.6,-79.8,54.7,-76.5,56.5,-78.5,58.8,-77.3,59.9,-78.1,62.3,-73.8,62.4,-69.6,61.1,-67.6,58.2,-64.6,60.3,-61.8,56.3,-57.3,54.6,-55.7,52.1,-60.0,50.2,-66.4,50.2,-71.1,46.8,-65.1,49.2,-64.5,46.2,-60.5,47.0,-59.8,45.9,-65.4,43.5,-66.2,44.5,-64.4,45.3,-67.1,45.1,-69.2,47.4,-71.5,45.0,-82.4,41.7,-82.6,45.3,-88.4,48.3,-122.8,49.0}, + &.{-84.0,62.5,-81.9,62.9,-84.0,62.5}, + &.{-79.8,72.8,-80.8,73.7,-76.3,72.8,-79.8,72.8}, + &.{-93.6,75.0,-96.8,74.9,-93.6,75.0}, + &.{-93.8,77.5,-96.4,77.8,-93.8,77.5}, + &.{-96.8,78.8,-95.6,78.4,-98.6,78.9,-96.8,78.8}, + &.{-88.2,74.4,-97.1,76.8,-79.8,74.9,-88.2,74.4}, + &.{-111.3,78.2,-109.9,78.0,-113.5,77.7,-111.3,78.2}, + &.{-111.0,78.8,-109.7,78.6,-112.5,78.4,-111.0,78.8}, + &.{-55.6,51.3,-56.8,49.8,-53.5,49.2,-53.1,46.7,-59.3,47.6,-55.6,51.3}, + &.{-83.9,65.1,-80.1,63.7,-87.2,63.5,-85.9,65.7,-83.9,65.1}, + &.{-78.8,72.4,-68.8,70.5,-67.0,69.2,-68.8,68.7,-61.9,66.9,-63.9,65.0,-68.0,66.3,-64.7,63.4,-68.8,63.7,-66.2,61.9,-68.9,62.3,-78.6,64.6,-74.0,65.5,-73.3,68.1,-79.0,70.2,-88.7,70.4,-90.2,72.2,-85.8,73.8,-85.8,72.5,-82.3,73.8,-78.8,72.4}, + &.{-94.5,74.1,-90.5,73.9,-95.4,72.1,-96.0,73.4,-94.5,74.1}, + &.{-122.9,76.1,-116.2,77.6,-122.9,76.1}, + &.{-132.7,54.0,-131.2,52.2,-132.7,54.0}, + &.{-105.5,79.3,-99.7,77.9,-105.5,79.3}, + &.{-123.5,48.5,-128.4,50.8,-123.5,48.5}, + &.{-121.5,74.4,-115.5,73.5,-123.1,70.9,-125.9,71.9,-123.9,73.7,-124.9,74.3,-121.5,74.4}, + &.{-107.8,75.8,-105.7,75.5,-117.7,75.2,-115.4,76.5,-107.8,75.8}, + &.{-106.5,73.1,-101.1,69.6,-113.3,68.5,-117.3,70.0,-112.4,70.4,-119.4,71.6,-115.2,73.3,-108.2,71.7,-108.4,73.1,-106.5,73.1}, + &.{-100.4,72.7,-101.5,73.4,-97.4,73.8,-96.5,72.6,-98.4,71.3,-102.5,72.5,-100.4,72.7}, + &.{-106.6,73.6,-104.5,73.4,-106.6,73.6}, + &.{-98.5,76.7,-98.2,75.0,-102.5,75.6,-98.5,76.7}, + &.{-96.0,80.6,-92.4,81.3,-85.8,79.3,-92.9,78.3,-96.0,80.6}, + &.{-91.6,81.9,-61.8,82.6,-76.9,79.3,-75.4,78.5,-80.6,76.2,-89.5,76.5,-88.3,77.9,-85.0,77.5,-88.0,78.4,-85.1,79.3,-86.9,80.3,-81.8,80.5,-91.6,81.9}, + &.{-75.2,67.4,-77.2,67.6,-75.2,67.4}, + &.{-96.3,69.5,-99.8,69.4,-96.3,69.5}, + &.{-64.5,49.9,-61.8,49.1,-64.5,49.9}, + &.{-64.0,47.0,-62.0,46.4,-64.0,47.0}, + }, + }, + .{ + .name = "Central African Rep.", + .iso = "CF", + .rings = &.{ + &.{27.4,5.2,22.4,4.0,19.5,5.0,16.0,2.3,14.5,5.5,15.3,7.4,22.9,11.1,27.4,5.2}, + }, + }, + .{ + .name = "Chad", + .iso = "TD", + .rings = &.{ + &.{23.8,19.6,23.9,15.6,21.9,12.6,22.9,11.1,15.3,7.4,13.5,14.4,15.9,20.4,14.9,22.9,23.8,19.6}, + }, + }, + .{ + .name = "Chile", + .iso = "CL", + .rings = &.{ + &.{-68.6,-52.6,-68.6,-54.9,-67.0,-54.9,-68.1,-55.6,-74.7,-52.8,-71.1,-54.1,-68.6,-52.6}, + &.{-69.6,-17.6,-67.0,-23.0,-70.5,-31.4,-69.8,-34.2,-72.1,-42.3,-71.2,-44.8,-73.4,-49.3,-71.9,-52.0,-68.6,-52.3,-71.4,-53.9,-74.9,-52.3,-75.6,-48.7,-74.1,-46.9,-75.6,-46.6,-72.7,-42.4,-74.3,-43.2,-69.6,-17.6}, + }, + }, + .{ + .name = "China", + .iso = "CN", + .rings = &.{ + &.{109.5,18.2,108.6,19.4,110.8,20.1,109.5,18.2}, + &.{80.3,42.3,80.0,44.9,87.8,49.3,91.0,46.9,90.9,45.3,96.3,42.7,109.2,42.5,111.9,45.1,119.7,46.7,115.5,48.1,122.2,53.4,125.9,52.8,131.0,47.8,135.0,48.5,133.1,45.1,131.0,45.0,130.6,42.4,121.1,38.9,121.6,40.9,117.5,38.7,122.4,37.5,119.2,34.9,121.9,31.7,121.7,28.2,118.7,24.5,110.4,20.3,105.3,23.4,101.7,22.3,101.8,21.2,99.2,22.1,97.6,23.9,98.7,27.5,96.1,29.5,88.8,27.3,78.7,31.5,78.9,34.3,73.7,39.4,80.3,42.3}, + }, + }, + .{ + .name = "Colombia", + .iso = "CO", + .rings = &.{ + &.{-66.9,1.3,-69.8,1.7,-69.9,-4.3,-70.0,-2.7,-77.4,0.4,-79.0,1.7,-77.1,3.8,-77.5,8.5,-71.4,12.4,-73.3,9.2,-72.0,7.0,-67.3,6.1,-66.9,1.3}, + }, + }, + .{ + .name = "Congo", + .iso = "CG", + .rings = &.{ + &.{18.5,3.5,16.0,-3.5,11.9,-5.0,11.5,-2.8,14.4,-1.3,13.1,2.3,15.9,1.7,18.5,3.5}, + }, + }, + .{ + .name = "Costa Rica", + .iso = "CR", + .rings = &.{ + &.{-82.5,9.6,-83.0,8.2,-85.9,10.9,-82.5,9.6}, + }, + }, + .{ + .name = "Côte d'Ivoire", + .iso = "CI", + .rings = &.{ + &.{-8.0,10.2,-2.8,9.6,-2.9,5.0,-7.7,4.4,-8.0,10.2}, + }, + }, + .{ + .name = "Croatia", + .iso = "HR", + .rings = &.{ + &.{16.6,46.5,19.4,45.2,15.8,44.8,18.5,42.5,13.7,45.1,16.6,46.5}, + }, + }, + .{ + .name = "Cuba", + .iso = "CU", + .rings = &.{ + &.{-82.3,23.2,-74.2,20.3,-77.8,19.9,-81.8,22.6,-85.0,21.9,-82.3,23.2}, + }, + }, + .{ + .name = "Cyprus", + .iso = "CY", + .rings = &.{ + &.{32.7,35.1,34.0,35.0,32.7,35.1}, + }, + }, + .{ + .name = "Czechia", + .iso = "CZ", + .rings = &.{ + &.{15.0,51.1,18.9,49.5,12.5,49.5,15.0,51.1}, + }, + }, + .{ + .name = "Dem. Rep. Congo", + .iso = "CD", + .rings = &.{ + &.{29.3,-4.5,30.7,-8.3,28.7,-8.5,28.4,-11.8,29.7,-13.3,22.2,-11.1,21.7,-7.3,17.5,-8.1,16.3,-5.9,12.2,-5.8,16.0,-3.5,19.5,5.0,29.7,4.6,31.2,2.2,29.3,-4.5}, + }, + }, + .{ + .name = "Denmark", + .iso = "DK", + .rings = &.{ + &.{9.9,55.0,8.1,56.5,10.6,57.7,9.9,55.0}, + &.{12.4,56.1,12.1,54.8,11.0,55.4,12.4,56.1}, + }, + }, + .{ + .name = "Djibouti", + .iso = "DJ", + .rings = &.{ + &.{42.4,12.5,42.8,10.9,42.4,12.5}, + }, + }, + .{ + .name = "Dominican Rep.", + .iso = "DO", + .rings = &.{ + &.{-71.7,18.0,-71.6,19.9,-68.3,18.6,-71.7,18.0}, + }, + }, + .{ + .name = "Ecuador", + .iso = "EC", + .rings = &.{ + &.{-75.4,-0.2,-78.6,-4.5,-80.4,-4.4,-80.1,0.8,-75.4,-0.2}, + }, + }, + .{ + .name = "Egypt", + .iso = "EG", + .rings = &.{ + &.{36.9,22.0,25.0,22.0,25.2,31.6,34.3,31.2,34.2,27.8,32.3,29.8,36.9,22.0}, + }, + }, + .{ + .name = "El Salvador", + .iso = "SV", + .rings = &.{ + &.{-89.4,14.4,-87.9,13.1,-90.1,13.7,-89.4,14.4}, + }, + }, + .{ + .name = "Eq. Guinea", + .iso = "GQ", + .rings = &.{ + &.{9.6,2.3,11.3,1.1,9.5,1.0,9.6,2.3}, + }, + }, + .{ + .name = "Eritrea", + .iso = "ER", + .rings = &.{ + &.{36.4,14.4,38.4,18.0,43.1,12.7,36.4,14.4}, + }, + }, + .{ + .name = "Estonia", + .iso = "EE", + .rings = &.{ + &.{28.0,59.5,27.3,57.5,23.3,59.2,28.0,59.5}, + }, + }, + .{ + .name = "eSwatini", + .iso = "SZ", + .rings = &.{ + &.{32.1,-26.7,31.0,-25.7,32.1,-26.7}, + }, + }, + .{ + .name = "Ethiopia", + .iso = "ET", + .rings = &.{ + &.{47.8,8.0,45.0,5.0,39.6,3.4,36.2,4.4,33.0,7.8,37.9,15.0,41.6,13.5,43.7,9.2,47.8,8.0}, + }, + }, + .{ + .name = "Falkland Is.", + .iso = "FK", + .rings = &.{ + &.{-61.2,-51.8,-57.7,-51.5,-61.2,-51.8}, + }, + }, + .{ + .name = "Finland", + .iso = "FI", + .rings = &.{ + &.{28.6,69.1,31.1,62.4,28.1,60.5,21.3,60.7,21.5,63.2,25.4,65.1,20.6,69.1,24.7,68.6,27.7,70.2,28.6,69.1}, + }, + }, + .{ + .name = "Fr. S. Antarctic Lands", + .iso = "TF", + .rings = &.{ + &.{68.9,-48.6,70.6,-49.3,68.7,-49.8,68.9,-48.6}, + }, + }, + .{ + .name = "France", + .iso = "FR", + .rings = &.{ + &.{-51.7,4.2,-52.9,2.1,-54.5,2.3,-54.0,5.8,-51.7,4.2}, + &.{6.2,49.5,8.1,49.0,6.0,46.7,7.4,43.7,1.8,42.3,-1.9,43.4,-1.2,46.0,-4.6,48.7,-1.6,48.6,-1.9,49.8,2.5,51.1,6.2,49.5}, + &.{8.7,42.6,9.2,41.4,8.7,42.6}, + }, + }, + .{ + .name = "Gabon", + .iso = "GA", + .rings = &.{ + &.{11.3,2.3,14.3,1.2,14.4,-1.3,11.1,-4.0,8.8,-1.1,11.3,2.3}, + }, + }, + .{ + .name = "Gambia", + .iso = "GM", + .rings = &.{ + &.{-16.7,13.6,-13.8,13.5,-16.7,13.6}, + }, + }, + .{ + .name = "Georgia", + .iso = "GE", + .rings = &.{ + &.{40.0,43.4,46.6,41.2,41.6,41.5,40.0,43.4}, + }, + }, + .{ + .name = "Germany", + .iso = "DE", + .rings = &.{ + &.{14.1,53.8,15.0,51.1,12.2,50.3,12.9,47.5,7.5,47.6,8.1,49.0,6.0,50.1,7.1,53.7,9.9,55.0,14.1,53.8}, + }, + }, + .{ + .name = "Ghana", + .iso = "GH", + .rings = &.{ + &.{0.0,11.0,1.1,5.9,-2.9,5.0,-2.9,11.0,0.0,11.0}, + }, + }, + .{ + .name = "Greece", + .iso = "GR", + .rings = &.{ + &.{26.3,35.3,23.5,35.3,26.3,35.3}, + &.{23.0,41.3,26.6,41.6,22.6,40.3,24.0,37.7,22.5,36.4,20.2,39.6,23.0,41.3}, + }, + }, + .{ + .name = "Greenland", + .iso = "GL", + .rings = &.{ + &.{-46.8,82.6,-27.1,83.5,-20.8,82.7,-31.9,82.2,-12.2,81.3,-20.0,80.2,-17.7,80.1,-19.7,78.8,-18.5,77.0,-21.7,76.6,-19.4,74.3,-24.8,72.3,-21.8,70.7,-25.5,71.4,-26.4,70.2,-22.3,70.1,-39.8,65.5,-43.4,60.1,-48.3,60.9,-51.6,63.6,-54.0,67.2,-50.9,69.9,-54.7,69.6,-54.4,70.8,-51.4,70.6,-55.8,71.7,-54.7,72.6,-58.6,75.5,-68.5,76.1,-71.4,77.0,-66.8,77.4,-73.3,78.0,-65.7,79.4,-68.0,80.1,-62.7,81.8,-44.5,81.7,-46.8,82.6}, + }, + }, + .{ + .name = "Guatemala", + .iso = "GT", + .rings = &.{ + &.{-92.2,14.5,-90.5,16.1,-91.0,17.8,-89.1,17.8,-88.2,15.7,-89.4,14.4,-92.2,14.5}, + }, + }, + .{ + .name = "Guinea", + .iso = "GN", + .rings = &.{ + &.{-13.7,12.6,-9.1,12.3,-8.3,7.7,-11.1,10.0,-13.2,8.9,-15.1,11.0,-13.7,12.6}, + }, + }, + .{ + .name = "Guinea-Bissau", + .iso = "GW", + .rings = &.{ + &.{-16.7,12.4,-13.7,11.8,-15.1,11.0,-16.7,12.4}, + }, + }, + .{ + .name = "Guyana", + .iso = "GY", + .rings = &.{ + &.{-56.5,1.9,-59.6,1.8,-61.4,6.0,-59.8,8.4,-57.1,6.0,-58.0,4.1,-56.5,1.9}, + }, + }, + .{ + .name = "Haiti", + .iso = "HT", + .rings = &.{ + &.{-71.7,19.7,-71.7,18.0,-74.5,18.3,-71.7,19.7}, + }, + }, + .{ + .name = "Honduras", + .iso = "HN", + .rings = &.{ + &.{-83.1,15.0,-87.3,13.0,-89.4,14.4,-87.9,15.9,-83.1,15.0}, + }, + }, + .{ + .name = "Hungary", + .iso = "HU", + .rings = &.{ + &.{22.1,48.4,21.0,46.3,16.2,46.9,17.0,48.1,22.1,48.4}, + }, + }, + .{ + .name = "Iceland", + .iso = "IS", + .rings = &.{ + &.{-14.5,66.5,-13.6,65.1,-18.7,63.5,-22.8,64.0,-21.8,64.4,-24.0,64.9,-22.2,65.4,-24.3,65.6,-14.5,66.5}, + }, + }, + .{ + .name = "India", + .iso = "IN", + .rings = &.{ + &.{97.3,28.3,92.7,22.0,91.2,23.5,92.4,25.0,88.6,26.4,88.9,21.7,80.3,15.9,79.9,10.4,77.5,8.0,72.6,21.4,70.5,20.9,68.2,23.7,71.0,24.4,69.5,26.9,75.3,32.3,73.7,34.3,77.8,35.5,78.7,31.5,81.1,30.2,80.1,28.8,83.3,27.4,88.1,26.4,88.7,28.1,92.0,26.8,96.1,29.5,97.3,28.3}, + }, + }, + .{ + .name = "Indonesia", + .iso = "ID", + .rings = &.{ + &.{141.0,-2.6,141.0,-9.1,137.6,-8.4,137.9,-5.4,133.0,-4.1,132.0,-2.8,133.7,-2.2,130.5,-0.9,134.0,-0.8,135.5,-3.4,137.4,-1.7,141.0,-2.6}, + &.{125.0,-8.9,123.5,-10.2,125.0,-8.9}, + &.{117.9,4.1,119.0,0.9,116.1,-4.0,110.2,-2.9,109.7,2.0,110.5,0.8,113.8,1.2,115.9,4.3,117.9,4.1}, + &.{129.4,-2.8,130.8,-3.9,127.9,-3.4,129.4,-2.8}, + &.{127.9,2.2,128.1,-0.9,127.9,2.2}, + &.{122.9,0.9,125.2,1.4,120.0,-0.5,123.3,-0.6,121.5,-1.9,123.2,-5.3,121.0,-2.6,119.8,-5.7,118.8,-2.8,119.8,0.2,122.9,0.9}, + &.{120.3,-10.3,119.0,-9.6,120.3,-10.3}, + &.{121.3,-8.5,122.9,-8.1,119.9,-8.8,121.3,-8.5}, + &.{118.3,-8.4,116.7,-9.0,118.3,-8.4}, + &.{108.5,-6.4,115.7,-8.4,105.4,-6.9,108.5,-6.4}, + &.{104.4,-1.1,106.1,-3.1,105.8,-5.9,102.6,-4.2,95.3,5.5,97.5,5.2,104.4,-1.1}, + }, + }, + .{ + .name = "Iran", + .iso = "IR", + .rings = &.{ + &.{48.6,29.9,45.4,34.0,46.1,35.7,44.1,39.4,48.1,39.6,50.8,36.9,56.6,38.1,61.1,36.5,60.9,29.8,63.3,26.8,61.5,25.1,57.4,25.7,48.6,29.9}, + }, + }, + .{ + .name = "Iraq", + .iso = "IQ", + .rings = &.{ + &.{39.2,32.2,41.3,36.4,44.8,37.2,48.6,29.9,44.7,29.2,39.2,32.2}, + }, + }, + .{ + .name = "Ireland", + .iso = "IE", + .rings = &.{ + &.{-6.2,53.9,-6.8,52.3,-10.0,51.8,-7.6,55.1,-6.2,53.9}, + }, + }, + .{ + .name = "Israel", + .iso = "IL", + .rings = &.{ + &.{35.7,32.7,34.9,29.5,34.3,31.2,35.7,32.7}, + }, + }, + .{ + .name = "Italy", + .iso = "IT", + .rings = &.{ + &.{10.4,46.9,13.8,46.5,12.6,44.1,18.3,39.8,16.9,40.4,15.7,37.9,15.4,40.0,10.2,43.9,7.4,43.7,6.8,46.0,10.4,46.9}, + &.{14.8,38.1,15.1,36.6,12.4,37.6,14.8,38.1}, + &.{8.7,40.9,9.8,40.5,8.8,38.9,8.7,40.9}, + }, + }, + .{ + .name = "Jamaica", + .iso = "JM", + .rings = &.{ + &.{-77.6,18.5,-76.2,17.9,-77.6,18.5}, + }, + }, + .{ + .name = "Japan", + .iso = "JP", + .rings = &.{ + &.{141.9,39.2,140.3,35.1,135.8,33.5,135.1,34.6,131.0,33.9,132.0,33.1,130.2,31.4,129.4,33.3,139.4,38.2,140.3,41.2,141.9,39.2}, + &.{144.6,44.0,145.5,43.3,140.0,41.6,142.0,45.6,144.6,44.0}, + &.{132.4,33.5,134.8,33.8,132.4,33.5}, + }, + }, + .{ + .name = "Jordan", + .iso = "JO", + .rings = &.{ + &.{35.5,32.4,38.8,33.4,39.2,32.2,37.0,31.5,38.0,30.5,36.1,29.2,34.9,29.5,35.5,32.4}, + }, + }, + .{ + .name = "Kazakhstan", + .iso = "KZ", + .rings = &.{ + &.{87.4,49.2,80.0,44.9,80.3,42.3,74.2,43.3,68.6,40.7,64.9,43.7,62.0,43.5,58.5,45.6,55.9,45.0,56.0,41.3,52.5,41.8,50.3,44.6,53.0,45.3,53.0,46.9,49.1,46.4,46.5,48.4,50.8,51.7,61.3,50.8,60.0,52.0,61.4,54.0,69.1,55.4,73.4,53.5,76.9,54.5,80.0,50.9,87.4,49.2}, + }, + }, + .{ + .name = "Kenya", + .iso = "KE", + .rings = &.{ + &.{39.2,-4.7,33.9,-0.9,35.3,5.5,38.1,3.6,41.9,3.9,41.6,-1.7,39.2,-4.7}, + }, + }, + .{ + .name = "Kosovo", + .iso = "XK", + .rings = &.{ + &.{20.6,41.9,20.6,43.2,21.8,42.7,20.6,41.9}, + }, + }, + .{ + .name = "Kuwait", + .iso = "KW", + .rings = &.{ + &.{48.0,30.0,48.4,28.6,46.6,29.1,48.0,30.0}, + }, + }, + .{ + .name = "Kyrgyzstan", + .iso = "KG", + .rings = &.{ + &.{71.0,42.3,74.2,43.3,80.3,42.3,73.7,39.4,69.5,39.5,73.1,40.9,71.0,42.3}, + }, + }, + .{ + .name = "Laos", + .iso = "LA", + .rings = &.{ + &.{107.4,14.2,105.2,14.3,104.0,18.2,101.1,17.5,100.1,20.4,101.7,22.3,104.4,20.8,103.9,19.3,107.4,14.2}, + }, + }, + .{ + .name = "Latvia", + .iso = "LV", + .rings = &.{ + &.{27.3,57.5,28.2,56.2,26.5,55.6,21.1,56.0,22.5,57.8,27.3,57.5}, + }, + }, + .{ + .name = "Lebanon", + .iso = "LB", + .rings = &.{ + &.{35.8,33.3,36.4,34.6,35.8,33.3}, + }, + }, + .{ + .name = "Lesotho", + .iso = "LS", + .rings = &.{ + &.{29.0,-29.0,28.1,-30.5,27.0,-29.9,29.0,-29.0}, + }, + }, + .{ + .name = "Liberia", + .iso = "LR", + .rings = &.{ + &.{-8.4,7.7,-7.7,4.4,-11.4,6.8,-10.2,8.4,-8.4,7.7}, + }, + }, + .{ + .name = "Libya", + .iso = "LY", + .rings = &.{ + &.{25.0,22.0,23.8,19.6,10.3,24.4,10.0,31.4,11.5,33.1,19.1,30.3,20.9,32.7,24.9,31.9,25.0,22.0}, + }, + }, + .{ + .name = "Lithuania", + .iso = "LT", + .rings = &.{ + &.{26.5,55.6,23.5,53.9,21.1,56.0,26.5,55.6}, + }, + }, + .{ + .name = "Madagascar", + .iso = "MG", + .rings = &.{ + &.{49.5,-12.5,50.4,-15.7,47.1,-24.9,45.4,-25.6,43.3,-22.8,44.0,-17.4,49.5,-12.5}, + }, + }, + .{ + .name = "Malawi", + .iso = "MW", + .rings = &.{ + &.{32.8,-9.2,35.7,-14.6,35.0,-16.8,32.7,-13.7,32.8,-9.2}, + }, + }, + .{ + .name = "Malaysia", + .iso = "MY", + .rings = &.{ + &.{100.1,6.5,103.0,5.5,104.2,1.3,101.4,2.8,100.1,6.5}, + &.{117.9,4.1,115.9,4.3,114.6,1.4,109.8,1.3,115.3,4.3,116.7,6.9,119.2,5.4,117.9,4.1}, + }, + }, + .{ + .name = "Mali", + .iso = "ML", + .rings = &.{ + &.{-11.5,12.4,-11.7,15.4,-5.5,15.5,-6.5,25.0,4.3,19.2,3.6,15.6,-4.0,13.5,-5.4,10.4,-11.5,12.4}, + }, + }, + .{ + .name = "Mauritania", + .iso = "MR", + .rings = &.{ + &.{-17.1,21.0,-12.9,21.3,-12.0,25.9,-8.7,25.9,-8.7,27.4,-4.9,25.0,-6.5,25.0,-5.5,15.5,-12.2,14.6,-14.6,16.6,-16.5,16.1,-17.1,21.0}, + }, + }, + .{ + .name = "Mexico", + .iso = "MX", + .rings = &.{ + &.{-117.1,32.5,-106.5,31.8,-103.9,29.3,-101.7,29.8,-97.1,25.9,-97.9,22.4,-95.9,18.8,-91.4,18.9,-90.3,21.0,-87.1,21.5,-87.8,18.3,-91.0,17.8,-90.5,16.1,-92.2,14.5,-103.5,18.3,-113.1,31.2,-114.9,31.4,-109.9,22.8,-115.1,27.7,-114.2,28.6,-117.1,32.5}, + }, + }, + .{ + .name = "Moldova", + .iso = "MD", + .rings = &.{ + &.{26.6,48.2,30.0,46.4,28.2,45.5,26.6,48.2}, + }, + }, + .{ + .name = "Mongolia", + .iso = "MN", + .rings = &.{ + &.{87.8,49.3,92.2,50.8,97.3,49.7,98.9,52.0,108.5,49.3,116.7,49.9,115.7,47.7,119.8,47.0,105.0,41.6,96.3,42.7,90.9,45.3,91.0,46.9,87.8,49.3}, + }, + }, + .{ + .name = "Montenegro", + .iso = "ME", + .rings = &.{ + &.{20.1,42.6,18.5,42.5,20.1,42.6}, + }, + }, + .{ + .name = "Morocco", + .iso = "MA", + .rings = &.{ + &.{-2.2,35.2,-1.3,32.3,-8.7,28.8,-8.8,27.1,-11.4,26.9,-14.8,21.5,-17.0,21.4,-14.4,26.3,-9.6,29.9,-8.7,33.2,-5.9,35.8,-2.2,35.2}, + }, + }, + .{ + .name = "Mozambique", + .iso = "MZ", + .rings = &.{ + &.{34.6,-11.5,40.3,-10.3,40.8,-14.7,34.8,-19.8,35.5,-24.1,32.1,-26.7,31.2,-22.3,32.8,-16.7,30.2,-14.8,33.2,-14.0,35.0,-16.8,34.6,-11.5}, + }, + }, + .{ + .name = "Myanmar", + .iso = "MM", + .rings = &.{ + &.{100.1,20.4,97.4,18.4,99.6,11.9,98.6,9.9,97.2,16.9,94.2,16.0,92.3,21.5,97.3,28.3,98.7,27.5,97.6,23.9,101.2,21.8,100.1,20.4}, + }, + }, + .{ + .name = "N. Cyprus", + .iso = "-99", + .rings = &.{ + &.{32.7,35.1,34.6,35.7,32.7,35.1}, + }, + }, + .{ + .name = "Namibia", + .iso = "NA", + .rings = &.{ + &.{19.9,-24.8,19.9,-28.5,16.3,-28.6,11.7,-17.3,25.1,-17.6,20.9,-18.3,19.9,-24.8}, + }, + }, + .{ + .name = "Nepal", + .iso = "NP", + .rings = &.{ + &.{88.1,27.9,87.2,26.4,80.1,28.8,81.5,30.4,88.1,27.9}, + }, + }, + .{ + .name = "Netherlands", + .iso = "NL", + .rings = &.{ + &.{6.9,53.5,6.2,50.8,3.3,51.3,6.9,53.5}, + }, + }, + .{ + .name = "New Caledonia", + .iso = "NC", + .rings = &.{ + &.{165.8,-21.1,167.1,-22.2,164.0,-20.1,165.8,-21.1}, + }, + }, + .{ + .name = "New Zealand", + .iso = "NZ", + .rings = &.{ + &.{176.9,-40.1,174.7,-41.3,174.7,-37.4,172.6,-34.5,176.0,-37.6,178.5,-37.7,176.9,-40.1}, + &.{169.7,-43.6,172.8,-40.5,174.2,-41.3,170.6,-45.9,166.7,-46.2,169.7,-43.6}, + }, + }, + .{ + .name = "Nicaragua", + .iso = "NI", + .rings = &.{ + &.{-83.7,10.9,-87.7,12.9,-83.1,15.0,-83.7,10.9}, + }, + }, + .{ + .name = "Niger", + .iso = "NE", + .rings = &.{ + &.{14.9,22.9,15.9,20.4,13.5,14.4,14.2,12.5,5.4,13.9,3.6,11.7,1.0,12.9,0.4,14.9,3.6,15.6,4.3,19.2,12.0,23.5,14.9,22.9}, + }, + }, + .{ + .name = "Nigeria", + .iso = "NG", + .rings = &.{ + &.{2.7,6.3,4.4,13.7,13.1,13.6,14.6,12.1,11.7,7.0,8.5,4.8,5.9,4.3,2.7,6.3}, + }, + }, + .{ + .name = "North Korea", + .iso = "KP", + .rings = &.{ + &.{130.6,42.4,127.5,39.8,128.2,38.4,124.7,38.1,125.1,40.6,130.6,42.4}, + }, + }, + .{ + .name = "North Macedonia", + .iso = "MK", + .rings = &.{ + &.{22.4,42.3,23.0,41.3,20.6,41.1,22.4,42.3}, + }, + }, + .{ + .name = "Norway", + .iso = "NO", + .rings = &.{ + &.{15.1,79.7,21.5,79.0,15.9,76.8,10.4,79.7,15.1,79.7}, + &.{31.1,69.6,18.0,68.6,12.6,64.1,11.0,58.9,5.7,58.6,5.0,62.0,19.2,69.8,28.2,71.2,31.1,69.6}, + &.{27.4,80.1,17.4,80.3,27.4,80.1}, + &.{24.7,77.9,20.7,77.7,24.7,77.9}, + }, + }, + .{ + .name = "Oman", + .iso = "OM", + .rings = &.{ + &.{55.2,22.7,56.4,24.9,59.8,22.3,57.7,18.9,53.1,16.7,52.0,19.0,55.0,20.0,55.2,22.7}, + }, + }, + .{ + .name = "Pakistan", + .iso = "PK", + .rings = &.{ + &.{77.8,35.5,73.7,34.3,75.3,32.3,69.5,26.9,71.0,24.4,61.5,25.1,63.3,26.8,60.9,29.8,66.3,29.9,71.8,36.5,75.2,37.1,77.8,35.5}, + }, + }, + .{ + .name = "Panama", + .iso = "PA", + .rings = &.{ + &.{-77.4,8.7,-77.9,7.2,-79.1,9.0,-80.9,7.2,-82.9,9.5,-77.4,8.7}, + }, + }, + .{ + .name = "Papua New Guinea", + .iso = "PG", + .rings = &.{ + &.{141.0,-2.6,147.6,-6.1,147.2,-7.4,150.7,-10.6,144.7,-7.6,141.0,-9.1,141.0,-2.6}, + &.{152.6,-3.7,152.8,-4.8,150.7,-2.7,152.6,-3.7}, + &.{151.3,-5.8,148.3,-5.7,152.1,-4.1,151.3,-5.8}, + &.{154.8,-5.3,155.9,-6.8,154.8,-5.3}, + }, + }, + .{ + .name = "Paraguay", + .iso = "PY", + .rings = &.{ + &.{-58.2,-20.2,-57.9,-22.1,-54.3,-24.0,-55.7,-27.4,-58.6,-27.1,-57.8,-25.2,-62.7,-22.2,-61.8,-19.6,-58.2,-20.2}, + }, + }, + .{ + .name = "Peru", + .iso = "PE", + .rings = &.{ + &.{-69.9,-4.3,-72.9,-5.3,-74.0,-7.5,-68.7,-12.6,-70.4,-18.3,-76.0,-14.6,-81.4,-4.7,-80.3,-3.4,-78.6,-4.5,-75.1,-0.1,-73.1,-2.3,-70.0,-2.7,-69.9,-4.3}, + }, + }, + .{ + .name = "Philippines", + .iso = "PH", + .rings = &.{ + &.{122.6,10.0,124.1,11.2,123.0,9.0,122.6,10.0}, + &.{126.4,8.4,125.4,5.6,123.6,7.8,121.9,7.2,125.4,9.8,126.4,8.4}, + &.{118.5,9.3,117.2,8.4,119.5,11.4,118.5,9.3}, + &.{122.3,18.2,121.7,14.3,124.1,12.5,119.9,15.4,120.7,18.5,122.3,18.2}, + &.{125.5,12.2,124.8,10.1,124.3,12.6,125.5,12.2}, + }, + }, + .{ + .name = "Poland", + .iso = "PL", + .rings = &.{ + &.{23.5,53.9,24.0,50.7,22.8,49.0,16.2,50.4,14.1,53.0,17.6,54.9,23.5,53.9}, + }, + }, + .{ + .name = "Portugal", + .iso = "PT", + .rings = &.{ + &.{-9.0,41.9,-6.4,41.4,-7.9,36.8,-9.5,38.7,-9.0,41.9}, + }, + }, + .{ + .name = "Puerto Rico", + .iso = "PR", + .rings = &.{ + &.{-66.3,18.5,-67.2,17.9,-66.3,18.5}, + }, + }, + .{ + .name = "Qatar", + .iso = "QA", + .rings = &.{ + &.{50.8,24.8,51.3,26.1,50.8,24.8}, + }, + }, + .{ + .name = "Romania", + .iso = "RO", + .rings = &.{ + &.{28.2,45.5,29.6,45.3,28.6,43.7,22.9,43.8,20.2,46.1,26.6,48.2,28.2,45.5}, + }, + }, + .{ + .name = "Russia", + .iso = "RU", + .rings = &.{ + &.{49.1,46.4,46.7,44.6,47.8,41.2,36.7,45.2,40.1,49.6,31.8,52.1,32.7,53.4,30.9,55.6,27.3,57.5,29.1,60.0,28.1,60.5,31.5,62.9,30.0,63.6,28.6,69.1,32.1,69.9,41.1,67.5,38.4,66.0,33.2,66.6,37.0,63.8,37.2,65.1,43.9,66.1,43.5,68.6,46.3,68.3,46.3,66.7,53.7,68.9,59.9,68.3,60.6,69.9,68.5,68.1,66.7,71.0,69.9,73.0,72.8,72.2,71.8,71.4,73.7,68.4,71.3,66.3,72.4,66.2,75.1,67.8,73.1,71.4,74.7,72.8,76.4,71.2,81.5,71.8,80.5,73.6,104.4,77.7,114.1,75.8,109.4,74.2,127.0,73.6,131.3,70.8,139.9,71.5,139.1,72.4,140.5,72.8,159.0,70.9,160.9,69.4,180.0,69.0,180.0,65.0,177.4,64.6,179.2,62.3,170.3,59.9,163.5,59.9,162.0,58.2,163.2,57.6,162.1,54.9,156.8,51.0,155.9,56.8,164.5,62.6,160.1,60.5,156.7,61.4,154.2,59.8,155.0,59.1,142.2,59.0,135.1,54.7,141.3,53.1,140.1,48.4,134.9,43.4,130.8,42.2,131.0,45.0,133.1,45.1,135.0,48.5,131.0,47.8,123.6,53.5,120.2,52.8,117.9,49.5,108.5,49.3,98.9,52.0,97.3,49.7,92.2,50.8,87.4,49.2,80.0,50.9,76.9,54.5,73.4,53.5,69.1,55.4,61.4,54.0,60.0,52.0,61.3,50.8,50.8,51.7,47.5,50.5,46.5,48.4,49.1,46.4}, + &.{93.8,81.0,100.2,79.8,97.8,78.8,91.2,80.3,93.8,81.0}, + &.{102.8,79.3,105.4,78.7,99.4,77.9,102.8,79.3}, + &.{138.8,76.1,145.1,75.6,137.0,75.3,138.8,76.1}, + &.{148.2,75.3,150.7,75.1,146.1,75.2,148.2,75.3}, + &.{139.9,73.4,143.6,73.2,139.9,73.4}, + &.{44.8,80.6,51.5,80.7,44.8,80.6}, + &.{22.7,54.3,19.7,54.4,22.7,54.3}, + &.{53.5,73.7,61.2,76.3,68.9,76.5,58.5,74.3,55.4,72.4,57.5,70.7,51.6,71.5,53.5,73.7}, + &.{142.9,53.7,144.7,49.0,143.2,49.3,143.5,46.1,142.1,46.0,141.7,53.3,142.9,53.7}, + &.{-174.9,67.2,-169.9,66.0,-173.0,64.3,-178.7,66.1,-180.0,65.0,-180.0,69.0,-174.9,67.2}, + &.{-178.7,70.9,-180.0,71.5,-177.6,71.3,-178.7,70.9}, + &.{33.4,46.0,36.5,45.5,33.9,44.4,32.5,45.3,33.4,46.0}, + }, + }, + .{ + .name = "Rwanda", + .iso = "RW", + .rings = &.{ + &.{30.4,-1.1,29.0,-2.8,30.4,-1.1}, + }, + }, + .{ + .name = "S. Sudan", + .iso = "SS", + .rings = &.{ + &.{30.8,3.5,23.9,8.6,25.8,10.4,31.4,9.8,33.2,12.2,33.0,7.8,35.3,5.5,30.8,3.5}, + }, + }, + .{ + .name = "Saudi Arabia", + .iso = "SA", + .rings = &.{ + &.{35.0,29.4,39.2,32.2,47.5,29.0,52.0,23.0,55.2,22.7,55.0,20.0,47.0,16.9,43.4,17.6,42.8,16.3,35.0,29.4}, + }, + }, + .{ + .name = "Senegal", + .iso = "SN", + .rings = &.{ + &.{-16.7,13.6,-17.6,14.7,-14.6,16.6,-11.5,12.4,-16.7,12.4,-13.8,13.5,-16.7,13.6}, + }, + }, + .{ + .name = "Serbia", + .iso = "RS", + .rings = &.{ + &.{18.8,45.9,22.7,44.6,22.5,42.5,19.2,43.5,18.8,45.9}, + }, + }, + .{ + .name = "Sierra Leone", + .iso = "SL", + .rings = &.{ + &.{-13.2,8.9,-11.1,10.0,-10.2,8.4,-11.4,6.8,-13.2,8.9}, + }, + }, + .{ + .name = "Slovakia", + .iso = "SK", + .rings = &.{ + &.{22.6,49.1,16.9,48.5,22.6,49.1}, + }, + }, + .{ + .name = "Slovenia", + .iso = "SI", + .rings = &.{ + &.{13.8,46.5,16.6,46.5,15.3,45.5,13.8,46.5}, + }, + }, + .{ + .name = "Solomon Is.", + .iso = "SB", + .rings = &.{ + &.{159.6,-8.0,158.2,-7.4,159.6,-8.0}, + }, + }, + .{ + .name = "Somalia", + .iso = "SO", + .rings = &.{ + &.{41.6,-1.7,41.0,2.8,45.0,5.0,48.9,9.5,48.9,11.4,51.1,12.0,48.6,5.3,41.6,-1.7}, + }, + }, + .{ + .name = "Somaliland", + .iso = "-99", + .rings = &.{ + &.{48.9,11.4,47.8,8.0,42.6,10.6,48.9,11.4}, + }, + }, + .{ + .name = "South Africa", + .iso = "ZA", + .rings = &.{ + &.{16.3,-28.6,19.9,-28.5,19.9,-24.8,21.6,-26.7,25.7,-25.5,29.4,-22.1,31.2,-22.3,31.9,-24.4,30.7,-26.7,32.8,-26.7,28.2,-32.8,20.1,-34.8,18.4,-34.1,16.3,-28.6}, + }, + }, + .{ + .name = "South Korea", + .iso = "KR", + .rings = &.{ + &.{126.2,37.7,128.3,38.6,129.1,35.1,126.5,34.4,126.2,37.7}, + }, + }, + .{ + .name = "Spain", + .iso = "ES", + .rings = &.{ + &.{-7.5,37.1,-6.4,41.4,-9.4,43.0,3.0,42.5,-2.1,36.7,-7.5,37.1}, + }, + }, + .{ + .name = "Sri Lanka", + .iso = "LK", + .rings = &.{ + &.{81.8,7.5,80.3,6.0,80.1,9.8,81.8,7.5}, + }, + }, + .{ + .name = "Sudan", + .iso = "SD", + .rings = &.{ + &.{24.6,8.2,21.9,12.6,25.0,22.0,36.9,22.0,38.4,18.0,34.0,8.7,32.7,12.2,31.4,9.8,25.1,10.3,24.6,8.2}, + }, + }, + .{ + .name = "Suriname", + .iso = "SR", + .rings = &.{ + &.{-54.5,2.3,-56.5,1.9,-57.6,3.3,-57.1,6.0,-54.0,5.8,-54.5,2.3}, + }, + }, + .{ + .name = "Sweden", + .iso = "SE", + .rings = &.{ + &.{11.0,58.9,12.6,61.3,11.9,63.1,16.8,68.0,20.6,69.1,23.5,67.9,23.9,66.0,17.8,62.7,17.1,61.3,18.8,60.1,15.9,56.1,12.9,55.4,11.0,58.9}, + }, + }, + .{ + .name = "Switzerland", + .iso = "CH", + .rings = &.{ + &.{9.6,47.5,10.4,46.5,6.0,46.3,9.6,47.5}, + }, + }, + .{ + .name = "Syria", + .iso = "SY", + .rings = &.{ + &.{35.7,32.7,36.7,36.8,42.3,37.2,41.0,34.4,35.7,32.7}, + }, + }, + .{ + .name = "Taiwan", + .iso = "TW", + .rings = &.{ + &.{121.8,24.4,120.7,22.0,120.1,23.6,121.8,24.4}, + }, + }, + .{ + .name = "Tajikistan", + .iso = "TJ", + .rings = &.{ + &.{67.8,37.1,67.7,39.6,70.7,41.0,69.5,39.5,73.7,39.4,75.0,37.4,71.8,36.7,70.8,38.5,67.8,37.1}, + }, + }, + .{ + .name = "Tanzania", + .iso = "TZ", + .rings = &.{ + &.{33.9,-0.9,39.2,-4.7,39.5,-10.9,34.6,-11.5,29.6,-6.5,30.4,-1.1,33.9,-0.9}, + }, + }, + .{ + .name = "Thailand", + .iso = "TH", + .rings = &.{ + &.{105.2,14.3,103.0,14.2,102.6,12.2,100.1,13.4,99.2,9.2,102.1,6.2,101.2,5.7,98.2,8.4,99.6,11.9,97.4,18.4,100.1,20.4,101.1,17.5,104.7,17.4,105.2,14.3}, + }, + }, + .{ + .name = "Timor-Leste", + .iso = "TL", + .rings = &.{ + &.{125.0,-8.9,127.3,-8.4,125.0,-8.9}, + }, + }, + .{ + .name = "Togo", + .iso = "TG", + .rings = &.{ + &.{0.9,11.0,1.1,5.9,0.9,11.0}, + }, + }, + .{ + .name = "Tunisia", + .iso = "TN", + .rings = &.{ + &.{9.5,30.3,7.5,34.1,9.5,37.3,11.0,37.1,10.1,34.3,11.5,33.1,9.5,30.3}, + }, + }, + .{ + .name = "Turkey", + .iso = "TR", + .rings = &.{ + &.{44.8,37.2,29.7,36.1,27.6,36.7,26.2,39.5,33.5,42.0,42.6,41.6,44.8,39.7,44.8,37.2}, + &.{26.1,41.8,29.0,41.3,26.4,40.2,26.1,41.8}, + }, + }, + .{ + .name = "Turkmenistan", + .iso = "TM", + .rings = &.{ + &.{52.5,41.8,57.1,41.3,58.6,42.8,66.5,37.4,62.2,35.3,57.3,38.0,53.9,37.2,52.7,40.0,54.7,41.0,52.5,41.8}, + }, + }, + .{ + .name = "Uganda", + .iso = "UG", + .rings = &.{ + &.{33.9,-0.9,29.6,-1.3,31.2,3.8,34.5,3.6,33.9,-0.9}, + }, + }, + .{ + .name = "Ukraine", + .iso = "UA", + .rings = &.{ + &.{31.8,52.1,40.1,49.6,39.7,47.9,35.0,45.7,31.7,46.7,28.7,45.3,30.0,46.4,28.7,48.1,22.1,48.4,23.5,51.6,31.8,52.1}, + }, + }, + .{ + .name = "United Arab Emirates", + .iso = "AE", + .rings = &.{ + &.{51.6,24.2,56.3,25.7,55.0,22.5,51.6,24.2}, + }, + }, + .{ + .name = "United Kingdom", + .iso = "GB", + .rings = &.{ + &.{-6.2,53.9,-7.6,55.1,-6.2,53.9}, + &.{-3.1,53.4,-6.1,56.8,-5.0,58.6,-2.0,57.7,-3.1,56.0,1.7,52.7,1.4,51.3,-5.8,50.2,-3.4,51.4,-5.3,52.0,-4.6,53.5,-3.1,53.4}, + }, + }, + .{ + .name = "United States of America", + .iso = "US", + .rings = &.{ + &.{-122.8,49.0,-88.4,48.3,-82.6,45.3,-82.7,41.7,-71.5,45.0,-69.2,47.4,-67.0,44.8,-70.1,43.7,-70.0,41.6,-75.5,39.5,-75.9,37.2,-76.3,39.2,-77.0,38.2,-75.7,35.6,-81.3,31.4,-80.4,25.2,-83.7,29.9,-86.4,30.4,-94.7,29.5,-97.5,25.8,-101.0,29.4,-103.9,29.3,-106.5,31.8,-117.1,32.5,-120.6,34.6,-124.4,40.3,-124.7,48.2,-122.6,47.1,-122.8,49.0}, + &.{-166.5,60.4,-165.6,59.9,-167.5,60.2,-166.5,60.4}, + &.{-153.2,58.0,-152.1,57.6,-154.5,57.0,-153.2,58.0}, + &.{-141.0,69.7,-141.0,60.3,-137.5,58.9,-135.5,59.8,-130.0,55.9,-130.5,54.8,-134.1,58.1,-139.9,59.5,-147.1,60.9,-151.7,59.2,-150.6,61.3,-158.4,56.0,-164.9,54.6,-157.0,58.9,-162.0,58.7,-165.3,60.5,-165.7,62.1,-160.8,64.8,-168.1,65.7,-161.7,66.1,-166.2,68.9,-156.6,71.4,-141.0,69.7}, + &.{-171.7,63.8,-168.7,63.3,-171.7,63.8}, + }, + }, + .{ + .name = "Uruguay", + .iso = "UY", + .rings = &.{ + &.{-57.6,-30.2,-53.8,-32.0,-53.8,-34.4,-58.4,-33.9,-57.6,-30.2}, + }, + }, + .{ + .name = "Uzbekistan", + .iso = "UZ", + .rings = &.{ + &.{56.0,41.3,55.9,45.0,58.5,45.6,62.0,43.5,64.9,43.7,68.3,40.7,71.0,42.3,73.1,40.9,67.7,39.6,67.8,37.1,58.6,42.8,56.0,41.3}, + }, + }, + .{ + .name = "Venezuela", + .iso = "VE", + .rings = &.{ + &.{-60.7,5.2,-64.8,4.1,-63.4,2.2,-66.3,0.7,-67.8,2.8,-67.3,6.1,-72.0,7.0,-72.9,10.5,-71.3,11.8,-71.3,9.1,-69.9,12.2,-68.2,10.6,-61.9,10.7,-59.8,8.4,-60.7,5.2}, + }, + }, + .{ + .name = "Vietnam", + .iso = "VN", + .rings = &.{ + &.{104.3,10.5,107.5,12.3,107.6,15.2,102.2,22.5,105.3,23.4,108.1,21.6,105.7,19.1,108.9,15.3,109.2,11.7,105.2,8.6,104.3,10.5}, + }, + }, + .{ + .name = "W. Sahara", + .iso = "EH", + .rings = &.{ + &.{-8.7,27.7,-8.7,25.9,-12.0,25.9,-12.9,21.3,-17.1,21.0,-14.8,21.5,-11.4,26.9,-8.7,27.7}, + }, + }, + .{ + .name = "Yemen", + .iso = "YE", + .rings = &.{ + &.{52.0,19.0,52.2,15.6,43.5,12.6,43.4,17.6,47.0,16.9,52.0,19.0}, + }, + }, + .{ + .name = "Zambia", + .iso = "ZM", + .rings = &.{ + &.{30.7,-8.3,33.2,-9.7,33.2,-14.0,27.0,-17.9,23.2,-17.5,21.9,-12.9,24.0,-12.9,23.9,-10.9,29.7,-13.3,28.4,-9.2,30.7,-8.3}, + }, + }, + .{ + .name = "Zimbabwe", + .iso = "ZW", + .rings = &.{ + &.{31.2,-22.3,28.0,-21.5,25.3,-17.7,30.3,-15.5,32.8,-16.7,31.2,-22.3}, + }, + }, +}; diff --git a/ports/zig/src/ui.zig b/ports/zig/src/ui.zig index 72f2837..6d78c07 100644 --- a/ports/zig/src/ui.zig +++ b/ports/zig/src/ui.zig @@ -375,6 +375,7 @@ const Node = union(enum) { calendar: w.CalendarOptions, chart: w.ChartOptions, shapes: graphics.CanvasOptions, + world_map: w.WorldMapOptions, clear: w.ClearOptions, fill: w.FillOptions, sparkline: w.SparklineWidgetOptions, @@ -467,6 +468,7 @@ fn drawNode(ctx: *Ctx, s: Surface, node: Node) anyerror!void { .calendar => |o| w.drawCalendar(s, o), .chart => |o| try w.drawChart(allocator, s, o), .shapes => |o| try graphics.drawCanvas(allocator, s, o), + .world_map => |o| try w.drawWorldMap(allocator, s, o), .clear => |o| w.drawClear(s, o), .fill => |o| w.drawFill(s, o), .sparkline => |o| w.drawSparkline(s, o), @@ -888,6 +890,16 @@ pub const Container = struct { try self.add(self.filling(), .{ .shapes = options }); } + /// A world map, and the country under whatever gets clicked. + /// + /// The click is answered by turning the cell back into degrees and testing + /// it against the outlines, so the answer is the country actually under the + /// cursor. Bounding boxes would be cheaper and wrong: Russia's covers most + /// of the northern hemisphere and Chile's covers Argentina. + pub fn worldMap(self: *Container, options: w.WorldMapOptions) !void { + try self.add(self.filling(), .{ .world_map = options }); + } + pub fn sparkline(self: *Container, options: w.SparklineWidgetOptions) !void { try self.add(self.leaf(1), .{ .sparkline = options }); } diff --git a/ports/zig/src/widgets.zig b/ports/zig/src/widgets.zig index ad4a2e4..acac111 100644 --- a/ports/zig/src/widgets.zig +++ b/ports/zig/src/widgets.zig @@ -7,6 +7,7 @@ pub const meters = @import("widgets/meters.zig"); pub const calendar = @import("widgets/calendar.zig"); pub const chart = @import("widgets/chart.zig"); pub const shadow = @import("widgets/shadow.zig"); +pub const world_map = @import("widgets/world.zig"); pub const surface_widgets = @import("widgets/surface.zig"); pub const scrollbar = @import("widgets/scrollbar.zig"); pub const table = @import("widgets/table.zig"); @@ -68,6 +69,9 @@ pub const CalendarOptions = calendar.CalendarOptions; pub const calendarHeight = calendar.calendarHeight; pub const drawCalendar = calendar.drawCalendar; pub const ChartOptions = chart.ChartOptions; +pub const WorldMapOptions = world_map.WorldMapOptions; +pub const drawWorldMap = world_map.drawWorldMap; +pub const countryAtCell = world_map.countryAtCell; pub const ShadowOptions = shadow.ShadowOptions; pub const dimRect = shadow.dimRect; pub const drawShadow = shadow.drawShadow; diff --git a/ports/zig/src/widgets/world.zig b/ports/zig/src/widgets/world.zig new file mode 100644 index 0000000..30e0665 --- /dev/null +++ b/ports/zig/src/widgets/world.zig @@ -0,0 +1,74 @@ +//! A world map you can click. +//! +//! The drawing is the canvas doing what it already does -- polylines in the +//! caller's own coordinates, which for a map are degrees. What this adds is the +//! other direction: turning a click back into a country. + +const std = @import("std"); + +const canvas_mod = @import("../graphics/canvas.zig"); +const color_mod = @import("../color.zig"); +const surface_mod = @import("../surface.zig"); +const world = @import("../graphics/world.zig"); + +const Bounds = canvas_mod.Bounds; +const Color = color_mod.Color; +const CountryOutline = world.CountryOutline; +const Surface = surface_mod.Surface; + +pub const WorldMapOptions = struct { + /// The window on the globe. Null means all of it. + x: ?Bounds = null, + y: ?Bounds = null, + /// Coastline colour. + color: ?Color = null, + /// Countries to pick out, by name or ISO code. + highlight: []const []const u8 = &.{}, + highlight_color: ?Color = null, + background: ?Color = null, + grid: bool = false, + + fn window(self: WorldMapOptions) struct { x: Bounds, y: Bounds } { + return .{ .x = self.x orelse world.WORLD_X, .y = self.y orelse world.WORLD_Y }; + } +}; + +pub fn drawWorldMap( + allocator: std.mem.Allocator, + s: Surface, + options: WorldMapOptions, +) !void { + if (s.isEmpty()) return; + const theme = s.theme; + const shapes = try world.worldShapes(allocator, .{ + .color = options.color orelse theme.border, + .highlight = options.highlight, + .highlight_color = options.highlight_color orelse theme.accent, + }); + defer world.freeShapes(allocator, shapes); + + const win = options.window(); + try canvas_mod.drawCanvas(allocator, s, .{ + .shapes = shapes, + .x = win.x, + .y = win.y, + .background = options.background, + .grid = options.grid, + }); +} + +/// The country under a cell of a map drawn with these bounds. +/// +/// Exposed so a caller can answer a hover as well as a click, and so the +/// arithmetic that has to agree with the drawing lives in one place. +pub fn countryAtCell( + column: usize, + row: usize, + width: usize, + height: usize, + options: WorldMapOptions, +) ?CountryOutline { + const win = options.window(); + const at = world.degreesAt(column, row, width, height, win.x, win.y) orelse return null; + return world.countryAt(at.lon, at.lat); +}