Overview
The projection converts GPS coordinates to image pixel positions using:- Lat/lng → meters - Haversine approximation for local NYC area
- Rotation - Camera azimuth angle (-15°)
- Elevation projection - Camera elevation angle (-45°)
- Pixel scaling - Meters per pixel ratio (0.293 m/px)
- Quadrant conversion - 512px quadrants to match tile system
Generation Config
The isometric map was generated with these parameters (fromgeneration_config.json):
123904 × 100864 px = 242 × 197 quadrants at 512px each
Calibration
The projection was calibrated using 15 ground-truth points across all 5 NYC boroughs plus New Jersey. Each point was:- Clicked on the map using OpenSeadragon’s
window.__osddebug utility - Tagged with precise lat/lng from Google Maps
- Used in a least-squares solver to determine the seed pixel position
Calibration Results
(-87, -84) quadrants → (44544, 43008) pixels.
Accuracy:
- RMS residual: 28px (~8 meters) across full NYC metro area
- Max error: 63px (~18 meters)
- Projection: Isotropic - meters per pixel X ≈ meters per pixel Y ≈ 0.293 m/px
Projection Formula
ThelatlngToQuadrantCoords() function implements the exact algorithm from isometric-nyc/generation/shared.py:
Step-by-Step Breakdown
Image Pixel Conversion
The public APIlatlngToImagePx() adds the calibrated seed pixel offset:
Example
Empire State Building:(40.7484, -73.9857) (the seed point)
(40.7580, -73.9855)
OpenSeadragon Viewport Coordinates
Placing Markers
TheplaceMarkers() function converts lat/lng to viewport coordinates and adds overlays:
Bounds Checking
Not all NYC coordinates fall within the rendered map area. We filter out markers outside the image bounds:- Western New Jersey - west of the map edge
- Far Rockaway - south of the map edge
- Northern Bronx - potentially north of the map edge
Accuracy Validation
We validated the projection accuracy by comparing:- Manual markers - Clicked locations on the map
- Google Maps coords - Precise lat/lng for the same landmarks
- Projected positions - Using
latlngToImagePx()
Sample Validation Points
| Location | Manual Pixel | Projected Pixel | Error (px) |
|---|---|---|---|
| Empire State Building | (45059, 43479) | (45059, 43479) | 0 (seed) |
| Times Square | (45240, 43070) | (45237, 43064) | 6.7 |
| Brooklyn Bridge | (46120, 44580) | (46095, 44552) | 33.5 |
| Yankee Stadium | (44820, 40950) | (44785, 40924) | 41.4 |
| Staten Island Ferry | (45980, 46210) | (45945, 46173) | 50.3 |
Mathematical Derivation
The projection formula is derived from the inverse of the image generation process:- World space - Real-world GPS coordinates in meters
- Camera rotation - Rotate horizontal plane by azimuth angle
- Camera projection - Apply elevation angle (isometric = -45°)
- Screen space - Convert to 2D pixel canvas
- Parallel lines - Remain parallel (no perspective distortion)
- Isotropy - Equal scaling in both axes
- Angles - Preserved within horizontal plane
Performance Considerations
The projection is fast enough to run on every permit (5,000+ times per render):addOverlay) takes ~0.1ms per marker, making it the bottleneck (which is why we chunk it).
Next Steps
Architecture
Understand how the projection integrates with the app architecture
Data Sources
Learn about the lat/lng data from NYC Open Data