maps/docs/SPECS.md
2026-03-30 09:22:16 +02:00

34 KiB

Product Specification: Privacy-First Maps Application

Version: 1.0.0-draft Date: 2026-03-29 Status: Ready for architecture & development review


Table of Contents

  1. Product Vision & Differentiators
  2. Core Features
  3. User Flows & Acceptance Criteria
  4. Non-Functional Requirements
  5. API Contract Outlines
  6. Data Sources & Licensing
  7. Tech Stack Summary

1. Product Vision & Differentiators

1.1 Vision Statement

A fully-featured maps and navigation application that provides the utility users expect from Google Maps while making an absolute guarantee: the application never transmits, stores, or processes any user data outside the user's own device and their own self-hosted backend.

1.2 Privacy-First Philosophy — Concrete Commitments

The term "privacy-first" is defined by the following non-negotiable constraints:

Commitment Implementation
No accounts The app has no sign-up, login, or authentication flow. There is no user identity.
No telemetry Zero analytics SDKs, no crash reporting services, no usage metrics sent anywhere.
No third-party network calls Every network request the app makes goes to the user's own self-hosted backend. The app binary contains no hardcoded URLs to Google, Apple, Facebook, Sentry, Firebase, Mapbox, or any other third-party service.
On-device history Search history, recent routes, and favorites are stored in a local SQLite database on the device. They are never transmitted over the network.
Self-hosted backend Tile serving (Martin), geocoding (Photon), and routing (OSRM) all run on infrastructure the deployer controls. No SaaS dependencies.
Auditable The application is open-source. Any user can verify the above claims by inspecting the codebase and monitoring network traffic.

1.3 Competitive Comparison

Capability Google Maps Apple Maps OsmAnd This App
Account required Yes (for full features) Apple ID No No
Tracks location history Yes (opt-out) Yes (opt-out) No No
Analytics/telemetry Extensive Moderate Minimal None
Third-party API calls N/A (is the third party) Apple services Some (optional) None
Search quality Excellent Good Fair Good (Photon/OSM)
Offline maps Limited Limited Full Full
Self-hostable backend No No Partial Fully
Open-source No No Yes (client) Yes (client + backend)
Routing quality Excellent Good Good (OSRM-based) Good (OSRM)
UI/UX polish Excellent Excellent Fair Target: Good

1.4 Target Users

  • Privacy-conscious individuals who want a usable maps app without surveillance.
  • Organizations (NGOs, journalists, activists) operating in environments where location privacy is critical.
  • Self-hosting enthusiasts who want full control over their infrastructure.
  • Users in regions where Google services are unavailable or undesirable.

2. Core Features

2.1 Map Rendering

Description: The primary map view renders vector tiles served by a self-hosted Martin tile server. Vector tiles are styled client-side, enabling theme switching without re-downloading data.

Requirements:

  • Vector tile rendering using Martin-served Mapbox Vector Tiles (MVT) format.
  • Smooth pan and zoom with 60fps target on mid-range devices (2023+).
  • Pinch-to-zoom, double-tap zoom, rotation, and tilt gestures.
  • Zoom levels 0 (world) through 18 (building-level).
  • Day theme: Light background, high-contrast roads and labels.
  • Night theme: Dark background, reduced brightness, suitable for driving at night. Activates automatically based on device time or manual toggle.
  • Terrain layer: Optional hillshade/contour overlay for hiking and outdoor use.
  • Map attribution displayed per ODbL requirements (persistent "© OpenStreetMap contributors" in corner).
  • Current location indicator (blue dot) with heading indicator when moving.
  • Compass indicator; tap to re-orient north-up.

Tile Caching:

  • On-device tile cache using SQLite (MBTiles format).
  • Cache size configurable, default 500 MB, maximum 2 GB.
  • LRU eviction policy when cache is full.
  • Tiles served from cache when available, network fetch only on cache miss.

2.2 Search / Geocoding

Description: Users search for addresses, place names, and points of interest. Search queries are sent to a self-hosted Photon instance. Recent searches are stored only on-device.

Requirements:

  • Single search bar at top of map view.
  • As-you-type suggestions with debounce (300ms after last keystroke).
  • Results ranked by relevance and proximity to current map viewport center.
  • Each result shows: name, address, category icon, distance from current location.
  • Tapping a result centers the map on that location and shows a place card.
  • Recent searches: Last 50 searches stored in local SQLite. Displayed when search bar is focused and query is empty. User can clear individual items or all history.
  • No search logging on the backend. Photon queries are stateless; the backend does not log query strings.

