Help us shape the future of Go! Take the 2026 Go Developer Survey

maps

package
v0.40.22 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 19 Imported by: 0

README

internal/maps

The server side of the maps: same-origin proxies and allowlists in front of the self-hosted tile, glyph, routing and terrain services, so a map never asks a third party about a person's drives.

Terrain (hillshade) and its credit

dem.go serves Terrarium terrain tiles from the in-cluster elevation mirror. The web map draws a hillshade from them (web/src/map/style.ts). The terrain dataset requires its sources to be credited wherever the hillshade is drawn:

  • Web: TERRAIN_ATTRIBUTION in web/src/map/style.ts is set on the dem source, so MapLibre shows it with the basemap credit. Do not remove it while the hillshade layer exists (style.test.ts checks it).
  • iPhone: the iPhone map draws no hillshade today. If it ever does, the same credit must ship in the same change, in the map's credit line (MapAttribution, ios/Rivolt/Map/DriveRouteMap.swift) and in Acknowledgements, before that build goes out. The OpenStreetMap and Protomaps credits are already there; the terrain one is separate and is not covered by them.
  • Elevation used for numbers (drive altitude, the Elevation colour mode) comes from the same dataset but draws no layer; its disclosure is the Privacy page's line about the self-hosted Terrarium copy.

Documentation

Overview

Package maps wires same-origin reverse-proxies that let the SPA reach self-hosted map services (Valhalla for routing + map matching, an nginx-served PMTiles bundle for vector basemaps) through the existing rivolt origin and session cookie.

Why proxy instead of CORS-on-the-upstream:

  • Most upstreams (Valhalla, the tile nginx) have no native CORS. nginx can be configured for it, but exposing each upstream behind its own ingress + cert burns hostnames for what is effectively the SPA's own data path.
  • Proxying through the rivolt API keeps drive maps same-origin, reuses the existing auth middleware (routes are mounted inside the requireUser group), and lets us add per-user rate limits later without touching the cluster.
  • The frontend code change shrinks to "swap base URL"; no CORS preflight handling, no second fetch credential mode.

The proxy is intentionally dumb: it forwards every request path unchanged to the upstream. Path-prefix stripping is done at mount time via http.StripPrefix in api.go, so this package only handles host rewrite and credential scrubbing.

Index

Constants

View Source
const (
	DEMMaxZoom       = 12
	DEMClientMinZoom = 3
	DEMClientMaxZoom = 10
)

