Repository navigation
ADR 0054 Aerial Photo Under The Map
Accepted (2026-10-27)
The mower map draws areas, no-go zones, paths, the mower and the heat layers in the mower's local metres, anchored on the earth by the map's GeoReference (origin, rotation; ADR 0023). Without a photo the owner has to imagine the garden behind the outlines; with it, missed spots and obstacles sit on the real lawn.
Forces:
- Photos come from a third party. A browser that loads them reveals the viewer's address and the garden's location to that party and needs a wider Content Security Policy.
- A tile route must not become an open proxy to a third-party service.
- The server-side map snapshot planned for the large map widget needs the same photo without a browser.
- Self-hosters live in other places and use other sources; the code must not send requests anywhere unless the operator asks for it.
Source research (verified 2026-10-27): the Bayerische Vermessungsverwaltung publishes the digital orthophoto at 40 cm as open data, WMS https://geoservices.bayern.de/od/wms/dop/v1/dop40 (WMS 1.3.0, layers by_dop40c colour and by_dop40g grey, CRS include EPSG:3857, 4326, 25832; limit 6000 x 6000 px; formats image/jpeg, image/png). The capabilities document states access constraints "CC BY 4.0" and fees "kostenfrei"; the terms of use (geodaten.bayern.de/odd/m/3/html/nutzungsbedingungen.html) require the credit "Bayerische Vermessungsverwaltung - www.geodaten.bayern.de", a link to the licence and a note when changes were made.
Off unless configured. Telemetry reads MOWDECK_TELEMETRY_AERIAL_* (Compose AERIAL_*, Helm config.AERIAL_*): a WMS base URL with layer (EPSG:3857 GetMap, version 1.3.0 or 1.1.1, image/jpeg or image/png) or an XYZ template, the credit line, zoom range, cache limits and a per-account rate. Empty (the default) means no aerial layer, no toggle in the app and no request to any third party. Sources must be https (a test flag allows http for the fake source of the test stack).
A tile proxy in Telemetry, because it already holds the map snapshot and the per-mower authorisation:
-
GET /api/v1/telemetry/mowers/{id}/aerialtells the client whether a photo is offered, the credit line, zoom range, the box where tiles are served and the tile URL template. -
GET /api/v1/telemetry/mowers/{id}/aerial/{z}/{x}/{y}returns one Web Mercator tile. It needscan_viewon the mower (as every mower route) and serves a tile only when its zoom is in range and its box overlaps the bounding box of everything on that mower's map plus a margin (60 m). Anything else is404 tile-outside-map: the route cannot be used to browse the source. - A missing tile is requested once from the source for exactly the tile's EPSG:3857 box (
BBOX= the tile, 256 x 256 px), so no client side reprojection exists and the photo lines up with the map's projection (Leaflet in EPSG:3857, local metres converted with the same equirectangular formula asto_wgs84). Answers must be PNG or JPEG up to 2 MB (a WMS error document with status 200 is refused); redirects are not followed. - Tiles are cached on a volume as files named by the hash of the upstream URL: TTL 30 days, size limit 256 MB (oldest deleted first), unwritable directory means no caching. Fetches of tiles not in the cache are limited per account (240 per minute,
429withRetry-After) and run with four in flight at most. - The route is the single producer of tiles (
Aerial.tile(account, mower, z, x, y)), so a server side composite of map and photo (widget, notification image) calls it with the same arguments; no composite is built here.
The browser never contacts the source. The web app loads the tiles from the route above with its bearer token (fetch, blob URLs; an <img> cannot send the header), so the CSP is unchanged (img-src already has blob:, connect-src 'self'). The optional browser side raster tile setting (WEB_MAP_TILES) stays independent.
The app. The mower page gets a toggle "Luftbild" with an opacity slider beside "Maehpfad" in the layer strip, shown only when the installation offers a photo; both are remembered per browser (mowdeck.aerial). With the photo on the map uses the geographic mode (north up, as with raster tiles) and shows the credit line.
Credit. The line is configured by the operator and always shown while the photo is visible. For Bavaria: "(c) Bayerische Vermessungsverwaltung - www.geodaten.bayern.de (CC BY 4.0)". The resampling into tiles is a change of the data; the ops document tells the operator to keep the line with the licence name.
- One more volume for Telemetry (
aerial-cache; an emptyDir in the Helm chart). - With a photo source the source learns the operator's server address and the tile coordinates around the garden, not the users. Documented in the ops guide.
- WMS sources must support EPSG:3857; sources without it are used through an XYZ proxy of the operator's choice.
- The first view of a garden costs some dozens of requests; later views and other users of the map come from the cache.
- Theft tracking reuses the source, the cache and the fetch limits for its base map, but around the tracked positions instead of the garden (ADR 0058, base map).
Mirrored from docs/adr/0054-aerial-photo-under-the-map.md at commit fc91850a2b39cb822032acc284bb1e82241af0a8. Edit the file in the repository, not this wiki page.
Mirrored from the docs/ directory at commit fc91850a2b39cb822032acc284bb1e82241af0a8. Edit the files in the repository, not this wiki.
MowDeck documentation
- Home
- Phases
- Architecture
-
Decisions (ADR)
- Architecture decisions
- ADR 0001: OpenFGA instead of self-built authorization
- ADR 0002: Self-built Identity service in FastAPI instead of Keycloak or Zitadel
- ADR 0003: SvelteKit for the web app
- ADR 0004: Monorepo with a uv workspace
- ADR 0005: AGPL-3.0 for services and apps, Apache-2.0 for libraries
- ADR 0006: PostgreSQL for all persistence, no SQLite
- ADR 0007: NATS JetStream as event bus
- ADR 0008: Traefik as gateway
- ADR 0009: Command and event envelopes, subject naming and de-duplication
- ADR 0010: Python 3.13 for all services and libraries
- ADR 0011: Separate GPL-3.0 package for the PyMammotion adapter
- ADR 0012: Mower control interface and safety rules
- ADR 0013: Device-Gateway registry, sharding and per-mower concurrency
- ADR 0014: Authorization model, tuple ownership and the mowdeck-authz client
- ADR 0015: Tokens, sessions and key rotation
- ADR 0016: Fleet commands and authorization per command kind
- ADR 0017: Telemetry storage and retention
- ADR 0018: Realtime WebSocket protocol
- ADR 0019: Mammotion adapter transports, manual drive and secrets
- ADR 0020: Web app architecture
- ADR 0021: Browser authentication flow
- ADR 0022: Notifications: rules, recipients and delivery
- ADR 0023: Map rendering and projection
- ADR 0024: Scheduler: recurrence, rain pause and command issuing
- ADR 0025: Home Assistant integration as a pure API client
- ADR 0026: Manual drive channel over the Realtime WebSocket
- ADR 0027: Health history and theft alarm
- ADR 0028: Entitlements: plans as data and enforcement
- ADR 0029: Helm chart and release pipeline
- ADR 0030: Mowing patterns as plug-ins and the Planner
- ADR 0031: Billing with a Stripe adapter
- ADR 0032: Live camera over Agora, joined directly by the browser
- ADR 0033: Vision-assisted recovery with human approval
- ADR 0034: Linked cloud accounts and device discovery
- ADR 0035: Server-side camera frames and automatic AI analysis on errors
- ADR 0036: Native app shells with Capacitor, loading the web app from the server
- ADR 0037: Camera panorama calibration in the browser, with the AI supplying points only
- ADR 0038: Dual link per mower and firmware updates
- ADR 0039: Spot mowing by MowDeck-driven paths
- ADR 0040: Link heartbeat for online status
- ADR 0041: Spot runs from the dock: undock, transit and return
- ADR 0042: Drive calibration
- ADR 0043: Turn styles
- ADR 0044: Drive view with camera
- ADR 0045: Camera relay
- ADR 0046: Device settings as capabilities
- ADR 0047: The RTK base station as a Fleet device
- ADR 0048: Maintenance commands
- ADR 0049: Manual driving over the Mammotion cloud
- ADR 0050: Passkeys and the app login hand-off
- ADR 0051: Sign in with GitHub
- ADR 0052: Sign in with Apple
- ADR 0053: Retention and downsampling of the position track
- ADR 0054: Aerial photo under the mower map
- ADR 0055: Deploy maintenance window
- ADR 0056: Camera photo in push notifications for errors and theft alarms
- ADR 0057: Step-up re-authentication for critical actions
- ADR 0058: Theft tracking mode
- ADR 0059: iOS widgets and the mowing Live Activity
- ADR 0060: Apple Watch app
- ADR 0061: Mowing angle rotation
- ADR 0062: Setup tour for new users
- ADR 0063: Matter server (mowers in Apple Home and Google Home)
- ADR 0064: Personal access tokens and the public API documentation
- ADR 0065: Security center and new-login alerts
- ADR 0066: Cloud provider plugins and adapters for more vendors
- ADR 0067: Error code lexicon, provider independent
- ADR 0068: Public demo mode
- ADR 0069: Provider conformance kit and provider package template
- ADR 0070: Virtual zones for mowers without a vendor map
- ADR 0071: Capability matrix generated from the adapters
- ADR 0072: API explorer in the web app, for the public demo
- ADR 0073: Guided tour with coach marks
- ADR 0074: NATS authentication and per-service subject permissions
- ADR 0075: Chat control as an optional assistant add-on
- ADR 0076: Demo sandboxes, one account per visitor
- ADR 0077: Liveness, readiness and routing
- ADR 0078: Batched state ingestion
-
Development
- Getting started
- Service template
- Shared libraries
- Cloud providers: adding a vendor
- Adding a provider: template and conformance kit
- Adding an error lexicon for a vendor
- Design: identity and authorization
- Design: fleet, telemetry, realtime
- Design: web app
- Web app
- Home Assistant integration
- Camera source bridge protocol
- Fuzzing and property-based tests
- GitHub wiki mirror
-
Operations
- Operating the Compose stack
- Operating MowDeck on Kubernetes (Helm)
- Building the Bluetooth bridge (ESPHome on a XIAO ESP32-S3)
- Releases
- Vendor library updates
- Billing operations
- Telemetry: retention and position downsampling
- iPhone and Android apps
- Passkeys
- Sign in with GitHub
- Aerial photo under the map
- Sign in with Apple
- Theft tracking mode
- Matter server
- Chat control (assistant add-on)
- Source code offer (AGPL-3.0 section 13)
- Public demo
- Load testing
- Spikes
-
Guides
- Guides
- ESP32 bridge: purchase and setup
- Signing in with a passkey
- Signing in with GitHub
- Error codes in plain words
- Aerial photo under the map
- Signing in with Apple
- Confirming critical actions
- Your security page
- iPhone widgets and the mowing Live Activity
- Apple Watch
- The setup tour
- Apple Home and Google Home (Matter)
- Chat control
- Worx Landroid, Kress and LandXcape mowers
- Scripting MowDeck
- Capability matrix
- API reference
- Original plan (German)