2.3 Routing / Navigation

Description: Turn-by-turn directions between two or more points, powered by a self-hosted OSRM instance.

Requirements:

  • Profiles: Driving, walking, cycling. Each profile uses a separate OSRM dataset optimized for that mode.
  • Route request flow:
    1. User selects origin (current location or search result) and destination.
    2. App requests route from OSRM.
    3. Up to 3 alternative routes displayed on map with estimated time and distance.
    4. User selects a route; turn-by-turn instruction list is shown.
  • Turn-by-turn display:
    • Next maneuver shown prominently at top of screen (icon + distance + street name).
    • Subsequent maneuver shown in smaller text below.
    • Full instruction list accessible by swiping up.
    • Voice guidance is out of scope for v1.0 (planned for v1.1).
  • Waypoints: User can add up to 5 intermediate stops by long-pressing on the map or searching.
  • Re-routing: If the user deviates more than 50 meters from the active route, the app automatically requests a new route from the current position.
  • Route summary: Total distance, estimated duration, and arrival time.
  • Offline routing: If the user has downloaded the relevant region's OSRM data, routing works without network access (see Section 2.6).

2.4 Points of Interest (POIs)

Description: POI data is sourced from OpenStreetMap and served through the backend. Users can browse POIs on the map and view detail pages.

Requirements:

  • POI categories rendered as icons on the map at appropriate zoom levels (zoom 14+ for most, zoom 12+ for major landmarks).
  • Categories include: Restaurants, cafes, shops, supermarkets, pharmacies, hospitals, fuel stations, parking, ATMs, public transport stops, hotels, tourist attractions, parks.
  • Detail view (shown when tapping a POI marker or search result):
    • Name
    • Category / type
    • Address
    • Opening hours (parsed from OSM opening_hours tag, displayed in human-readable format with current open/closed status)
    • Phone number (if available)
    • Website (if available, opened in external browser)
    • Wheelchair accessibility (if tagged)
    • "Get Directions" button
    • "Save to Favorites" button
  • POIs at the current viewport are fetched from the backend's POI endpoint (see Section 5.5).

2.5 Bookmarks / Favorites

Description: Users can save places to a local favorites list for quick access. No cloud sync.

Requirements:

  • Save any location (POI, search result, or arbitrary map point via long-press) as a favorite.
  • Each favorite stores: name (editable), coordinates, address, optional note, timestamp saved.
  • Favorites list accessible from main menu.
  • Favorites displayed as distinct markers on the map.
  • User can organize favorites into custom groups (e.g., "Work", "Travel", "Restaurants").
  • Import/export favorites as GeoJSON file for manual backup or transfer between devices.
  • All data stored in local SQLite. No network calls involved.

2.6 Offline Maps

Description: Users can download map regions for use without network access. A downloaded region includes vector tiles, OSRM routing data, and POI data.

Requirements:

  • Region selection: User selects a rectangular area on the map, or chooses from a list of predefined regions (cities, states/provinces, countries).

  • Download package contents:

    • Vector tiles for the selected area (zoom levels 0-16).
    • OSRM routing graph for the selected area (driving + walking + cycling).
    • POI data for the selected area (as SQLite database).
  • Storage estimates:

    Region Size Tiles Routing POIs Total
    City (~30km radius) 30-80 MB 15-50 MB 5-20 MB 50-150 MB
    State/Province 200-500 MB 100-300 MB 20-80 MB 320-880 MB
    Country (medium) 1-3 GB 0.5-1.5 GB 50-200 MB 1.5-4.7 GB
  • Download management:

    • Progress indicator with percentage and estimated time remaining.
    • Pause and resume support.
    • Background download (continues when app is backgrounded).
    • List of downloaded regions with size and last-updated date.
    • Update a downloaded region (delta updates where possible).
    • Delete a downloaded region.
  • Offline behavior:

    • When offline, the app uses cached/downloaded tiles, local OSRM data, and local POI database.
    • Search uses a local Photon index bundled with the download (or falls back to coordinate-based lookup from local POI data).
    • A banner indicates "Offline mode" at the top of the screen.

3. User Flows & Acceptance Criteria

