Skip to content

Reference

Generated from app/app.js, app/build.py.

Constants in app.js.

Name Default What it does
NEAR_FARM_M 1500 Farther than this from the field (metres), the app says you’re not at the maze.
OUTSIDE_MARGIN_M 20 How far past the field edge (metres) still counts as being in the maze.
MAX_GPS_ACCURACY_M 50 GPS fixes less accurate than this (metres) are ignored.
ARRIVED_M 8 Within this distance of the exit (metres), the app says you’re at the exit.
DEMO_WALK_MPS 1.2 Walking speed (metres per second) of the simulated visitor in demo mode.
DEMO_GPS_NOISE_M 3 How far simulated GPS fixes wander from the true spot in demo mode (metres, one standard deviation).
MAX_OFFSET_M 40 A Fix my spot correction bigger than this (metres) is a mistake, not GPS error, so it’s ignored.
HANDS_OFF_MS 15000 How long (ms) after the user pans or zooms before the map frames them and the exit again.
STORE_KEY 'maze-offset-v3:' + location.pathname Browser storage key for the user’s Fix my spot correction, one per maze.
LOCK_M 8 Keep the current route while GPS stays within this distance of it (metres).
OFF_ROUTE_FIXES 3 Re-route after this many GPS fixes in a row are off the current route.

What visitors see in red when something needs their attention.

Message What it tells the visitor
You’re not at the maze … away
Turn on Precise Location Settings › Privacy & Security › Location Services › Safari Websites › Precise Location
You’re not in the maze … away
Can’t find a path near you Tap Fix my spot, then tap where you are
Location is off Settings › Privacy › Location Services › Safari Websites. Or tap Fix my spot.
Couldn’t load the maze
  • toEN: Converts a longitude/latitude to metres east and north of the maze raster’s origin.
  • toLonLat: Converts metres east/north of the raster origin back to longitude/latitude.
  • imgToGround: Converts a maze raster pixel to ground metres, including the user’s Fix my spot correction.
  • groundToImg: Converts ground metres to a maze raster pixel, including the user’s Fix my spot correction.
  • imgToLonLat: Converts a maze raster pixel to longitude/latitude.
  • lonLatToImg: Converts a longitude/latitude to a maze raster pixel.
  • imageCorners: The maze image’s four corners on the map, for MapLibre’s image source.
  • saveOffset: Saves the Fix my spot correction to browser storage.
  • loadOffset: Restores the Fix my spot correction from browser storage, if there is one.
  • loadGrid: Loads the walkable path grid from assets/walk.png into memory. Each cell holds 0 for corn, or a value that grows with distance from the nearest corn.
  • Heap: A min-heap of grid cells keyed by distance, used by dijkstra() and buildRoute().
  • stepCost: Cost of stepping into a cell. Cells near corn cost more, so routes keep to the middle of paths.
  • dijkstra: Walking distance from one cell to every reachable cell on the path grid. Run once from the exit, so the way out from anywhere is a lookup.
  • snap: Finds the nearest grid cell that has a path to the exit.
  • traceRoute: Walks downhill on the distance-to-exit field from a cell to the exit.
  • pointInPoly: Whether a point is inside a polygon.
  • distToPoly: Distance from a point to a polygon’s edge; 0 if inside.
  • addLayers: Adds the maze image, route line and position dot to the map.
  • drawMarks: Places the EXIT and Entrance labels on the map.
  • fitMaze: Zooms the map to show the whole maze.
  • fitBearing: Which way to turn the map when showing the whole maze: the way the maze’s own map faces, so the app matches the sign at the entrance. If that leaves a wide map small on a tall screen (or a tall one on a wide screen), it turns a further 90 degrees to fill the screen. The compass needle shows where north is, and the compass button turns it north-up.
  • setHud: Sets the status card at the top of the screen.
  • currentImagePos: Where the user is, in raster pixels: from GPS when it’s accurate enough, otherwise the spot they last marked.
  • metersPerScreenPx: How many metres one screen pixel covers at the current zoom.
  • buildRoute: Builds a route from a cell to the exit, with cumulative distances along it.
  • project: Nearest point on a route to a position. Only searches between minAlong and maxAlong, since a maze route often doubles back one row away.
  • routeFor: The route and the user’s spot on it. Keeps the current route through small GPS jitter and re-routes only after OFF_ROUTE_FIXES fixes in a row are off it. @param {{u: number, v: number, kind: string}} pos From currentImagePos().
  • refresh: Redraws everything that depends on position: the dot, the route and the status card. Called on every GPS fix, tap and zoom.
  • frameRoute: Keeps you, the route and the exit on screen, zoomed in as close as they all fit. Reframes at most every few seconds, and not while the user is moving the map themselves.
  • onGps: Handles a new GPS fix from the browser.
  • onGpsError: Handles a location error; shows how to turn location on if it was denied.
  • startGps: Starts watching the phone’s location. The browser asks the user for permission.
  • startCompass: Starts reading the compass so Follow mode can turn the map. iPhone asks for permission.
  • setMode: Switches between north-up and follow (map turns with the phone and stays centred on you).
  • snapRaw: Nearest walkable cell, ignoring whether it reaches the exit. Used before the exit distances exist.
  • startDemo: Demo mode: add ?demo to a maze’s link and a simulated visitor walks out from the middle of the maze, sending GPS fixes with realistic wobble through the same code as real GPS. ?demo=8 walks 8x faster. Used to try a maze from home and to record videos.
  • boot: Starts the app: loads the maze data and path grid, works out distances from the exit, draws the map and starts GPS.

app/build.py turns a map photo and config into the map overlay and path grid.

Builds the georeferenced maze overlay and routing grid from a photo of the maze map.
The sign is warped (homography) onto the real field outline, so every output is a
north-up raster: pixel (x, y) -> east = x * RES, north = -y * RES from ORIGIN.
Usage: python build.py [maze.config.json] (run from the folder that holds the config)
Needs: opencv-python-headless, numpy