gods-eye-view/tools/README.md

183 lines
6.9 KiB
Markdown

# Tools
CLI scripts for fetching, rendering, and reprojecting geospatial imagery. All tools read the Google Maps API key from `.env` automatically.
Output files go to `output/` by default (gitignored).
## Prerequisites
- Node.js (via `mise`)
- `sharp` and `puppeteer` (devDependencies — `npm install`)
- Google Maps API key in `.env` as `GOOGLE_MAPS_API_KEY`
- APIs enabled on your Google Cloud project: **Map Tiles API**, **Street View Static API**
---
## sat-ortho.mjs
Stitches satellite tiles from the Map Tiles API into a single ortho image centered on a lat/lon.
```sh
# 2K ortho at zoom 21 (~6.4 cm/pixel, ~132m coverage)
node tools/sat-ortho.mjs --lat 30.266476 --lon -97.73719
# Zoom 20 for wider coverage (~264m), or zoom 22 for max detail (~3.7 cm/pixel)
node tools/sat-ortho.mjs --lat 30.266476 --lon -97.73719 --zoom 20 --size 2048
node tools/sat-ortho.mjs --lat 30.266476 --lon -97.73719 --zoom 22 --size 2048
```
| Option | Default | Description |
|--------|---------|-------------|
| `--lat` | — | Center latitude (required) |
| `--lon` | — | Center longitude (required) |
| `--zoom` | 21 | Tile zoom level (max typically 22) |
| `--size` | 2048 | Output square size in pixels |
Reports GSD, ground coverage, and NW/SE corner coordinates for georeferencing.
---
## streetview-headings.mjs
Fetches 8 Street View static images (one per compass heading) at a location, with optional first-order neighbor traversal.
```sh
# 8 images at the intersection
node tools/streetview-headings.mjs --lat 30.266476 --lon -97.73719
# Include all first-order neighbor locations (auto-deduped)
node tools/streetview-headings.mjs --lat 30.266476 --lon -97.73719 --neighbors
# Custom FOV and pitch
node tools/streetview-headings.mjs --lat 30.266476 --lon -97.73719 --fov 120 --pitch -10
```
| Option | Default | Description |
|--------|---------|-------------|
| `--lat` | — | Latitude (required) |
| `--lon` | — | Longitude (required) |
| `--fov` | 90 | Field of view in degrees (10-120) |
| `--pitch` | 0 | Camera pitch (-90 to 90) |
| `--size` | 640x640 | Image size (max 640 per axis) |
| `--step` | 45 | Heading step in degrees |
| `--neighbors` | off | Also fetch images from nearby Street View locations |
The `--neighbors` flag queries Google's panorama metadata for linked locations, deduplicates them by pano ID via the metadata API, and fetches 8 heading images from each unique neighbor within 15m.
---
## streetview-panorama.mjs
Fetches and stitches a full equirectangular panorama from the Map Tiles API (Street View tiles endpoint).
```sh
# Default zoom 3 → 4096x2048 panorama
node tools/streetview-panorama.mjs --lat 30.266476 --lon -97.73719
# Max resolution (zoom 5 → up to 16384x8192)
node tools/streetview-panorama.mjs --lat 30.266476 --lon -97.73719 --zoom 5
```
| Option | Default | Description |
|--------|---------|-------------|
| `--lat` | — | Latitude (required) |
| `--lon` | — | Longitude (required) |
| `--zoom` | 3 | Tile zoom 0-5 (higher = larger pano) |
| `--radius` | 50 | Search radius in meters for nearest panorama |
Output is a standard equirectangular JPEG suitable as input for `pano-pinhole.mjs`.
---
## pano-pinhole.mjs
Reprojects an equirectangular panorama into pinhole (perspective) camera views. Uses inverse mapping with bilinear interpolation — every output pixel is sampled from the panorama, so there are no holes.
```sh
# Single view: looking west, 90 deg FOV, 1920x1080
node tools/pano-pinhole.mjs --input output/panorama_30.266476_-97.73719.jpg --heading 270 --hfov 90
# All 8 compass headings from one panorama
node tools/pano-pinhole.mjs --input output/panorama_30.266476_-97.73719.jpg --all --hfov 90
# 2K output with narrow FOV
node tools/pano-pinhole.mjs --input output/panorama_30.266476_-97.73719.jpg --heading 180 --hfov 60 --width 2560 --height 1440
# Specify focal length instead of FOV
node tools/pano-pinhole.mjs --input output/panorama_30.266476_-97.73719.jpg --heading 90 --focal 2000
# 12 views at 30-degree steps
node tools/pano-pinhole.mjs --input output/panorama_30.266476_-97.73719.jpg --all --step 30 --hfov 90
```
| Option | Default | Description |
|--------|---------|-------------|
| `--input` | — | Equirectangular panorama JPEG (required) |
| `--heading` | 0 | Compass heading (0=N, 90=E, 180=S, 270=W) |
| `--pitch` | 0 | Pitch in degrees (positive=up) |
| `--roll` | 0 | Roll in degrees (positive=clockwise) |
| `--hfov` | 90 | Horizontal field of view in degrees |
| `--focal` | — | Focal length in pixels (overrides `--hfov`) |
| `--width` | 1920 | Output width |
| `--height` | 1080 | Output height |
| `--all` | off | Render all compass headings (ignores `--heading`) |
| `--step` | 45 | Heading step for `--all` mode |
Reports horizontal, vertical, and diagonal FOV plus equivalent focal length.
---
## cesium-render.mjs
Renders a CesiumJS 3D view to JPEG via headless Chromium with Google Photorealistic 3D tiles. Requires SwiftShader (no GPU).
```sh
# Look-at mode: camera looks at a target point from above
node tools/cesium-render.mjs --lookat-lat 30.266476 --lookat-lon -97.73719 --heading 180 --pitch -30 --height 25
# Top-down view at 80m
node tools/cesium-render.mjs --lookat-lat 30.266476 --lookat-lon -97.73719 --pitch -90 --height 80 --fov 90
# 2K output
node tools/cesium-render.mjs --lookat-lat 30.266476 --lookat-lon -97.73719 --heading 180 --pitch -30 --height 25 --width 2560 --height-px 1440
# Direct mode: camera placed at coordinates
node tools/cesium-render.mjs --lat 30.266476 --lon -97.73719 --heading 270 --pitch -15 --height 8
```
| Option | Default | Description |
|--------|---------|-------------|
| `--lat/--lon` | — | Camera position (direct mode) |
| `--lookat-lat/--lookat-lon` | — | Target to look at (lookat mode) |
| `--heading` | 0 | Compass heading |
| `--pitch` | -10 | Camera pitch (must be negative in lookat mode) |
| `--height` | 8 | Meters above ground |
| `--fov` | 60 | Vertical field of view |
| `--width` | 1280 | Image width |
| `--height-px` | 720 | Image height |
| `--sse` | 2 | Screen-space error (lower = sharper, slower) |
| `--timeout` | 30 | Max wait in seconds |
Uses progressive SSE refinement and automatic ground-height sampling. Street-level views are limited by Google's photogrammetry tile resolution.
---
## Typical Workflow
```sh
# 1. Get a satellite ortho of an intersection
node tools/sat-ortho.mjs --lat 30.266476 --lon -97.73719 --zoom 21
# 2. Fetch a 4K panorama at the same location
node tools/streetview-panorama.mjs --lat 30.266476 --lon -97.73719
# 3. Extract 8 pinhole views from the panorama
node tools/pano-pinhole.mjs --input output/panorama_30.266476_-97.73719.jpg --all --hfov 90
# 4. Also grab the Street View static API versions for comparison
node tools/streetview-headings.mjs --lat 30.266476 --lon -97.73719
# 5. Render a 3D view of the same location from above
node tools/cesium-render.mjs --lookat-lat 30.266476 --lookat-lon -97.73719 --pitch -90 --height 50 --fov 90 --width 2560 --height-px 1440
```