3.1 Open App and Browse Map

Precondition: App is installed, location permission granted (optional).

Flow:

  1. User launches the app.
  2. Splash screen displays for no more than the cold start time.
  3. Map renders centered on the user's current location (if permission granted) or on a default location (configurable, default: last viewed location, or Europe center on first launch).
  4. User pans, zooms, rotates, and tilts the map.

Acceptance Criteria:

  • Map is interactive within 2 seconds of launch (cold start).
  • Tiles within the initial viewport load from cache in < 200ms or from network in < 500ms.
  • Pan and zoom maintain 60fps on a mid-range device.
  • No network request is made to any domain other than the configured backend.
  • If location permission is denied, the app still functions with no error—map centers on default location.
  • Map attribution ("© OpenStreetMap contributors") is visible at all times.

3.2 Search for a Place

Precondition: App is open, map is visible.

Flow:

  1. User taps the search bar.
  2. Recent searches appear (if any).
  3. User types a query (e.g., "Vondelpark").
  4. After 300ms of no typing, suggestions appear.
  5. User taps a result.
  6. Map animates to the selected location, a marker appears, and a place card slides up from the bottom.

Acceptance Criteria:

  • Recent searches load from local SQLite in < 50ms.
  • Search suggestions appear within 500ms of the debounce firing.
  • Results are ordered by relevance, with proximity to viewport as a secondary signal.
  • Tapping a result dismisses the keyboard and animates the map smoothly.
  • The query is saved to local search history.
  • The query is NOT logged or stored on the backend.
  • Searching while offline returns results from the local POI database (if region is downloaded).

3.3 Get Directions Between Two Points

Precondition: App is open.

Flow:

  1. User taps "Directions" (either from a place card or from the main menu).
  2. Origin defaults to current location; user can change it via search.
  3. User enters or selects a destination.
  4. User selects a travel profile (driving/walking/cycling).
  5. App displays up to 3 route alternatives on the map with time/distance.
  6. User taps a route to select it.
  7. Turn-by-turn instruction list appears.
  8. User taps "Start" to begin navigation mode.

Acceptance Criteria:

  • Route response returns within 1 second for distances under 100 km.
  • At least 1 route (and up to 3 alternatives) is displayed.
  • Each route shows total distance (km/mi based on locale) and estimated duration.
  • Route line is drawn on the map with clear visual distinction between alternatives.
  • Selected route is visually highlighted; unselected routes are dimmed.
  • Turn-by-turn instructions include maneuver type, street name, and distance to maneuver.
  • Re-routing triggers automatically when user deviates > 50m from the active route.
  • Routing works offline if the region's OSRM data is downloaded.

3.4 Save a Place as Favorite

Precondition: A place card is visible (from search result, POI tap, or long-press on map).

Flow:

  1. User taps the "Save" / bookmark icon on the place card.
  2. A dialog appears with the place name pre-filled (editable), an optional note field, and a group selector (default: "Favorites").
  3. User confirms.
  4. The marker changes to a favorites icon. The place appears in the favorites list.

Acceptance Criteria:

  • Favorite is persisted to local SQLite immediately.
  • No network request is made.
  • Favorite appears in the favorites list and on the map.
  • User can edit the name and note after saving.
  • User can delete a favorite.
  • Favorites survive app restart and device reboot.

3.5 Download Area for Offline Use

Precondition: App is open, device is online.

Flow:

  1. User navigates to Settings > Offline Maps > Download Region.
  2. User either selects a predefined region from a list or drags a selection rectangle on the map.
  3. App shows estimated download size and required storage.
  4. User confirms download.
  5. Progress bar shows download progress. User can background the app.
  6. On completion, a notification appears: "Region X is ready for offline use."

Acceptance Criteria:

  • Estimated size is shown before download begins.
  • Download can be paused and resumed.
  • Download continues when the app is backgrounded.
  • Downloaded region is listed under "Downloaded Regions" with name, size, and date.
  • After download, map tiles for that region load from local storage without network.
  • After download, search within that region works offline.
  • After download, routing within that region works offline.
  • User can delete a downloaded region and storage is reclaimed.

3.6 View POI Details

Precondition: Map is zoomed in enough to show POI icons (zoom level 14+).

Flow:

  1. User taps a POI icon on the map.
  2. A place card slides up from the bottom with the POI name and category.
  3. User swipes the card up to reveal full details: address, opening hours, phone, website, accessibility info.
  4. User taps "Get Directions" or "Save" or dismisses the card.

Acceptance Criteria:

  • POI data loads within 300ms (from cache or network).
  • Opening hours display the current open/closed status with today's hours.
  • Phone number is tappable (opens dialer).
  • Website is tappable (opens external browser — the app itself makes no request to the website).
  • "Get Directions" pre-fills the POI as the destination in the routing flow.
  • POI details work offline if the region is downloaded.

3.7 Share a Location

Precondition: A location is selected (via search, POI tap, or long-press on map).

Flow:

  1. User taps the "Share" icon on the place card.
  2. A share sheet appears with options:
    • Coordinates: Plain text, e.g., "52.3676, 4.9041"
    • geo: URI: geo:52.3676,4.9041
    • OpenStreetMap link: https://www.openstreetmap.org/#map=17/52.3676/4.9041
  3. User selects an option. The system share sheet opens (or content is copied to clipboard).

Acceptance Criteria:

  • Sharing does not make any network request.
  • Shared content does not include any tracking parameters or unique identifiers.
  • Coordinates are in WGS84 (latitude, longitude) with 4 decimal places (11m precision).
  • The system share sheet is used (no custom sharing implementation that phones home).

3.8 Switch Map Theme

Precondition: App is open, map is visible.

Flow:

  1. User taps the layers/theme button on the map.
  2. A panel shows available themes: Day, Night, Terrain.
  3. User selects a theme.
  4. Map re-renders with the new style immediately (no tile re-download needed since vector tiles are styled client-side).

Acceptance Criteria:

  • Theme switch completes in < 500ms (no network request required).
  • Night theme has noticeably reduced brightness suitable for dark environments.
  • Terrain theme shows hillshade/contour overlay.
  • Selected theme persists across app restarts (stored locally).
  • Auto night mode option: switches to night theme based on device clock (sunset/sunrise for current location, or a fixed schedule like 20:00-06:00).

4. Non-Functional Requirements

4.1 Performance

Metric Target Measurement Method
Cold start to interactive map < 2 seconds Time from process start to first rendered frame with interactive tiles, on a mid-range 2023 Android device
Tile load from cache < 200ms Time from tile request to rendered tile, tile present in SQLite cache
Tile load from network < 500ms Time from tile request to rendered tile, tile not cached, backend in same region
Search suggestions < 500ms Time from debounce trigger to suggestions rendered
Route calculation (< 100km) < 1 second Time from request sent to route drawn on map
Route calculation (< 500km) < 3 seconds Time from request sent to route drawn on map
Pan/zoom frame rate 60fps Sustained frame rate during continuous gesture on mid-range device
POI detail load < 300ms Time from tap to full detail card rendered

4.2 Battery

  • Map browsing (active panning/zooming) must not drain battery faster than Google Maps doing the same activity on the same device. Target: within 10% of Google Maps' battery consumption.
  • Background navigation (screen off, GPS active) must use less than 5% battery per hour.
  • When the app is not actively being used and no navigation is in progress, GPS must be fully released (zero location updates).

4.3 Data Usage

  • Vector tiles are approximately 10x smaller than equivalent raster tiles. A typical browsing session (30 minutes, exploring a city) should use < 15 MB of data.
  • Initial app install size: < 30 MB (no bundled tile data; tiles are fetched or downloaded on demand).
  • Tile cache uses LRU eviction. Default limit: 500 MB.

4.4 Offline Storage

Region Type Storage Estimate
Single city (~30km radius) 50-150 MB
State/Province 320-880 MB
Medium-sized country 1.5-4.7 GB

The user must be warned if a download would fill more than 80% of available device storage.

4.5 Privacy — Hard Requirements