DEMMaxZoom is the deepest zoom the route admits (the recorder's own mirror holds z=12 for Texas); DEMMinZoom/DEMClientMaxZoom are what the client is told to request (the hillshade needs the coarse levels everywhere and nothing finer than z10).

View Source
const (
	MaxValhallaBody     = 1 << 20
	MaxTraceShapePoints = 2000
	MaxRouteLocations   = 100
)

Ceilings on one Valhalla request, so the proxy serves what the clients draw and can't be used for bulk routing. The web and the iPhone send at most 1500 trace points plus the two parked ends to /trace_route, and the planner's /route carries the origin, the destination and up to 25 charging stops.

View Source
const GlyphMaxAge = "public, max-age=604800"

GlyphMaxAge is how long a glyph file may be cached: the files never change under a URL (a new set would be a new release), and the ETag lets a client revalidate cheaply after that.

View Source
const MaxDEMResponse = 512 << 10

MaxDEMResponse is the most a DEM tile may carry: a Terrarium tile is under 100 KB; a larger answer is the upstream serving something else.

View Source
const MaxTileResponse = 2 << 20

MaxTileResponse is the most a single tile read may carry. Real reads are a header, a directory or one tile (the largest in the 19 GB basemap is 344 KiB); 2 MiB leaves room, and anything bigger is an upstream answering something other than the byte range that was asked for.

Variables

View Source
var (
	GlyphFonts  = []string{"Noto Sans Regular", "Noto Sans Medium", "Noto Sans Italic"}
	GlyphRanges = []string{"0-255", "256-511"}
)

GlyphFonts and GlyphRanges are exactly what is shipped; anything else is a 404.

View Source
var ErrInvalidURL = errors.New("maps: base URL must include scheme and host")

ErrInvalidURL is returned when a configured base URL parses but is missing a scheme or host. Treated as fatal at startup so a typo in RIVOLT_VALHALLA_BASE_URL / RIVOLT_TILES_BASE_URL fails fast instead of silently dropping every drive map back to public CDNs.

View Source
var ErrNoRoute = errors.New("valhalla: no route found")

ErrNoRoute means Valhalla found no road route between the locations (a location in the ocean, or islands with no ferry). It is the caller's input, not a service failure.

Functions

func AllowDEM added in v0.40.16

func AllowDEM(next http.Handler) http.Handler

AllowDEM admits only GET/HEAD of a single tile path, and guards the answer: PNG only, bounded, with no cookies and a private cache header the upstream's own does not override. A tile the mirror does not hold is a 404 (the client draws no terrain there), cached briefly.

func AllowTiles added in v0.37.5

func AllowTiles(next http.Handler) http.Handler

AllowTiles admits only GET/HEAD of a single top-level *.pmtiles file (Range requests are plain GETs). Directory listings, nested paths and any other file on the tile server are refused. Whatever is admitted goes through guardTiles, so a GET can only ever be answered with a bounded byte range.

func AllowValhalla added in v0.37.5

func AllowValhalla(next http.Handler) http.Handler

AllowValhalla admits only what the SPA uses: POST /route and POST /trace_route. Everything else (isochrone, matrix, expansion, status, any GET) is refused before it reaches Valhalla.

func DEMPath added in v0.40.16

func DEMPath(p string) bool

DEMPath reports whether p (the path after /api/maps/dem) names one tile.

func DEMTile added in v0.40.16

func DEMTile(p string) (z, x, y int, ok bool)

DEMTile parses "/z/x/y.png" strictly (no leading zeros, no extra segments or characters, x and y inside the zoom's grid).

func GlyphHandler added in v0.40.16

func GlyphHandler() http.Handler

GlyphHandler serves a glyph range from memory, with the path after /api/maps/glyphs. GET and HEAD only, an exact shipped font and range only, an ETag and a week of caching.

func GlyphPath added in v0.40.16

func GlyphPath(p string) (key string, ok bool)

GlyphPath parses "/{fontstack}/{range}.pbf" for a shipped font and range, and returns the file's key. The font stack is the decoded path segment (a single font; a comma-joined stack is not served).

func GlyphSize added in v0.40.16

func GlyphSize(p string) int

GlyphSize is the size in bytes of the file at a valid glyph path (0 when it is not one).

func LimitValhalla added in v0.38.19

func LimitValhalla(next http.Handler) http.Handler

LimitValhalla refuses a body over MaxValhallaBody (413), a /trace_route with more than MaxTraceShapePoints shape points or a /route with more than MaxRouteLocations locations (413), and anything the counts can't see (400): a query string (Valhalla reads a whole request from ?json=), a body that isn't one JSON object, an encoded_polyline (a polyline string holds points the count can't see), and a top-level key repeated or spelled in another case (Go's decoder and Valhalla's may pick different copies). Our clients send none of these. What passes is handed on with its body intact.

func NewProxy

func NewProxy(baseURL string) (http.Handler, error)

NewProxy builds a reverse-proxy handler that forwards to baseURL. The empty string returns (nil, nil) - callers (api.New) treat nil as "feature off" and skip mounting the route.

baseURL is typically a cluster-internal Service address ("http://valhalla.valhalla.svc.cluster.local", "http://tiles.tiles.svc.cluster.local").

Types

type BadShapeError added in v0.40.19

type BadShapeError struct {
	Kind   string // "offglobe" or "detour": which check refused it (the recorder's refusal counter is labelled by it)
	Reason string
}

BadShapeError is RouteShape's answer when the routing engine's shape cannot be a road between the ends. The recorder counts these apart from a routing engine that is merely down (it recognises the BadShape method, so it needs no import of this package).

func (*BadShapeError) BadShape added in v0.40.19

func (e *BadShapeError) BadShape() bool

func (*BadShapeError) Error added in v0.40.19

func (e *BadShapeError) Error() string

func (*BadShapeError) ShapeKind added in v0.40.19

func (e *BadShapeError) ShapeKind() string

type Route added in v0.37.0

type Route struct {
	Legs []RouteLeg
}

Route is a Valhalla route through an ordered list of locations.

type RouteLeg added in v0.37.0

type RouteLeg struct {
	LengthMeters float64
	TimeSec      float64
	// Shape is the leg's road geometry as (lat, lon) pairs.
	Shape [][2]float64
}

RouteLeg is one leg of a Route: from one location to the next.

type RouteOpts added in v0.37.0

type RouteOpts struct {
	AvoidTolls    bool
	AvoidFerries  bool
	AvoidHighways bool
}

RouteOpts are the routing preferences Route honours.

type Valhalla

type Valhalla struct {
	HTTP    *http.Client
	BaseURL string
}

Valhalla is a thin server-to-server client for the in-cluster Valhalla routing service. It exists alongside the same-origin reverse proxy (NewProxy in this package) — the proxy is for the SPA, this client is for backend code that wants a routing answer directly without going through the user-facing HTTP path.

Today the only consumer is the live recorder's GPS-gap fill: when the WS feed drops fixes for several seconds, we ask Valhalla what road-level path is most likely between the last good fix and the new one, and splice that shape into the drive polyline so the rendered trace doesn't contain a straight-line shortcut across whatever the car actually drove.

func NewValhalla

func NewValhalla(baseURL string) *Valhalla

NewValhalla returns a client pointed at baseURL. Empty baseURL returns nil — callers must nil-check and skip the feature.

func (*Valhalla) Enabled

func (v *Valhalla) Enabled() bool

Enabled reports whether the client is configured.

func (*Valhalla) Route added in v0.37.0

func (v *Valhalla) Route(ctx context.Context, locs [][2]float64, opts RouteOpts) (Route, error)

Route asks Valhalla for the auto route through locs (lat, lon pairs, at least two, every one a stop so each gap is its own leg) in one request, and returns per-leg length, time and shape. It sits beside RouteShape, which stays as it is.

func (*Valhalla) RouteShape

func (v *Valhalla) RouteShape(ctx context.Context, from, to [2]float64) ([][2]float64, error)

RouteShape returns the on-road shape between two points as a sequence of (lat, lon) pairs, derived from a Valhalla /route call with auto costing. Used by the recorder to fill GPS-lag gaps with a plausible road-snapped path. The returned slice INCLUDES the requested endpoints (Valhalla always echoes them as the first/last shape vertex).

The context's deadline bounds the upstream call. Callers should pass a tight timeout (a few hundred ms) — if Valhalla can't answer quickly it's better to fall back to a straight line than to delay the live recorder.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL