From 2dd96e1f25f37a1464cf84ddbd162c677e546f87 Mon Sep 17 00:00:00 2001 From: MiguelGFerreira Date: Sat, 12 Sep 2026 14:24:33 -0300 Subject: [PATCH 1/5] =?UTF-8?q?feat(data):=20add=20seven=20Brazilian=20Bik?= =?UTF-8?q?e=20Ita=C3=BA=20cities=20to=20the=20GBFS=20registry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit São Paulo, Rio de Janeiro, Salvador, Recife, Porto Alegre, Brasília and Curitiba run on the same PBSC platform as the existing Austin, Honolulu and Chattanooga entries, so they need no new feed handling and no proxy allowlist change — isAllowedGbfsHost already accepts *.publicbikesystem.net. These are the layer's first stations outside the United States: 1,017 stations across the seven systems. --- CHANGELOG.md | 4 ++ DATA_SOURCES.md | 2 +- src/data/dataCredits.js | 2 +- src/layers/bikeshare/registry.js | 84 ++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f8245b4..3c451b4f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,10 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md ## [Unreleased] +- Add seven Brazilian Bike Itaú (Tembici) cities to the GBFS bikeshare registry: + São Paulo, Rio de Janeiro, Salvador, Recife, Porto Alegre, Brasília and + Curitiba. These are the layer's first stations outside the United States. + - Separate radio directory loading, station selection, globe presentation and playback into composed components with an explicit metadata source. - Separate submarine cable sources and rendering components, and export bundled geography lookup modules. diff --git a/DATA_SOURCES.md b/DATA_SOURCES.md index 650d3fe6b..2dc8ba0d7 100644 --- a/DATA_SOURCES.md +++ b/DATA_SOURCES.md @@ -35,7 +35,7 @@ How to read this: | **City of Austin Open Data** | CCTV camera catalog + frames | City of Austin Open Data Terms of Use | "City of Austin, TX — data.austintexas.gov" | | **Caltrans (cwwp2.dot.ca.gov)** | CCTV camera catalogs + frames, California districts | Public Caltrans traffic camera data | "Caltrans — cwwp2.dot.ca.gov" (courtesy) | | **TfL Open Data (JamCams)** | CCTV camera catalog + frames, London | [TfL Open Data terms](https://tfl.gov.uk/info-for/open-data-users/) — attribution REQUIRED | "Powered by TfL Open Data. Contains OS data © Crown copyright and database rights" | -| **GBFS (Lyft / BCycle)** | Bikeshare availability | Per-feed (attribution-only) | Credit the operator (e.g. Austin BCycle) + its `license_url` | +| **GBFS (Lyft / BCycle / PBSC)** | Bikeshare availability | Per-feed (attribution-only) | Credit the operator (e.g. Austin BCycle, Bike Itaú/Tembici) + its `license_url` | | **Radio Browser** | Geolocated internet-radio station directory and station-level tags | Public-domain directory data under PDDL 1.0; individual broadcaster stream terms apply | "Radio Browser" plus a link to the selected broadcaster | | **Re:Earth Terrain** (Mapterhorn) | Terrain (keyless globe stacks — OSM etc. — + `/api/terrain/heights` ellipsoidal-height lookups) | Terrain mesh: CC BY 4.0; geoid: EGM2008 (NGA, public domain) | "Terrain (keyless globe stacks): Re:Earth Terrain / Mapterhorn (CC BY 4.0) / EGM2008 (NGA)" | diff --git a/src/data/dataCredits.js b/src/data/dataCredits.js index 009ff6d01..817261608 100644 --- a/src/data/dataCredits.js +++ b/src/data/dataCredits.js @@ -137,7 +137,7 @@ export const DATA_CREDITS = [ }, { key: 'gbfs', - html: 'Bikeshare availability: GBFS operator feeds (e.g. Austin BCycle)', + html: 'Bikeshare availability: GBFS operator feeds (e.g. Austin BCycle, Bike Itaú/Tembici)', }, { key: 'radio-browser', diff --git a/src/layers/bikeshare/registry.js b/src/layers/bikeshare/registry.js index ff752f664..a5a1ee32c 100644 --- a/src/layers/bikeshare/registry.js +++ b/src/layers/bikeshare/registry.js @@ -167,6 +167,90 @@ const RAW_GBFS_CITY_REGISTRY = [ 'https://chat.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', provider: 'Bike Chattanooga', }, + { + id: 'sao-paulo-bike-itau', + city: 'São Paulo, SP', + centerLat: -23.5505, + centerLon: -46.6333, + loadRadiusKm: 100, + stationInformationUrl: + 'https://saopaulo.publicbikesystem.net/customer/gbfs/v2/en/station_information.json', + stationStatusUrl: + 'https://saopaulo.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', + provider: 'Bike Itaú', + }, + { + id: 'rio-de-janeiro-bike-itau', + city: 'Rio de Janeiro, RJ', + centerLat: -22.9068, + centerLon: -43.1729, + loadRadiusKm: 100, + stationInformationUrl: + 'https://riodejaneiro.publicbikesystem.net/customer/gbfs/v2/en/station_information.json', + stationStatusUrl: + 'https://riodejaneiro.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', + provider: 'Bike Itaú', + }, + { + id: 'salvador-bike-itau', + city: 'Salvador, BA', + centerLat: -12.9777, + centerLon: -38.5016, + loadRadiusKm: 90, + stationInformationUrl: + 'https://salvador.publicbikesystem.net/customer/gbfs/v2/en/station_information.json', + stationStatusUrl: + 'https://salvador.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', + provider: 'Bike Itaú', + }, + { + id: 'recife-bike-itau', + city: 'Recife, PE', + centerLat: -8.0476, + centerLon: -34.877, + loadRadiusKm: 90, + stationInformationUrl: + 'https://recife.publicbikesystem.net/customer/gbfs/v2/en/station_information.json', + stationStatusUrl: + 'https://recife.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', + provider: 'Bike Itaú', + }, + { + id: 'porto-alegre-bike-itau', + city: 'Porto Alegre, RS', + centerLat: -30.0346, + centerLon: -51.2177, + loadRadiusKm: 90, + stationInformationUrl: + 'https://portoalegre.publicbikesystem.net/customer/gbfs/v2/en/station_information.json', + stationStatusUrl: + 'https://portoalegre.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', + provider: 'Bike Itaú', + }, + { + id: 'brasilia-bike-itau', + city: 'Brasília, DF', + centerLat: -15.7939, + centerLon: -47.8828, + loadRadiusKm: 90, + stationInformationUrl: + 'https://brasilia.publicbikesystem.net/customer/gbfs/v2/en/station_information.json', + stationStatusUrl: + 'https://brasilia.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', + provider: 'Bike Itaú', + }, + { + id: 'curitiba-bike-itau', + city: 'Curitiba, PR', + centerLat: -25.4284, + centerLon: -49.2733, + loadRadiusKm: 90, + stationInformationUrl: + 'https://curitiba.publicbikesystem.net/customer/gbfs/v2/en/station_information.json', + stationStatusUrl: + 'https://curitiba.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', + provider: 'Bike Itaú', + }, bcycleEntry({ id: 'boulder-bcycle', city: 'Boulder, CO', From bdd64660e7d9d0de31ce58dc8450a09e7a632214 Mon Sep 17 00:00:00 2001 From: Cliff Flood Date: Wed, 16 Sep 2026 18:17:09 -0400 Subject: [PATCH 2/5] feat: ship a web app manifest and home-screen icons Adding the app to a phone home screen produced a blank tile: index.html linked only an SVG favicon, and Safari ignores SVG for apple-touch-icon, substituting a screenshot of the page. There was no manifest at all, so Android had nothing to install against either. Generate the rasters from public/logo.svg with sharp, which the repo already carries. The output is committed so a plain `vite build` needs no image toolchain; re-run scripts/generate-icons.mjs after changing the logo. The logo's viewBox is wider than it is tall, so each icon centres the mark on a square #0a0a0f canvas rather than stretching it, inset to 78% normally and 54% for the maskable variant to survive Android's circular crop. The SVG favicon stays as the browser tab icon. --- CHANGELOG.md | 6 +++ docs/CURRENT-STATE.md | 16 +++++++ index.html | 10 +++++ public/icons/icon-180.png | Bin 0 -> 12055 bytes public/icons/icon-192.png | Bin 0 -> 12993 bytes public/icons/icon-32.png | Bin 0 -> 1160 bytes public/icons/icon-512-maskable.png | Bin 0 -> 28226 bytes public/icons/icon-512.png | Bin 0 -> 40995 bytes public/manifest.webmanifest | 21 +++++++++ scripts/format-scope.json | 4 +- scripts/generate-icons.mjs | 70 +++++++++++++++++++++++++++++ src/tooling/webAppIcons.test.mjs | 70 +++++++++++++++++++++++++++++ 12 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 public/icons/icon-180.png create mode 100644 public/icons/icon-192.png create mode 100644 public/icons/icon-32.png create mode 100644 public/icons/icon-512-maskable.png create mode 100644 public/icons/icon-512.png create mode 100644 public/manifest.webmanifest create mode 100644 scripts/generate-icons.mjs create mode 100644 src/tooling/webAppIcons.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 032c6fe2a..eb61ad1db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +- Ship a web app manifest and raster home-screen icons, so adding the app to a + phone home screen or installing it on desktop uses the God's Eye View mark + instead of a blank tile. The icons are generated from `public/logo.svg` by + `node scripts/generate-icons.mjs` and committed, so a plain `vite build` needs + no image toolchain. The existing SVG favicon still serves the browser tab. + - GBFS v3 station names use English translations when available, then the first usable translation or short name. Empty or malformed names no longer appear as `[object Object]` or interrupt station loading; legacy string names remain diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 0d3ef31e0..f8e6bff3a 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -1131,6 +1131,22 @@ services, local voice and standalone key setup. Importing them starts no network acquisition. Browser layer lifecycle, rendering and voice execution stay in their existing modules. +## Web app install icons + +`public/manifest.webmanifest` declares the installable app: standalone display, +`#0a0a0f` background and theme, and 192/512 icons plus a 512 maskable variant. +`index.html` links the manifest, a 180px `apple-touch-icon` and a 32px PNG +favicon; the existing SVG favicon still serves the browser tab. Safari ignores an +SVG apple-touch-icon and substitutes a page screenshot, so the raster is what +makes an installed icon appear at all. + +`node scripts/generate-icons.mjs` renders every raster from `public/logo.svg` and +writes `public/icons/`. Output is committed, so building needs no image +toolchain; re-run the script after changing the logo. Ordinary icons inset the +mark to 78% of the canvas and the maskable variant to 54%, keeping it inside the +circle Android may crop to. `src/tooling/webAppIcons.test.mjs` checks the links, +the manifest fields and that each committed PNG is square at its declared size. + ## Local build preview After `npm run build`, `npm run preview` serves the built app with the same data diff --git a/index.html b/index.html index 2e630c4a8..31db11b06 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,16 @@ God's Eye View + + + + + + + + diff --git a/public/icons/icon-180.png b/public/icons/icon-180.png new file mode 100644 index 0000000000000000000000000000000000000000..5655a9e705643b9a36e8d27666014bc940e3d94f GIT binary patch literal 12055 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 literal 0 HcmV?d00001 diff --git a/public/icons/icon-32.png b/public/icons/icon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..a19279f0690e87da20ab7989e3ef4dd401336f59 GIT binary patch literal 1160 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

jis$0avX_KC%q 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 kDTR-v5{3c)59}m9;{X5v literal 0 HcmV?d00001 diff --git a/public/icons/icon-512.png b/public/icons/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..644a5c8eae4afb4859ea929be2c517a19e834b39 GIT binary patch literal 40995 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 literal 0 HcmV?d00001 diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 000000000..574b90faf --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "God's Eye View", + "short_name": "God's Eye", + "description": "Live spatial intelligence in the browser.", + "start_url": "/", + "scope": "/", + "display": "standalone", + "orientation": "any", + "background_color": "#0a0a0f", + "theme_color": "#0a0a0f", + "icons": [ + { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }, + { + "src": "/icons/icon-512-maskable.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/scripts/format-scope.json b/scripts/format-scope.json index 662d33a6b..d910536a2 100644 --- a/scripts/format-scope.json +++ b/scripts/format-scope.json @@ -981,5 +981,7 @@ "src/tools/globePanel.test.mjs", "src/costEndpointThrottle.test.mjs", "src/sources/gbfsStations.test.mjs", - "scripts/qa-gbfs-names.mjs" + "scripts/qa-gbfs-names.mjs", + "scripts/generate-icons.mjs", + "src/tooling/webAppIcons.test.mjs" ] diff --git a/scripts/generate-icons.mjs b/scripts/generate-icons.mjs new file mode 100644 index 000000000..3c16eb46e --- /dev/null +++ b/scripts/generate-icons.mjs @@ -0,0 +1,70 @@ +#!/usr/bin/env node +// Renders the home-screen and favicon rasters from `public/logo.svg`. +// +// Re-run after changing the logo: `node scripts/generate-icons.mjs`. The output +// is committed, because the install icons have to exist in a plain `vite build` +// and contributors should not need an image toolchain to ship the app. +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import sharp from 'sharp'; + +const REPO_ROOT = path.resolve( + fileURLToPath(new URL('.', import.meta.url)), + '..', +); +const LOGO = path.join(REPO_ROOT, 'public', 'logo.svg'); +const OUT_DIR = path.join(REPO_ROOT, 'public', 'icons'); + +/** `--bg-dark` from src/ui/styles/foundation.css, so the tile matches the app. */ +const BACKGROUND = '#0a0a0f'; + +/** + * Fraction of the canvas the mark is allowed to occupy. + * + * A maskable icon may be cropped to a circle inscribed in the middle 80% of the + * square, so its mark stays well inside that circle; the other icons are shown + * as drawn and only need optical breathing room. + */ +const INSET = { normal: 0.78, maskable: 0.54 }; + +const TARGETS = [ + { file: 'icon-32.png', size: 32, inset: INSET.normal }, + { file: 'icon-180.png', size: 180, inset: INSET.normal }, + { file: 'icon-192.png', size: 192, inset: INSET.normal }, + { file: 'icon-512.png', size: 512, inset: INSET.normal }, + { file: 'icon-512-maskable.png', size: 512, inset: INSET.maskable }, +]; + +/** Fit the logo inside `size * inset` and centre it on an opaque square. */ +async function render(svg, { size, inset }) { + const box = Math.round(size * inset); + // The logo's viewBox is wider than it is tall, so `contain` bounds it by + // width and leaves the vertical padding uneven — centring is done by the + // composite below rather than by the resize. + const mark = await sharp(svg, { density: 384 }) + .resize(box, box, { + fit: 'contain', + background: { r: 0, g: 0, b: 0, alpha: 0 }, + }) + .png() + .toBuffer(); + return sharp({ + create: { + width: size, + height: size, + channels: 4, + background: BACKGROUND, + }, + }) + .composite([{ input: mark, gravity: 'centre' }]) + .png({ compressionLevel: 9 }) + .toBuffer(); +} + +const svg = await readFile(LOGO); +await mkdir(OUT_DIR, { recursive: true }); +for (const target of TARGETS) { + await writeFile(path.join(OUT_DIR, target.file), await render(svg, target)); + console.log(`icons: wrote ${target.file} (${target.size}px)`); +} diff --git a/src/tooling/webAppIcons.test.mjs b/src/tooling/webAppIcons.test.mjs new file mode 100644 index 000000000..2267872fe --- /dev/null +++ b/src/tooling/webAppIcons.test.mjs @@ -0,0 +1,70 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import sharp from 'sharp'; + +const REPO_ROOT = path.resolve( + fileURLToPath(new URL('.', import.meta.url)), + '../..', +); +const PUBLIC_ROOT = path.join(REPO_ROOT, 'public'); +const INDEX_HTML = readFileSync(path.join(REPO_ROOT, 'index.html'), 'utf8'); + +/** `public/` is served at the site root, so a manifest href maps to a file by dropping the leading slash. */ +function publicFile(href) { + assert.ok(href.startsWith('/'), `${href} must be a root-relative URL`); + return path.join(PUBLIC_ROOT, href.slice(1)); +} + +const manifestHref = INDEX_HTML.match( + /]+rel="manifest"[^>]+href="([^"]+)"/, +)?.[1]; + +test('index.html links a web app manifest', () => { + assert.ok(manifestHref, 'no in index.html'); +}); + +test('iOS gets a raster home-screen icon', () => { + // Safari ignores SVG for apple-touch-icon and falls back to a screenshot of + // the page, which is the blank tile this whole file exists to prevent. The + // existing `rel="icon"` SVG covers the browser tab and nothing else. + const href = INDEX_HTML.match( + /]+rel="apple-touch-icon"[^>]+href="([^"]+)"/, + )?.[1]; + assert.ok(href, 'no in index.html'); + assert.match(href, /\.png$/, 'apple-touch-icon must be a PNG'); + assert.doesNotThrow(() => readFileSync(publicFile(href))); +}); + +test('the manifest declares a standalone installable app', () => { + const manifest = JSON.parse(readFileSync(publicFile(manifestHref), 'utf8')); + assert.equal(manifest.display, 'standalone'); + assert.equal(manifest.start_url, '/'); + for (const field of ['name', 'short_name', 'background_color', 'theme_color']) + assert.ok(manifest[field], `manifest is missing ${field}`); + // Android's install prompt needs both, and uses the 512 for the splash screen. + const sizes = new Set(manifest.icons.map((icon) => icon.sizes)); + for (const required of ['192x192', '512x512']) + assert.ok(sizes.has(required), `manifest declares no ${required} icon`); + // Without a maskable icon Android pads the square into a circle and clips + // the mark; with one it crops to the safe zone instead. + assert.ok( + manifest.icons.some((icon) => icon.purpose?.includes('maskable')), + 'manifest declares no maskable icon', + ); +}); + +test('every declared icon is a square PNG at its declared size', async () => { + const manifest = JSON.parse(readFileSync(publicFile(manifestHref), 'utf8')); + for (const icon of manifest.icons) { + const meta = await sharp(publicFile(icon.src)).metadata(); + const [width, height] = icon.sizes.split('x').map(Number); + assert.equal(meta.format, 'png', `${icon.src} is not a PNG`); + // A size the manifest overstates is worse than a missing one: the platform + // trusts the declaration and scales whatever it finds. + assert.equal(meta.width, width, `${icon.src} width`); + assert.equal(meta.height, height, `${icon.src} height`); + } +}); From fc2c372ffcc7cbb67ecda42bd8e27a08be719ca5 Mon Sep 17 00:00:00 2001 From: Cliff Flood Date: Thu, 1 Oct 2026 15:01:32 -0400 Subject: [PATCH 3/5] fix: keep install assets inside a non-root base Manifest start_url, scope and icon src resolve against the manifest URL, so root-relative values sent an app served under a path prefix to the domain root. Make them relative, and add a build test under base: '/example/' that asserts every emitted link and manifest URL stays inside the base. index.html is unchanged: Vite already rewrites its links under base. --- CHANGELOG.md | 2 + docs/CURRENT-STATE.md | 6 +++ public/manifest.webmanifest | 10 ++-- src/tooling/webAppIcons.test.mjs | 85 ++++++++++++++++++++++++++++++-- 4 files changed, 95 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb61ad1db..0c07f3c64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ instead of a blank tile. The icons are generated from `public/logo.svg` by `node scripts/generate-icons.mjs` and committed, so a plain `vite build` needs no image toolchain. The existing SVG favicon still serves the browser tab. + Manifest paths are relative to the manifest, so a build served under a path + prefix installs and launches inside that prefix. - GBFS v3 station names use English translations when available, then the first usable translation or short name. Empty or malformed names no longer appear as diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index f8e6bff3a..3d51bad2a 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -1140,12 +1140,18 @@ favicon; the existing SVG favicon still serves the browser tab. Safari ignores a SVG apple-touch-icon and substitutes a page screenshot, so the raster is what makes an installed icon appear at all. +Manifest `start_url`, `scope` and icon paths are relative to the manifest, and +Vite rewrites the `index.html` links under `base`, so a build served from a path +prefix installs and launches inside that prefix. + `node scripts/generate-icons.mjs` renders every raster from `public/logo.svg` and writes `public/icons/`. Output is committed, so building needs no image toolchain; re-run the script after changing the logo. Ordinary icons inset the mark to 78% of the canvas and the maskable variant to 54%, keeping it inside the circle Android may crop to. `src/tooling/webAppIcons.test.mjs` checks the links, the manifest fields and that each committed PNG is square at its declared size. +It also builds the install links under `base: '/example/'` and asserts every +emitted link and manifest URL stays inside that base. ## Local build preview diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest index 574b90faf..cf4b2b791 100644 --- a/public/manifest.webmanifest +++ b/public/manifest.webmanifest @@ -2,17 +2,17 @@ "name": "God's Eye View", "short_name": "God's Eye", "description": "Live spatial intelligence in the browser.", - "start_url": "/", - "scope": "/", + "start_url": ".", + "scope": ".", "display": "standalone", "orientation": "any", "background_color": "#0a0a0f", "theme_color": "#0a0a0f", "icons": [ - { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }, - { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }, + { "src": "icons/icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png" }, { - "src": "/icons/icon-512-maskable.png", + "src": "icons/icon-512-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/src/tooling/webAppIcons.test.mjs b/src/tooling/webAppIcons.test.mjs index 2267872fe..6a6cdb350 100644 --- a/src/tooling/webAppIcons.test.mjs +++ b/src/tooling/webAppIcons.test.mjs @@ -1,9 +1,12 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; +import { rm, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import sharp from 'sharp'; +import { build } from 'vite'; +import { makeFixtureRoot } from './fixtureRoot.mjs'; const REPO_ROOT = path.resolve( fileURLToPath(new URL('.', import.meta.url)), @@ -12,7 +15,7 @@ const REPO_ROOT = path.resolve( const PUBLIC_ROOT = path.join(REPO_ROOT, 'public'); const INDEX_HTML = readFileSync(path.join(REPO_ROOT, 'index.html'), 'utf8'); -/** `public/` is served at the site root, so a manifest href maps to a file by dropping the leading slash. */ +/** `public/` is served at the site root, so an index.html href maps to a file by dropping the leading slash. */ function publicFile(href) { assert.ok(href.startsWith('/'), `${href} must be a root-relative URL`); return path.join(PUBLIC_ROOT, href.slice(1)); @@ -22,6 +25,18 @@ const manifestHref = INDEX_HTML.match( /]+rel="manifest"[^>]+href="([^"]+)"/, )?.[1]; +/** + * Manifest URLs resolve against the manifest, not the page. Keeping them + * relative is what lets a build served under a path prefix install inside it. + */ +function manifestFile(src) { + assert.ok( + !src.startsWith('/') && !/^[a-z][a-z\d+.-]*:/i.test(src), + `${src} must be relative to the manifest`, + ); + return path.join(path.dirname(publicFile(manifestHref)), src); +} + test('index.html links a web app manifest', () => { assert.ok(manifestHref, 'no in index.html'); }); @@ -41,7 +56,8 @@ test('iOS gets a raster home-screen icon', () => { test('the manifest declares a standalone installable app', () => { const manifest = JSON.parse(readFileSync(publicFile(manifestHref), 'utf8')); assert.equal(manifest.display, 'standalone'); - assert.equal(manifest.start_url, '/'); + assert.equal(manifest.start_url, '.'); + assert.equal(manifest.scope, '.'); for (const field of ['name', 'short_name', 'background_color', 'theme_color']) assert.ok(manifest[field], `manifest is missing ${field}`); // Android's install prompt needs both, and uses the 512 for the splash screen. @@ -59,7 +75,7 @@ test('the manifest declares a standalone installable app', () => { test('every declared icon is a square PNG at its declared size', async () => { const manifest = JSON.parse(readFileSync(publicFile(manifestHref), 'utf8')); for (const icon of manifest.icons) { - const meta = await sharp(publicFile(icon.src)).metadata(); + const meta = await sharp(manifestFile(icon.src)).metadata(); const [width, height] = icon.sizes.split('x').map(Number); assert.equal(meta.format, 'png', `${icon.src} is not a PNG`); // A size the manifest overstates is worse than a missing one: the platform @@ -68,3 +84,66 @@ test('every declared icon is a square PNG at its declared size', async () => { assert.equal(meta.height, height, `${icon.src} height`); } }); + +test('install assets stay inside a non-root Vite base', async (t) => { + // The real index.html pulls the whole Cesium app into a build, so only its + // install links are copied, verbatim, into a fixture page. + const tags = INDEX_HTML.match( + /]+rel="(?:icon|apple-touch-icon|manifest)"[^>]*>/g, + ); + assert.ok(tags?.length >= 3, 'index.html lost its install links'); + const root = await makeFixtureRoot('gev-webapp-icons-'); + t.after(() => rm(root, { recursive: true, force: true })); + await writeFile( + path.join(root, 'index.html'), + `${tags.join('')}`, + ); + const base = '/example/'; + const origin = 'https://host.test'; + const result = await build({ + root, + configFile: false, + envFile: false, + logLevel: 'silent', + base, + publicDir: PUBLIC_ROOT, + build: { write: false }, + }); + const html = result.output.find( + (item) => item.fileName === 'index.html', + ).source; + const hrefs = [...html.matchAll(/]+href="([^"]+)"/g)].map( + (match) => match[1], + ); + assert.equal(hrefs.length, tags.length); + for (const href of hrefs) { + assert.ok(href.startsWith(base), `${href} escapes ${base}`); + assert.doesNotThrow(() => + readFileSync(path.join(PUBLIC_ROOT, href.slice(base.length))), + ); + } + + // Vite copies public/ only when it writes, and copies it verbatim, so the + // deployed manifest is the committed one, served at the emitted href. + const manifestUrl = new URL( + html.match(/]+rel="manifest"[^>]+href="([^"]+)"/)[1], + origin, + ); + const manifest = JSON.parse(readFileSync(publicFile(manifestHref), 'utf8')); + const inBase = (value) => { + const url = new URL(value, manifestUrl); + assert.ok( + url.href.startsWith(origin + base), + `${value} resolves to ${url.href}, outside ${base}`, + ); + return url; + }; + assert.equal(inBase(manifest.start_url).href, origin + base); + assert.equal(inBase(manifest.scope).href, origin + base); + for (const icon of manifest.icons) { + const url = inBase(icon.src); + assert.doesNotThrow(() => + readFileSync(path.join(PUBLIC_ROOT, url.pathname.slice(base.length))), + ); + } +}); From 3cdb440bc2166fb9bd42104b69f4ddc162c3dd31 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 15 Oct 2026 17:27:55 -0700 Subject: [PATCH 4/5] Use the opaque iOS status bar and keep the SVG tab icon black-translucent needs viewport-fit=cover and safe-area padding, which this change does not add. List the PNG favicon before the SVG so browsers that take the last usable icon keep the SVG, add mobile-web-app-capable, and give the manifest a stable id. --- index.html | 7 +++++-- public/manifest.webmanifest | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index bdc82bd25..8a01415b6 100644 --- a/index.html +++ b/index.html @@ -4,16 +4,19 @@ God's Eye View - + + + - + diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest index cf4b2b791..478cd9019 100644 --- a/public/manifest.webmanifest +++ b/public/manifest.webmanifest @@ -1,4 +1,5 @@ { + "id": ".", "name": "God's Eye View", "short_name": "God's Eye", "description": "Live spatial intelligence in the browser.", From 687a94c9df5226ace08c5cbb8ff888641ec919da Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 15 Oct 2026 17:27:55 -0700 Subject: [PATCH 5/5] =?UTF-8?q?Document=20Bike=20Ita=C3=BA=20(Tembici)=20a?= =?UTF-8?q?nd=20test=20the=20Brazilian=20cities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The seven feeds publish no licence, operator or attribution fields, so DATA_SOURCES records that and credits the operator. Adds a registry test per city: allowed host and path, provider, and activation over its outermost live station. --- CHANGELOG.md | 3 +- DATA_SOURCES.md | 3 +- docs/CURRENT-STATE.md | 2 +- src/data/dataCredits.js | 2 +- src/layers/bikeshare/brPbscCities.test.mjs | 73 ++++++++++++++++++++++ 5 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 src/layers/bikeshare/brPbscCities.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index f63083839..5d0194a6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,7 +66,8 @@ - Add seven Brazilian Bike Itaú (Tembici) cities to the GBFS bikeshare registry: São Paulo, Rio de Janeiro, Salvador, Recife, Porto Alegre, Brasília and - Curitiba. These are the layer's first stations in South America. + Curitiba (about 1,000 stations in total). These are the layer's first stations + in South America. - Add Bike Share Toronto (1,069 stations) to the bikeshare registry. The system publishes the same PBSC GBFS v2 feeds as the Austin, Honolulu and Chattanooga diff --git a/DATA_SOURCES.md b/DATA_SOURCES.md index ac1718308..66c92174e 100644 --- a/DATA_SOURCES.md +++ b/DATA_SOURCES.md @@ -65,8 +65,9 @@ How to read this: | **Statens vegvesen** (catalog `ogckart-sn1.atlas.vegvesen.no`, frames `kamera.atlas.vegvesen.no`, HLS `kamera.vegvesen.no`) | CCTV camera catalog + frames + live HLS video, Norway road cameras | [NLOD 2.0](https://data.norge.no/nlod/en/2.0) — attribution REQUIRED | "Contains data under the Norwegian licence for Open Government data (NLOD) distributed by Statens vegvesen" | | **NYC DOT Traffic Management Center** (`webcams.nyctmc.org`) | CCTV camera catalog + frames, New York City five boroughs | NYC DOT requires developers to sign a [data-sharing agreement](https://www.nyc.gov/html/dot/html/about/datafeeds.shtml) for the camera feed. **Off by default**; enable only under your own agreement | "NYC DOT Traffic Management Center" + NYC DOT's required application disclaimer (in the Data attribution popover) | | **City of Toronto Open Data** (`opendata.toronto.ca`) | CCTV camera catalog + frames, Toronto city streets | [Open Government Licence – Toronto](https://www.toronto.ca/city-government/data-research-maps/open-data/open-data-licence/) — attribution REQUIRED | "Contains information licensed under the Open Government Licence – Toronto" | -| **GBFS (Lyft / BCycle / Social Bicycles / PBSC)** | Bikeshare availability | Per-feed (attribution-only) | Credit the operator (e.g. Austin BCycle, Bike Itaú/Tembici) + its `license_url` | +| **GBFS (Lyft / BCycle / Social Bicycles / PBSC)** | Bikeshare availability | Per-feed (attribution-only) | Credit the operator (e.g. Austin BCycle) + its `license_url` | | **Bike Share Toronto** (`toronto.publicbikesystem.net`) | Bikeshare station availability, Toronto | Operator GBFS feed. No licence is published: the City's open-data package declares none and the feed has no `license_url`. Attribution is given to the operator. | "Bike Share Toronto (Toronto Parking Authority)" | +| **Bike Itaú (Tembici)** (`*.publicbikesystem.net`, seven Brazilian cities) | Bikeshare station availability: São Paulo, Rio de Janeiro, Salvador, Recife, Porto Alegre, Brasília and Curitiba | Operator GBFS feeds. No licence is published: the feeds' `system_information.json` carries no `license_url`, operator or attribution fields. Attribution is given to the operator. | "Bike Itaú (Tembici)" | | **Oslo / Bergen / Trondheim Bysykkel** (Urban Sharing, `gbfs.urbansharing.com`) | Bikeshare availability, Oslo, Bergen and Trondheim city bikes. Keyless GBFS; the proxy sends the `Client-Identifier` header the operators ask for | [NLOD 2.0](https://data.norge.no/nlod/en/2.0) — attribution REQUIRED ([Oslo](https://oslobysykkel.no/en/open-data/realtime), [Bergen](https://bergenbysykkel.no/en/apne-data/sanntid), [Trondheim](https://trondheimbysykkel.no/en/open-data/realtime)) | "Contains data under the Norwegian licence for Open Government data (NLOD) made available by Oslo Bysykkel, Bergen Bysykkel and Trondheim Bysykkel" | | **Kolumbus / Farte Bysykkel** (Entur Mobility, `api.entur.io/mobility/v2/gbfs`) | Bikeshare availability, Kolumbus Bysykkel (Stavanger/Nord-Jæren) and Farte Bysykkel (Skien/Porsgrunn). Keyless GBFS; the proxy sends the `ET-Client-Name` header Entur requires and is limited to Entur's GBFS paths | [NLOD](https://data.norge.no/nlod/en/2.0) per the [Entur Mobility API docs](https://developer.entur.org/pages-mobility-docs-mobility-v2) — attribution REQUIRED | "Contains data under the Norwegian licence for Open Government data (NLOD) made available by Entur (Kolumbus Bysykkel, Farte Bysykkel)" | | **JCDecaux Cyclocity** (`api.cyclocity.fr`) | Bikeshare availability, Bysykkel Lillestrøm. Keyless GBFS; the proxy is limited to JCDecaux contract paths | [Licence Ouverte / Etalab](https://developer.jcdecaux.com/files/Open-Licence-fr.pdf) (the feed's `license_url`) — attribution REQUIRED | "Bysykkel Lillestrøm: JCDecaux, Licence Ouverte" | diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 61f7a0e80..7a211e806 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -3232,7 +3232,7 @@ its criteria cannot be silently ignored. | CCTV | Austin + Caltrans (CA) + TfL London + Ontario 511 + Fintraffic (FI) + DriveBC (BC) + TxDOT (TX) + Estonia (Tallinn, Tarktee) + Live Traffic NSW + Open Calgary Open Data + Statens vegvesen (NO, stills + live HLS) + King County (WA) Open Data + WSDOT (WA) Open Data + NYC DOT TMC (opt-in) + City of Toronto RESCU + Catalonia (Servei Català de Trànsit) + Street View fallback | `src/data/cctv.js` | `/api/cctv` | 10s (active) | | Radio | Radio Browser (public-domain station directory) | `src/data/radio.js` | `/api/radio/stations`, `/api/radio/click/:uuid` | 45 min directory refresh | | Transit 🚌 | Operator GTFS-Realtime VehiclePositions (7 keyless regions, `src/data/transitFeeds.js`) | `src/layers/transit/` via `src/app/layers/transit.js` | `/api/transit` | 15s (poll + delayed playback) | -| Bikeshare 🚲 | GBFS (Lyft + BCycle + Social Bicycles + PBSC incl. Bike Share Toronto + Urban Sharing, Entur, JCDecaux NO) | `src/data/bikeshare.js` | `/api/gbfs` | 60s | +| Bikeshare 🚲 | GBFS (Lyft + BCycle + Social Bicycles + PBSC incl. Bike Share Toronto and Bike Itaú (Brazil) + Urban Sharing, Entur, JCDecaux NO) | `src/data/bikeshare.js` | `/api/gbfs` | 60s | | Directions 🧭 | OSRM on FOSSGIS servers (OpenStreetMap) | `src/data/directions.js` | `/api/route` (`steps=1`) | on placement / mode change | | Datacenters ▣ | OSM extract (bundled) | `src/data/localLayers.js` | — | static | | Dams ▰ | OpenInfraMap/OSM extract (bundled) | `src/data/localLayers.js` | — | static | diff --git a/src/data/dataCredits.js b/src/data/dataCredits.js index 40537d4a9..d6c265ddc 100644 --- a/src/data/dataCredits.js +++ b/src/data/dataCredits.js @@ -304,7 +304,7 @@ export const DATA_CREDITS = [ key: 'gbfs', html: 'Bikeshare availability: GBFS operator feeds ' + - '(e.g. Austin BCycle; Bike Share Toronto, operated by the Toronto Parking Authority; Bike Itaú/Tembici)', + '(e.g. Austin BCycle; Bike Share Toronto, operated by the Toronto Parking Authority; Bike Itaú, operated by Tembici)', }, { key: 'urbansharing-gbfs', diff --git a/src/layers/bikeshare/brPbscCities.test.mjs b/src/layers/bikeshare/brPbscCities.test.mjs new file mode 100644 index 000000000..60e12c23b --- /dev/null +++ b/src/layers/bikeshare/brPbscCities.test.mjs @@ -0,0 +1,73 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { CITY_BY_ID } from './registry.js'; +import { createModel } from './model.js'; +import { createViewport } from './viewport.js'; +import { isAllowedGbfsHost, isAllowedGbfsPath } from '../../data/gbfsSource.js'; + +// Each Bike Itaú city's PBSC host, and the station farthest from its +// registered centre in the live feed (checked 2026-10-15). +const CITIES = { + 'sao-paulo-bike-itau': { + host: 'saopaulo.publicbikesystem.net', + stations: [{ name: '260 - P3', lat: -23.568235, lon: -46.740715 }], + }, + 'rio-de-janeiro-bike-itau': { + host: 'riodejaneiro.publicbikesystem.net', + stations: [{ name: '293 - Praia da Macumba', lat: -23.03135, lon: -43.479584 }], + }, + 'salvador-bike-itau': { + host: 'salvador.publicbikesystem.net', + stations: [{ name: '49 - Stella Maris', lat: -12.93645, lon: -38.32701 }], + }, + 'recife-bike-itau': { + host: 'recife.publicbikesystem.net', + stations: [{ name: '69 - Praia de Piedade', lat: -8.186205, lon: -34.918371 }], + }, + 'porto-alegre-bike-itau': { + host: 'portoalegre.publicbikesystem.net', + stations: [{ name: '72 - Avenida Tramandaí', lat: -30.135915, lon: -51.22684 }], + }, + 'brasilia-bike-itau': { + host: 'brasilia.publicbikesystem.net', + stations: [{ name: "60 - Parque Olhos d'Água", lat: -15.7431, lon: -47.88667 }], + }, + 'curitiba-bike-itau': { + host: 'curitiba.publicbikesystem.net', + stations: [{ name: '42 - Terminal Portão', lat: -25.476885, lon: -49.292608 }], + }, +}; + +// The production range check, with no viewer or fetches behind it. +const parts = { model: createModel({ state: {}, services: {}, parts: {}, source: null }) }; +const { computeInRangeCities } = createViewport({ + state: {}, + services: { render: { governorRequestRender() {} } }, + parts, + source: null, +}); + +test('the Bike Itaú systems are registered and readable through the proxy', () => { + for (const [id, expected] of Object.entries(CITIES)) { + const city = CITY_BY_ID.get(id); + assert.ok(city, `${id} is registered`); + assert.equal(city.provider, 'Bike Itaú'); + assert.deepEqual(city.hosts, [expected.host]); + for (const feed of [city.stationInformationUrl, city.stationStatusUrl]) { + const url = new URL(feed); + assert.ok(isAllowedGbfsHost(url.hostname), `${id} host ${url.hostname}`); + assert.ok(isAllowedGbfsPath(url.pathname), `${id} path ${url.pathname}`); + } + } +}); + +test('a camera over each outermost station activates its system', () => { + for (const [id, expected] of Object.entries(CITIES)) { + for (const station of expected.stations) { + assert.ok( + computeInRangeCities({ lat: station.lat, lon: station.lon }).has(id), + `${station.name} activates ${id}`, + ); + } + } +});