These are non-negotiable and must be verified in CI and during code review.

  1. Zero third-party network calls. The app binary must not contain any hardcoded URLs to external services. Every network request goes to the single configured backend URL. CI pipeline must include a static analysis step that scans the compiled binary and dependency tree for third-party URLs.
  2. No analytics or crash reporting SDKs. The dependency tree must not include Firebase, Sentry, Amplitude, Mixpanel, Google Analytics, or any similar library. Enforced via dependency allow-listing in CI.
  3. No device fingerprinting. The app must not read or transmit: IMEI, advertising ID, MAC address, serial number, or any persistent device identifier.
  4. No data collection on the backend. The backend must not log: IP addresses (beyond ephemeral connection-level), query strings, coordinates, or any data that could identify a user or session. Backend access logs must be configured to omit query parameters and client IPs by default.
  5. On-device data encrypted at rest. The local SQLite databases (history, favorites, cache) must be stored in the platform's encrypted storage (Android: EncryptedSharedPreferences / encrypted file system; iOS: Data Protection Complete).
  6. TLS only. All communication between app and backend must use TLS 1.2 or higher. The app must reject plaintext HTTP connections. Certificate pinning is recommended but optional (since the user controls the backend).

4.6 Accessibility

  • All interactive elements must have accessible labels.
  • Minimum touch target: 48x48dp (Android) / 44x44pt (iOS).
  • Support for system-level font scaling (up to 200%).
  • Screen reader support for search results, POI details, and navigation instructions.
  • Sufficient color contrast (WCAG 2.1 AA) in both day and night themes.

4.7 Supported Platforms

Platform Minimum Version
Android API 26 (Android 8.0)
iOS iOS 15.0

5. API Contract Outlines

The mobile app communicates with a single self-hosted backend. The backend is a Rust/Actix-web service that proxies or wraps Martin, Photon, and OSRM.

Base URL: Configured by the user at first launch (e.g., https://maps.example.com).

All endpoints are unauthenticated (no API keys, no tokens, no cookies).

5.1 Tile Serving

Proxied from Martin tile server.

Endpoint: GET /tiles/{layer}/{z}/{x}/{y}.pbf

Path Parameters:

Param Type Description
layer string Tile layer name (e.g., openmaptiles, terrain, hillshade)
z integer Zoom level (0-18)
x integer Tile column
y integer Tile row

Response:

  • 200 OK — Body: Protobuf-encoded Mapbox Vector Tile. Headers: Content-Type: application/x-protobuf, Content-Encoding: gzip, Cache-Control: public, max-age=86400.
  • 404 Not Found — Tile does not exist for the given coordinates.

Style Endpoint: GET /tiles/style.json

Returns a Mapbox GL style JSON document referencing the tile endpoints. The app uses this to configure the renderer.

5.2 Search / Geocoding

Proxied from Photon.

Endpoint: GET /api/search

Query Parameters:

Param Type Required Description
q string Yes Search query
lat float No Latitude for proximity bias
lon float No Longitude for proximity bias
limit integer No Max results (default: 10, max: 20)
lang string No Preferred language for results (ISO 639-1)
bbox string No Bounding box filter: minLon,minLat,maxLon,maxLat

Response (200 OK):

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [4.9041, 52.3676]
      },
      "properties": {
        "osm_id": 12345678,
        "osm_type": "N",
        "name": "Vondelpark",
        "street": "Vondelpark",
        "city": "Amsterdam",
        "state": "North Holland",
        "country": "Netherlands",
        "postcode": "1071 AA",
        "type": "park",
        "extent": [4.8580, 52.3585, 4.8820, 52.3620]
      }
    }
  ]
}

5.3 Reverse Geocoding

Proxied from Photon.

Endpoint: GET /api/reverse

Query Parameters:

Param Type Required Description
lat float Yes Latitude
lon float Yes Longitude
limit integer No Max results (default: 1)
lang string No Preferred language

Response: Same GeoJSON FeatureCollection format as the search endpoint.

5.4 Routing

Proxied from OSRM.

Endpoint: GET /api/route/{profile}/{coordinates}

Path Parameters:

Param Type Description
profile string One of: driving, walking, cycling
coordinates string Semicolon-separated coordinate pairs: {lon},{lat};{lon},{lat}[;...]

Query Parameters:

Param Type Required Description
alternatives integer No Number of alternative routes (default: 0, max: 3)
steps boolean No Include turn-by-turn steps (default: true)
geometries string No Response geometry format: polyline, polyline6, or geojson (default: geojson)
overview string No Geometry detail: full, simplified, or false (default: full)
language string No Language for turn instructions (ISO 639-1)

Response (200 OK):

