[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-2022":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":15,"forks30d":15,"starsTrendScore":19,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":22,"hasPages":22,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":42,"readmeContent":43,"aiSummary":44,"trendingCount":15,"starSnapshotCount":15,"syncStatus":45,"lastSyncTime":46,"discoverSource":47},2022,"mini-macau","asdfghj1237890\u002Fmini-macau","asdfghj1237890","Trilingual 3D visualization of Macau's public transit and aviation — LRT, buses, ferries, and MFM airport flights with schedule-driven simulation","https:\u002F\u002Fmini-map-macau.app",null,"HTML",603,76,51,0,54,127,394,162,9.66,"MIT License",false,"master",[25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41],"3d-visualization","aviation","bus","ferry","i18n","lrt","macau","maplibre","openstreetmap","react","real-time","simulation","tailwindcss","transit","typescript","vite","webgl","2026-06-12 02:00:36","# Mini Map Macau 🚈🚌✈️🛥️\n\n> **[mini-map-macau.app](https:\u002F\u002Fmini-map-macau.app\u002F)**\n\n[![Live site](https:\u002F\u002Fimg.shields.io\u002Fwebsite?url=https%3A%2F%2Fmini-map-macau.app&label=live&up_message=online&down_message=offline)](https:\u002F\u002Fmini-map-macau.app\u002F)\n[![Latest release](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fv\u002Ftag\u002Fasdfghj1237890\u002Fmini-macau?label=release&sort=semver)](https:\u002F\u002Fgithub.com\u002Fasdfghj1237890\u002Fmini-macau\u002Ftags)\n[![Deploy](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Factions\u002Fworkflow\u002Fstatus\u002Fasdfghj1237890\u002Fmini-macau\u002Fdeploy.yml?label=deploy&branch=master)](https:\u002F\u002Fgithub.com\u002Fasdfghj1237890\u002Fmini-macau\u002Factions\u002Fworkflows\u002Fdeploy.yml)\n[![Flights sync](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Factions\u002Fworkflow\u002Fstatus\u002Fasdfghj1237890\u002Fmini-macau\u002Fupdate-flights.yml?label=flights%20sync)](https:\u002F\u002Fgithub.com\u002Fasdfghj1237890\u002Fmini-macau\u002Factions\u002Fworkflows\u002Fupdate-flights.yml)\n[![Ferries sync](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Factions\u002Fworkflow\u002Fstatus\u002Fasdfghj1237890\u002Fmini-macau\u002Fupdate-ferry-schedules.yml?label=ferries%20sync)](https:\u002F\u002Fgithub.com\u002Fasdfghj1237890\u002Fmini-macau\u002Factions\u002Fworkflows\u002Fupdate-ferry-schedules.yml)\n\n[![License](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Flicense\u002Fasdfghj1237890\u002Fmini-macau)](.\u002FLICENSE)\n[![Made with React](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FReact-19-61DAFB?logo=react&logoColor=white)](https:\u002F\u002Freact.dev\u002F)\n[![TypeScript](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FTypeScript-6-3178C6?logo=typescript&logoColor=white)](https:\u002F\u002Fwww.typescriptlang.org\u002F)\n[![Vite](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FVite-8-646CFF?logo=vite&logoColor=white)](https:\u002F\u002Fvitejs.dev\u002F)\n[![MapLibre GL](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FMapLibre_GL-5-396CB2?logo=maplibre&logoColor=white)](https:\u002F\u002Fmaplibre.org\u002F)\n\n3D visualization of Macau's public transit, ferry, and aviation system, inspired by [Mini Tokyo 3D](https:\u002F\u002Fminitokyo3d.com) and [Mini Taiwan](https:\u002F\u002Fmini-taiwan-learning-project.itsmigu.com\u002F).\n\nVisualizes the **Macau Light Rapid Transit (LRT)**, **bus network**, **HK–Macau ferry routes**, and **MFM airport flights** on an interactive 3D map. Vehicles move along actual geometry in a **timetable-driven simulation**, with an **opt-in RT mode** that replaces simulated bus positions with live DSAT realtime data.\n\n> **How \"live\" is this?** See [Data freshness & update strategy](#data-freshness--update-strategy) for a per-layer breakdown — LRT \u002F buses \u002F flights \u002F ferries each sit at a different point on the simulated-to-live spectrum.\n\n![og-image](https:\u002F\u002Fmini-map-macau.app\u002Fog-image.png)\n\n[![Demo — Macau bus fleet on the roundabout (click to play video)](.\u002Fdocs\u002Fdemo-01-poster.jpg)](https:\u002F\u002Fgithub.com\u002Fasdfghj1237890\u002Fmini-macau\u002Freleases\u002Fdownload\u002Freadme-assets-v1\u002Fdemo-01.mp4)\n\n[![Demo — LRT line with timetable panel (click to play video)](.\u002Fdocs\u002Fdemo-02-poster.jpg)](https:\u002F\u002Fgithub.com\u002Fasdfghj1237890\u002Fmini-macau\u002Freleases\u002Fdownload\u002Freadme-assets-v1\u002Fdemo-02.mp4)\n\n## Contents\n\n- [Features](#features)\n- [Architecture](#architecture)\n- [Tech Stack](#tech-stack)\n- [Getting Started](#getting-started)\n- [Data Pipeline](#data-pipeline)\n- [Data Sources](#data-sources)\n- [Data freshness & update strategy](#data-freshness--update-strategy)\n- [Project Structure](#project-structure)\n- [Performance Notes](#performance-notes)\n- [Acknowledgements](#acknowledgements)\n- [License](#license)\n\n## Features\n\n- **3D LRT vehicles** — 3 lines, 15 stations, real track geometry and elevated viaducts\n- **3D Bus fleet** — 92 routes, road-snapped via OSRM, with accurate cross-harbour bridge geometry\n- **3D Aircraft** — 176 real MFM flights (87 dep + 89 arr) with detailed airplane models, apron stands, and taxi paths\n- **3D Ferries** — 6 HK\u002FShenzhen ↔ Macau sea routes (TurboJET + CotaiJet) with jetfoil-shaped hull, red belly belt, and multi-deck cabin\n- **Timetable-driven simulation** — Schedule-synced playback with ETAs, service status, and trilingual labels (EN \u002F 繁中 \u002F PT)\n- **RT mode (opt-in)** — Toggle replaces simulated bus positions with the DSAT live feed; simulation stays active for LRT \u002F flights \u002F ferries\n- **Time controls** — Play\u002Fpause, 1×–60× speed, jump-to-now, free date\u002Ftime picker\n- **Vehicle tracking** — Click-to-follow with smooth camera and free zoom\u002Fpan\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>Full feature list\u003C\u002Fstrong>\u003C\u002Fsummary>\n\n- **3D LRT vehicles** — All 3 lines (Taipa, Seac Pai Van, Hengqin) with 15 stations, rendered as 3D models with real track geometry and elevated viaducts\n- **3D Bus fleet** — 92 routes with road-snapped paths via OSRM, including accurate bridge geometry (Macau–Taipa bridges)\n- **3D Aircraft** — 176 real MFM airport flights (87 departures + 89 arrivals) with detailed airplane models (fuselage, swept wings, vertical tail in airline colors, engine nacelles, window rows, cockpit windshield); aircraft park at 12 apron stands before departure and taxi along waypoint paths before takeoff\n- **Landing & holding patterns** — Aircraft approach from North or South with multi-waypoint landing routes; when the runway is occupied, arriving flights enter a realistic circular holding pattern above the airport and smoothly transition back to the landing route when clear\n- **3D Ferries** — 6 sea routes (Hong Kong Outer Harbour \u002F Taipa \u002F Sheung Wan, HKIA, Shenzhen Airport, Shekou) served by TurboJET and CotaiJet, rendered as jetfoil models (pontoon hull, red belt, white TurboJET band, cabin, windows, wheelhouse, roof) following great-circle paths with wake-aware headings\n- **Real-time simulation** — Vehicles move along routes based on timetables, service frequencies, and schedule types (Mon–Thu \u002F Friday \u002F Sat–Sun)\n- **ETA & vehicle info** — Click any vehicle or station to see live ETAs, next arrivals, route details, and service status\n- **Flight info** — Click any aircraft to see flight number, airline, destination\u002Forigin (with localized names), scheduled time, aircraft type, and live\u002Fsim status\n- **Ferry info** — Click any ferry to see operator, route, origin\u002Fdestination port (localized), scheduled departure, crossing time, and live progress\n- **Automated ferry data** — GitHub Actions workflow scrapes TurboJET and CotaiJet timetables monthly and commits updated schedules if changed\n- **Time controls** — Play, pause (spacebar), speed up (1×–60×), jump to current time, or pick any date\u002Ftime with the DateTimePicker; Esc toggles the sidebar menu\n- **Vehicle tracking** — Click a vehicle to follow it with smooth camera animation; freely zoom\u002Fpan while tracking\n- **Route visibility** — Toggle individual bus routes by group (Peninsula, Cross-Harbour, Taipa\u002FCotai, Night, Special); auto-mode shows only routes currently in service\n- **3D\u002F2D toggle** — Switch between perspective and top-down views\n- **Dark\u002FLight mode** — Two map styles (CARTO Dark Matter \u002F Positron)\n- **Trilingual UI** — English \u002F 繁體中文 \u002F Português — flight destinations, station names, and all labels switch with the language\n- **Cyberpunk-styled menu** — Hamburger menu with Orbitron-font title and gradient branding\n- **Responsive mobile UI** — Hamburger menu for map controls, compact legend buttons (LRT\u002FBus), optimized touch layout with safe-area support\n- **Lazy loading** — Code-split panels (VehicleInfoPanel, StationInfoPanel, FlightInfoPanel) for fast initial load\n- **Automated flight data** — GitHub Actions workflow syncs MFM flight schedules from the [AviationStack](https:\u002F\u002Faviationstack.com\u002F) API daily\n\n\u003C\u002Fdetails>\n\n## Architecture\n\nThree clean stages: upstream sources get normalized by Python into versioned static JSON, which the browser runtime replays on a simulated clock. RT mode adds a parallel live-feed path for buses only.\n\n```mermaid\nflowchart LR\n  subgraph Sources[\"External sources\"]\n    OSM[OpenStreetMap]\n    MLM[MLM LRT timetables]\n    DSATFreq[DSAT bus frequencies]\n    AS[AviationStack API]\n    TJ[TurboJET · CotaiJet]\n    DSATrt[DSAT realtime feed]\n  end\n\n  subgraph Pipeline[\"Python pipeline · GitHub Actions\"]\n    Scripts[\"data\u002Fscripts\u002F*.py\u003Cbr\u002F>(manual regen)\"]\n    CronF[\"update-flights.yml\u003Cbr\u002F>(daily)\"]\n    CronFerry[\"update-ferry-schedules.yml\u003Cbr\u002F>(monthly)\"]\n  end\n\n  subgraph Static[\"Bundled static JSON (public\u002Fdata\u002F)\"]\n    J1[lrt-lines · stations · trips]\n    J2[bus-routes · bus-stops]\n    J3[flights.json]\n    J4[ferry-schedules.json]\n  end\n\n  subgraph Runtime[\"Browser runtime\"]\n    Sim[\"Simulation engine\u003Cbr\u002F>timetable-driven playback\"]\n    RT[\"RT client\u003Cbr\u002F>\u002Fapi\u002Fdsat\u002Fbatch · 8s cache\"]\n    UI[MapLibre layers + React UI]\n  end\n\n  OSM --> Scripts\n  MLM --> Scripts\n  DSATFreq --> Scripts\n  AS --> CronF\n  TJ --> CronFerry\n\n  Scripts --> J1\n  Scripts --> J2\n  CronF --> J3\n  CronFerry --> J4\n\n  J1 --> Sim\n  J2 --> Sim\n  J3 --> Sim\n  J4 --> Sim\n\n  DSATrt -.->|opt-in RT toggle| RT\n  Sim --> UI\n  RT --> UI\n```\n\n## Tech Stack\n\n| Layer | Technology |\n|-------|-----------|\n| Frontend | React 19, TypeScript 6, Vite 8 |\n| 3D Map | MapLibre GL JS, custom WebGL fill-extrusion layers |\n| Geo utilities | Turf.js (nearest-point-on-line) + custom precomputed-polyline cache |\n| Styling | Tailwind CSS v4 |\n| Fonts | Orbitron, JetBrains Mono, Noto Sans HK (Google Fonts) |\n| Data pipeline | Python 3.13+, uv, OpenStreetMap Overpass API, OSRM |\n| Flight data | [AviationStack API](https:\u002F\u002Faviationstack.com\u002F) (daily sync) |\n| Ferry data | [TurboJET](https:\u002F\u002Fwww2.turbojet.com.hk\u002F) + [CotaiJet](https:\u002F\u002Fwww.cotaiwaterjet.com\u002F) timetables (monthly web scraper) |\n| Deployment | Cloudflare Pages (via GitHub Actions) |\n| Analytics | Google Analytics (gtag.js) |\n\n## Getting Started\n\n### Prerequisites\n\n- [Node.js](https:\u002F\u002Fnodejs.org\u002F) 20+\n- npm\n- [uv](https:\u002F\u002Fdocs.astral.sh\u002Fuv\u002F) (for data pipeline only)\n\n### Install & Run\n\n```bash\nnpm install\nnpm run dev\n```\n\nThe app will be available at `http:\u002F\u002Flocalhost:5173`.\n\n### Build for Production\n\n```bash\nnpm run build\nnpm run preview\n```\n\n## Data Pipeline\n\nTransit data is pre-generated and included in `public\u002Fdata\u002F`.\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>Regenerate transit data\u003C\u002Fstrong>\u003C\u002Fsummary>\n\n```bash\ncd data\n\n# Set up Python environment\nuv sync\n\n# Run all data extraction scripts\nuv run python main.py\n```\n\nThis will:\n1. Extract LRT track geometry from OpenStreetMap (`railway=light_rail` ways)\n2. Extract bus routes and stops from OpenStreetMap + [motransportinfo.com](https:\u002F\u002Fwww.motransportinfo.com) reference data\n3. Fetch bridge approach geometry for accurate cross-harbour routing\n4. Snap bus routes to roads via OSRM with custom bridge geometry patching\n5. Generate timetables based on published service frequencies\n6. Output JSON files to `data\u002Foutput\u002F`\n\nThen copy the output to `public\u002Fdata\u002F`.\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>Flight data sync\u003C\u002Fstrong>\u003C\u002Fsummary>\n\nFlight schedules are fetched from the [AviationStack](https:\u002F\u002Faviationstack.com\u002F) API and stored as a static JSON file:\n\n```bash\ncd data\n\n# Fetch today's MFM flights (requires API key)\nAVIATIONSTACK_API_KEY=your_key uv run python scripts\u002Ffetch_flights.py\n\n# Fetch a specific date\nAVIATIONSTACK_API_KEY=your_key uv run python scripts\u002Ffetch_flights.py 2026-04-19\n```\n\nThe sync:\n- Pulls arrivals and departures for MFM (IATA: `MFM`) from the AviationStack flights endpoint\n- Filters by the target date's active schedule\n- Validates aircraft type codes (ICAO format like A320, B738)\n- Outputs `public\u002Fdata\u002Fflights.json` with times in Macau local (UTC+8)\n\nThis is also automated via GitHub Actions (`.github\u002Fworkflows\u002Fupdate-flights.yml`), which runs daily at 04:00 Macau time (UTC+8) and commits updated flight data if changed.\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>Ferry schedule scraper\u003C\u002Fstrong>\u003C\u002Fsummary>\n\nFerry timetables are scraped from the operator sites and stored as a single static JSON file with 6 routes across two operators (TurboJET and CotaiJet):\n\n```bash\ncd data\n\n# Scrape the current month's schedules for all routes\nuv run python scripts\u002Ffetch_ferry_schedules.py\n```\n\nThe scraper:\n- Pulls TurboJET schedules for Hong Kong (Outer Harbour), Hong Kong (Taipa), HKIA, Shenzhen Airport, and Shekou\n- Pulls CotaiJet schedule for Hong Kong (Sheung Wan) ↔ Macau Taipa\n- Records `fetchedAtUtc` and `effectiveAs` metadata so stale data is easy to spot\n- Outputs `public\u002Fdata\u002Fferry-schedules.json`\n\nAutomated via GitHub Actions (`.github\u002Fworkflows\u002Fupdate-ferry-schedules.yml`), which runs on the 1st of each month at 00:00 UTC (08:00 Macau) and commits updates if changed.\n\n\u003C\u002Fdetails>\n\n## Data Sources\n\n- **LRT tracks & stations** — [OpenStreetMap](https:\u002F\u002Fwww.openstreetmap.org\u002F) (railway=light_rail relations)\n- **LRT timetables** — [MLM 澳門輕軌股份有限公司](https:\u002F\u002Fwww.mlm.com.mo\u002F) official per-station timetable publications (Taipa \u002F Seac Pai Van \u002F Hengqin lines)\n- **Bus routes & stops** — OpenStreetMap + [motransportinfo.com](https:\u002F\u002Fwww.motransportinfo.com) curated stop data\n- **Road-snapped routes** — [OSRM](http:\u002F\u002Fproject-osrm.org\u002F) with custom bridge approach geometry\n- **Bus timetables** — Based on published DSAT service frequencies\n- **Flight schedules** — [AviationStack API](https:\u002F\u002Faviationstack.com\u002F) (MFM arrivals + departures)\n- **Ferry schedules** — [TurboJET](https:\u002F\u002Fwww2.turbojet.com.hk\u002Fzh-tw\u002F%E6%B5%B7-%E8%88%B9\u002F) + [CotaiJet](https:\u002F\u002Fm.cotaiwaterjet.com\u002Fhk\u002Fferry-schedule\u002Fhongkong-macau-taipa.html) official monthly timetables\n\n## Data freshness & update strategy\n\nNot every layer is equally \"live.\" The default view is **fully simulated**; RT mode is the only path that touches an actual realtime feed, and even then only for buses.\n\n| Layer | Mode | Source | Refresh cadence | Staleness indicator |\n|-------|------|--------|-----------------|---------------------|\n| **LRT** | Simulated | OSM geometry + MLM published per-station timetable | Manual regen (`uv run python data\u002Fmain.py`) | None — static JSON |\n| **Bus (default)** | Simulated | OSM geometry + DSAT published service frequencies | Manual regen | None — static JSON |\n| **Bus (RT toggle)** | **Live** | DSAT realtime feed via nginx `\u002Fapi\u002Fdsat\u002Fbatch` proxy | Client polls every 15 s · server edge-caches 8 s | Per-bus `lastAt`; stale beyond 60 s window |\n| **Flights** | Static daily sync | [AviationStack API](https:\u002F\u002Faviationstack.com\u002F) | Daily at 04:00 Macau time — `update-flights.yml` | `fetchedAtUtc` embedded in `flights.json` |\n| **Ferries** | Static monthly sync | TurboJET + CotaiJet timetable pages (scraped) | 1st of month · `update-ferry-schedules.yml` | `fetchedAtUtc` + `effectiveAs` in `ferry-schedules.json` |\n\n**What each mode means**\n\n- **Simulated** — Vehicles are placed on pre-generated polylines and moved by the client clock using the published timetable. They don't reflect any single bus or train's actual position at that moment; they show \"what the schedule says should be moving through this segment right now.\"\n- **Live (RT mode)** — The client polls DSAT's realtime endpoint (a batch fan-out proxied through nginx with an 8 s shared cache). DSAT itself only publishes current-stop, direction, and speed per plate — not continuous GPS — so the client interpolates between consecutive stop reports. RT mode is opt-in via the control-panel toggle; when off, buses fall back to the simulated timetable.\n- **Static sync** — A scheduled GitHub Actions job fetches upstream data and commits a new `public\u002Fdata\u002F*.json` if it changed. The app reads whatever was in the last build; there is no per-page-load fetch for flights or ferries.\n\n## Project Structure\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>File tree\u003C\u002Fstrong>\u003C\u002Fsummary>\n\n```\nmini-macau\u002F\n├── src\u002F\n│   ├── components\u002F\n│   │   ├── MapView.tsx           # Main map + hamburger menu\n│   │   ├── ControlPanel.tsx      # Playback speed controls\n│   │   ├── TimeDisplay.tsx       # Clock + DateTimePicker trigger\n│   │   ├── DateTimePicker.tsx    # Date\u002Ftime selection overlay\n│   │   ├── LineLegend.tsx        # LRT\u002FBus\u002FFlight legend (desktop + mobile)\n│   │   ├── VehicleInfoPanel.tsx  # Vehicle detail + ETA\n│   │   ├── StationInfoPanel.tsx  # Station detail + next arrivals\n│   │   ├── FlightInfoPanel.tsx   # Flight detail panel\n│   │   └── FerryInfoPanel.tsx    # Ferry detail panel\n│   ├── engines\u002F\n│   │   └── simulationEngine.ts   # Timetable-driven vehicle + flight position computation\n│   ├── hooks\u002F\n│   │   ├── useSimulationClock.ts # RAF-based clock with speed\u002Fpause\n│   │   └── useTransitData.ts     # JSON data loader\n│   ├── layers\u002F\n│   │   ├── Bus3DLayer.ts         # 3D bus model (fill-extrusion)\n│   │   ├── LRT3DLayer.ts         # 3D LRT model (fill-extrusion)\n│   │   ├── Flight3DLayer.ts      # 3D airplane model (fill-extrusion)\n│   │   ├── Ferry3DLayer.ts       # 3D jetfoil model (fill-extrusion, 8 layers)\n│   │   └── VehicleLayer.ts       # 2D vehicle circles + labels\n│   ├── App.tsx                   # Root layout + state management\n│   ├── main.tsx                  # React entry point with I18nProvider\n│   ├── routeGroups.ts            # Bus route grouping logic\n│   ├── i18n.tsx                  # Internationalization (EN \u002F 繁中 \u002F PT)\n│   ├── types.ts                  # TypeScript interfaces\n│   └── index.css                 # Tailwind + MapLibre control overrides\n├── public\u002F\n│   ├── data\u002F\n│   │   ├── lrt-lines.json\n│   │   ├── stations.json\n│   │   ├── trips.json\n│   │   ├── bus-routes.json\n│   │   ├── bus-stops.json\n│   │   ├── flights.json          # MFM flight schedules (with localized names)\n│   │   └── ferry-schedules.json  # TurboJET + CotaiJet monthly timetables\n│   ├── favicon.svg\n│   ├── icons.svg\n│   ├── og-image.png\n│   ├── sitemap.xml\n│   └── robots.txt\n├── data\u002F\n│   ├── scripts\u002F\n│   │   ├── extract_lrt_osm.py\n│   │   ├── extract_bus_data.py\n│   │   ├── fetch_bus_data.py\n│   │   ├── fetch_bridge_geometry.py\n│   │   ├── fetch_flights.py      # AviationStack flight data sync (MFM)\n│   │   ├── fetch_ferry_schedules.py # TurboJET + CotaiJet monthly scraper\n│   │   ├── osrm_route.py\n│   │   ├── patch_bus_bridges.py\n│   │   └── generate_timetable.py\n│   ├── bus_reference\u002F\n│   ├── output\u002F\n│   └── main.py\n├── .github\u002Fworkflows\u002F\n│   ├── deploy.yml                  # Cloudflare Pages CI\u002FCD\n│   ├── docker-release.yml          # Docker image release on new tag\n│   ├── service-status.yml          # Upstream service availability check\n│   ├── update-flights.yml          # Daily flight data update\n│   └── update-ferry-schedules.yml  # Monthly ferry data update\n└── index.html\n```\n\n\u003C\u002Fdetails>\n\n## Performance Notes\n\nSimulating 300–400 moving vehicles at 20 Hz while MapLibre re-draws 3D extrusions every frame puts real pressure on the main thread. A few optimizations worth calling out:\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>Polyline progress lookup — \u003Ccode>cumKm\u003C\u002Fcode> + binary search\u003C\u002Fstrong>\u003C\u002Fsummary>\n\nThe simulation asks the same question once per vehicle per tick: *given a route and a progress ∈ [0, 1], where on the polyline is the vehicle, and which way is it facing?*\n\nThe original implementation used Turf's [`along`](https:\u002F\u002Fturfjs.org\u002Fdocs\u002Fapi\u002Falong) twice per vehicle (once for position, once for a 1-metre-ahead lookahead to derive bearing). `along` walks the coordinate array from index 0 and sums haversine distances until it reaches the target km — **O(n) haversines per call**. At ~400 vehicles × 2 calls × 20 Hz × 100-point routes, that worked out to roughly **12 000 full-route scans per second**, all on the main thread.\n\nKey observation: each route's geometry is immutable, so the per-segment work only needs to happen once. On first touch we cache:\n\n- `cumKm[i]` — cumulative kilometres from `coords[0]` to `coords[i]` (`Float64Array`)\n- `segBearing[i]` — heading of segment `coords[i] → coords[i+1]` (`Float64Array`)\n\nPer-call cost then collapses to a binary search on `cumKm` (≈ 8 comparisons for a 150-point route), a linear interpolation between two lat\u002Flng pairs, and a table lookup for bearing. No trig in the hot loop, and no second `along` call since the segment index already tells us the heading.\n\nWe deliberately don't cache a per-line \"last index\" hint: multiple vehicles share the same polyline at different progress values, so a shared hint would thrash. `O(log n)` is cheap enough that per-vehicle state isn't worth it. See [`simulationEngine.ts`](src\u002Fengines\u002FsimulationEngine.ts) (`getLineCache` \u002F `interpolateOnLine`).\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>One bus-routes source instead of 92\u003C\u002Fstrong>\u003C\u002Fsummary>\n\nMapLibre GeoJSON sources are **tiled in a web worker**: the worker clips each source's features to tile boundaries, tessellates lines into triangle strips, and ships vertex buffers back to the main thread. Originally each of the 92 bus routes was its own `addSource` + `addLayer`, meaning every zoom level change forced 92 separate `postMessage` round-trips and 92 independent tile-index rebuilds.\n\nConsolidating into a single `bus-routes` source (one tile index, one round-trip per reindex) drastically cut worker chatter during zoom. Per-route dimming — previously `setPaintProperty('bus-route-${id}', 'line-opacity', …)` against 92 layers — became `setFeatureState({ source: 'bus-routes', id }, { inService })` on one layer, with opacity driven by a `['case', ['==', ['feature-state', 'inService'], false], DIM, FULL]` paint expression. `setFeatureState` doesn't recompile paint; `setPaintProperty` does.\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>Two-tier animation throttle\u003C\u002Fstrong>\u003C\u002Fsummary>\n\nMoving 300+ buses as 3D fill-extrusion polygons is heavy (each bus is 8 quads × lat\u002Flng math). Moving them as 2D circles is almost free (just a `setData` on a Point FeatureCollection).\n\nThe animate loop splits them: simulation + 2D circle updates run every 50 ms unconditionally, while 3D polygon rebuilds throttle to 160 ms whenever the map is actively moving (`movestart` \u002F `moveend` set a `mapBusy` flag). During zoom gestures the 2D layer keeps vehicles visibly moving at full cadence while the expensive 3D rebuild backs off, leaving MapLibre's own render pipeline more time to finish zoom frames.\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>Decouple zoom display from React re-renders\u003C\u002Fstrong>\u003C\u002Fsummary>\n\nThe zoom indicator in the HUD used to be a `useState`, so every `map.on('zoom', …)` event caused `\u003CMapView>` to re-render — which is a *huge* component with map refs, ETA panels, and layer toggles. Now zoom lives in an external store read via [`useSyncExternalStore`](https:\u002F\u002Freact.dev\u002Freference\u002Freact\u002FuseSyncExternalStore), and only a tiny `\u003CZoomText>` leaf subscribes. The rest of `\u003CMapView>` stays stable during pinch\u002Fscroll zoom.\n\n\u003C\u002Fdetails>\n\n## Acknowledgements\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>Inspiration\u003C\u002Fstrong>\u003C\u002Fsummary>\n\n- [Mini Tokyo 3D](https:\u002F\u002Fgithub.com\u002Fnagix\u002Fmini-tokyo-3d) — Original inspiration for the concept\n- [Mini Taiwan](https:\u002F\u002Fmini-taiwan-learning-project.itsmigu.com\u002F) — Sister project inspiration\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>Data sources\u003C\u002Fstrong>\u003C\u002Fsummary>\n\n- [OpenStreetMap](https:\u002F\u002Fwww.openstreetmap.org\u002F) — LRT track geometry, bus routes, and stop locations\n- [MLM 澳門輕軌股份有限公司](https:\u002F\u002Fwww.mlm.com.mo\u002F) — Official per-station LRT timetables (used to hand-transcribe `data\u002Fscripts\u002Fgenerate_timetable.py` for the Taipa \u002F Seac Pai Van \u002F Hengqin lines)\n- [MoTransport Info](https:\u002F\u002Fmotransportinfo.com\u002Fzh\u002Fsearch) — Curated Macau bus stop reference data\n- [DSAT 巴士資訊](https:\u002F\u002Fbis.dsat.gov.mo\u002Fmacauweb\u002Findex.html?language=zh-tw&fromDzzp=false) — Official Macau bus realtime feed (live bus positions in RT mode)\n- [AviationStack](https:\u002F\u002Faviationstack.com\u002F) — MFM flight schedule data (arrivals + departures)\n- [TurboJET](https:\u002F\u002Fwww2.turbojet.com.hk\u002F) — Ferry timetable (Hong Kong, HKIA, Shenzhen Airport, Shekou routes)\n- [CotaiJet](https:\u002F\u002Fwww.cotaiwaterjet.com\u002F) — Ferry timetable (Hong Kong ↔ Macau Taipa route)\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cstrong>Libraries, tiles, and fonts\u003C\u002Fstrong>\u003C\u002Fsummary>\n\n- [MapLibre GL JS](https:\u002F\u002Fmaplibre.org\u002F) — Open-source map rendering\n- [CARTO](https:\u002F\u002Fcarto.com\u002F) — Basemap tiles (Dark Matter \u002F Positron)\n- [OpenFreeMap](https:\u002F\u002Fopenfreemap.org\u002F) — 3D building tiles\n- [OSRM](http:\u002F\u002Fproject-osrm.org\u002F) — Road routing engine\n- [Turf.js](https:\u002F\u002Fturfjs.org\u002F) — Geospatial analysis\n- [Google Fonts](https:\u002F\u002Ffonts.google.com\u002Fspecimen\u002FOrbitron) — Orbitron, JetBrains Mono, Noto Sans HK\n\n\u003C\u002Fdetails>\n\n## License\n\n[MIT](.\u002FLICENSE)\n","Mini Map Macau 是一个用于展示澳门公共交通和航空系统的三维可视化项目，包括轻轨、公交、渡轮以及澳门国际机场航班，并通过时间表驱动的模拟来展示这些交通工具的实际运行情况。该项目采用React、TypeScript和Vite等现代前端技术栈构建，结合MapLibre GL进行3D渲染，支持中英葡三语界面。特别适合城市规划者、交通研究人员及对澳门交通系统感兴趣的公众使用，以直观地了解澳门复杂的多模式交通网络如何运作。此外，用户还可以选择启用实时数据模式，使部分公交位置更新基于实际DSAT提供的即时信息，增强了应用的实用性和互动性。",2,"2026-06-11 02:47:36","CREATED_QUERY"]