trips/router/internal/route/route.go
Greg Pomerantz efde2cc71b maps project: design, survey, mock app, and route-aware planning backend
- DESIGN.md: full design incl. driving-trip requirements (R1-R4),
  stays model, focus mode, mobile, provenance rules
- SURVEY.md: open-source landscape
- mock/: interaction mock (Florence itinerary, focus mode, stays,
  region stops, mobile layout)
- router/: Go module (stdlib-only) with Router interface
  (Valhalla + OSRM backends), stop_cost, optimize_stops, corridor,
  routectl CLI, bench (5 real NE-corridor tasks, 26 checks passing),
  integration tests, and setup-osrm.sh for the self-hosted router
- osm/: NH+MA+CT+NY PBFs (gitignored) + setup artifacts
2026-09-06 00:05:17 -04:00

127 lines
3.5 KiB
Go

// Package route defines the Router interface and the two backends:
// a hosted Valhalla (works today, no geometry) and local OSRM
// (full geometry, self-hosted — see scripts/setup-osrm.sh).
//
// Design rule (DESIGN.md R1/R4): the route is computed, never narrated.
// Every Duration/Distance value returned here is `computed` provenance.
package route
import (
"context"
"math"
"time"
"maps/router/internal/geo"
)
// Profile is the routing profile.
type Profile string
const (
ProfileDrive Profile = "drive"
ProfileWalk Profile = "walk"
)
// Route is the result of routing through an ordered set of points.
type Route struct {
// Duration is total travel time in seconds (computed by the router).
Duration float64
// Distance is total path length in meters.
Distance float64
// BBox is the bounding box of the routed path (from the router's
// summary). Zero values if the backend doesn't provide one.
BBox BBox
// Geometry is the routed polyline. May be nil for backends that
// don't return it (hosted Valhalla) — callers must degrade
// gracefully (chord corridor, flagged low-confidence).
Geometry []geo.Point
// Legs holds per-segment durations for multi-point routes.
// len(Legs) == len(points)-1 when present.
Legs []Leg
}
// Leg is one segment between consecutive waypoints.
type Leg struct {
Duration float64 // seconds
Distance float64 // meters
}
// BBox is a latitude/longitude bounding box in degrees.
type BBox struct {
MinLat, MaxLat, MinLon, MaxLon float64
}
// Empty reports whether the box is unset.
func (b BBox) Empty() bool { return b == BBox{} }
// DistMeters is the minimum distance from p to the box (0 if inside).
func (b BBox) DistMeters(p geo.Point) float64 {
dLat, dLon := 0.0, 0.0
if p.Lat < b.MinLat {
dLat = b.MinLat - p.Lat
} else if p.Lat > b.MaxLat {
dLat = p.Lat - b.MaxLat
}
if p.Lon < b.MinLon {
dLon = b.MinLon - p.Lon
} else if p.Lon > b.MaxLon {
dLon = p.Lon - b.MaxLon
}
if dLat == 0 && dLon == 0 {
return 0
}
mLat := dLat * 111190.0
mLon := dLon * 111190.0 * math.Cos(rad(p.Lat))
return math.Hypot(mLat, mLon)
}
func rad(d float64) float64 { return d * math.Pi / 180 }
// Router is the backend interface.
type Router interface {
// Route routes through pts in order and returns the combined trip.
Route(ctx context.Context, profile Profile, pts []geo.Point) (*Route, error)
// Name identifies the backend in reports ("valhalla-hosted", "osrm-local").
Name() string
}
// Matrix builds a full origin-destination matrix over pts using the
// router. m[i][j] is seconds; -1 means unroutable. Built with pairwise
// two-point Route calls — the single code path that works on both
// backends (Valhalla's sources_to_targets is capped at 150 km).
type Matrix [][]float64
func (m Matrix) From(i, j int) float64 { return m[i][j] }
// BuildMatrix computes the matrix with a small politeness delay between
// calls when polite > 0 (hosted endpoints).
func BuildMatrix(ctx context.Context, r Router, profile Profile, pts []geo.Point, polite time.Duration) (Matrix, error) {
n := len(pts)
m := make(Matrix, n)
for i := range m {
m[i] = make([]float64, n)
m[i][i] = 0
}
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if i == j {
continue
}
if polite > 0 {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(polite):
}
}
rr, err := r.Route(ctx, profile, []geo.Point{pts[i], pts[j]})
if err != nil {
m[i][j] = -1
continue
}
m[i][j] = rr.Duration
}
}
return m, nil
}