{
  "code": "Ok",
  "routes": [
    {
      "distance": 12456.7,
      "duration": 1823.4,
      "geometry": {
        "type": "LineString",
        "coordinates": [[4.9041, 52.3676], [4.9100, 52.3700]]
      },
      "legs": [
        {
          "distance": 12456.7,
          "duration": 1823.4,
          "steps": [
            {
              "distance": 234.5,
              "duration": 32.1,
              "geometry": {
                "type": "LineString",
                "coordinates": [[4.9041, 52.3676], [4.9060, 52.3680]]
              },
              "maneuver": {
                "type": "turn",
                "modifier": "left",
                "location": [4.9041, 52.3676],
                "bearing_before": 90,
                "bearing_after": 0,
                "instruction": "Turn left onto Keizersgracht"
              },
              "name": "Keizersgracht",
              "mode": "driving"
            }
          ]
        }
      ]
    }
  ],
  "waypoints": [
    {
      "name": "Vondelstraat",
      "location": [4.9041, 52.3676]
    },
    {
      "name": "Dam Square",
      "location": [4.8952, 52.3732]
    }
  ]
}

5.5 POI Details

Custom endpoint served by the Rust/Actix-web backend. Queries PostGIS for OSM-sourced POI data.

List POIs in Bounding Box:

GET /api/pois

Query Parameters:

Param Type Required Description
bbox string Yes minLon,minLat,maxLon,maxLat
category string No Filter by category (comma-separated, e.g., restaurant,cafe)
limit integer No Max results (default: 100, max: 500)

Response (200 OK):

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [4.9041, 52.3676]
      },
      "properties": {
        "osm_id": 987654321,
        "osm_type": "N",
        "name": "Cafe de Jaren",
        "category": "cafe",
        "address": {
          "street": "Nieuwe Doelenstraat",
          "housenumber": "20",
          "postcode": "1012 CP",
          "city": "Amsterdam"
        },
        "opening_hours": "Mo-Th 09:30-01:00; Fr-Sa 09:30-02:00; Su 10:00-01:00",
        "opening_hours_parsed": {
          "is_open": true,
          "today": "09:30 - 01:00",
          "next_change": "01:00"
        },
        "phone": "+31 20 625 5771",
        "website": "https://www.cafedejaren.nl",
        "wheelchair": "yes"
      }
    }
  ]
}

Single POI Detail:

GET /api/pois/{osm_type}/{osm_id}

Path Parameters:

Param Type Description
osm_type string N (node), W (way), or R (relation)
osm_id integer OpenStreetMap element ID

Response: Single GeoJSON Feature with the same structure as above.

5.6 Offline Data Packages

Custom endpoint for downloading offline regions.

List Available Regions:

GET /api/offline/regions

Response (200 OK):

{
  "regions": [
    {
      "id": "amsterdam",
      "name": "Amsterdam",
      "bbox": [4.7288, 52.2783, 5.0796, 52.4311],
      "size_mb": 95,
      "last_updated": "2026-03-25T00:00:00Z",
      "components": {
        "tiles_mb": 55,
        "routing_mb": 30,
        "pois_mb": 10
      }
    }
  ]
}

Download Region Component:

GET /api/offline/regions/{region_id}/{component}

Path Parameters:

Param Type Description
region_id string Region identifier
component string One of: tiles, routing-driving, routing-walking, routing-cycling, pois

Response:

  • 200 OK — Binary file download. Content-Type: application/octet-stream. Supports Range headers for pause/resume.
  • Accept-Ranges: bytes header included.

5.7 Health Check

GET /api/health

Response (200 OK):

{
  "status": "ok",
  "version": "1.0.0",
  "services": {
    "martin": "ok",
    "photon": "ok",
    "osrm_driving": "ok",
    "osrm_walking": "ok",
    "osrm_cycling": "ok",
    "postgres": "ok"
  }
}

6. Data Sources & Licensing

6.1 OpenStreetMap

  • Use: Base map data for tiles, POIs, addresses.
  • License: Open Data Commons Open Database License (ODbL) 1.0.
  • Obligations:
    • Attribution: "© OpenStreetMap contributors" must be displayed on the map at all times.
    • Share-Alike: If the database is modified and redistributed, the modified database must be released under ODbL.
    • The application's own source code is not affected by ODbL (it is not a "derivative database").
  • Update frequency: Backend imports OSM data weekly via osm2pgsql or imposm3.

6.2 OSRM (Open Source Routing Machine)

  • Use: Route calculation and turn-by-turn navigation.
  • License: BSD 2-Clause License.
  • Obligations: Include copyright notice and license text in documentation / about screen.
  • Deployment: Self-hosted OSRM backend, one instance per profile (driving, walking, cycling). Data preprocessed from OSM PBF files using osrm-extract, osrm-partition, osrm-customize.

6.3 Photon

  • Use: Forward and reverse geocoding (search).
  • License: Apache License 2.0.
  • Obligations: Include copyright notice and license text. State any modifications.
  • Deployment: Self-hosted Photon instance. Data imported from Nominatim database (itself built from OSM data). Updated weekly alongside the OSM data import.

6.4 Martin

  • Use: Vector tile serving.
  • License: Dual-licensed: MIT License and Apache License 2.0.
  • Obligations: Include copyright notice and license text (either MIT or Apache 2.0, at our choice).
  • Deployment: Self-hosted Martin instance connected to PostGIS database containing tile data generated from OSM via openmaptiles tools.

6.5 License Attribution in App

The "About" screen in the app must display:

  • OpenStreetMap attribution and ODbL license summary with link.
  • OSRM copyright and BSD-2 license notice.
  • Photon copyright and Apache 2.0 license notice.
  • Martin copyright and MIT/Apache 2.0 license notice.
  • Any additional open-source libraries used (auto-generated from dependency metadata).

7. Tech Stack Summary

7.1 Mobile App

Component Technology
Framework Flutter (latest stable)
Language Dart
Map renderer maplibre_gl (Flutter plugin for MapLibre GL Native)
Local storage SQLite via sqflite / drift
HTTP client dio (with TLS enforcement, no third-party interceptors)
State management riverpod or bloc (to be decided during architecture)
Tile cache MBTiles in SQLite
Offline routing Embedded OSRM data files with Dart FFI bindings (or pre-calculated route graphs)

7.2 Backend

Component Technology
API gateway / proxy Rust + Actix-web
Tile server Martin (connected to PostGIS)
Geocoding Photon (Java, self-hosted)
Routing OSRM (C++, self-hosted, one instance per profile)
Database PostgreSQL 16+ with PostGIS 3.4+
OSM data import osm2pgsql or imposm3
Tile generation openmaptiles toolchain
Containerization Docker Compose for all services
Reverse proxy / TLS Caddy or nginx (user-configured)

7.3 Infrastructure Requirements (Minimum)

For serving a single country (e.g., Netherlands):

Resource Minimum Recommended
CPU 4 cores 8 cores
RAM 8 GB 16 GB
Disk 50 GB SSD 100 GB SSD
Network 100 Mbps 1 Gbps

For global coverage, significantly more resources are needed (primarily for OSRM preprocessing and Photon index size).


Appendix A: Glossary

Term Definition
MVT Mapbox Vector Tile — a compact binary format for encoding tiled map data as vector geometry
PBF Protocolbuffer Binary Format — the compact binary format used for OSM data extracts and vector tiles
ODbL Open Data Commons Open Database License — the license governing OpenStreetMap data
PostGIS Spatial extension for PostgreSQL enabling geographic queries
MBTiles A specification for storing tiled map data in SQLite databases
OSRM Open Source Routing Machine — a C++ routing engine for shortest paths in road networks
Photon A geocoder built for OpenStreetMap data, powered by Elasticsearch
Martin A PostGIS/MBTiles vector tile server written in Rust
WGS84 World Geodetic System 1984 — the coordinate reference system used by GPS (EPSG:4326)

Appendix B: Open Questions

  1. Voice navigation (v1.1): Use platform TTS APIs (on-device, no network) or bundle an open-source TTS engine? Platform TTS is simpler but quality varies. To be decided before v1.1 planning.
  2. Public transit routing: OSRM does not support public transit. Options include integrating OpenTripPlanner (AGPL — license implications need review) or deferring to a future version.
  3. Map style customization: Should users be able to load custom Mapbox GL styles, or do we provide only the bundled Day/Night/Terrain themes? Custom styles add flexibility but increase support surface.
  4. Multi-backend support: Should the app support configuring multiple backend URLs (e.g., one for tiles, one for routing) or require a single unified backend? Single URL is simpler; multiple URLs enable distributed setups.
  5. Delta updates for offline maps: Implementing true delta updates (only download changed tiles) is complex. v1.0 may use full re-download of components. Delta updates can be a v1.1 optimization.