trips/router/internal/geo/geo.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

71 lines
1.8 KiB
Go

// Package geo holds the small, dependency-free geodesy used by the
// router/plan layers. Kept tiny on purpose: anything heavier belongs
// in the router backend, not here.
package geo
import "math"
// Point is a lat/lon in degrees.
type Point struct {
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
}
const R = 6371000.0 // meters
func rad(d float64) float64 { return d * math.Pi / 180 }
// Meters is great-circle distance in meters.
func Meters(a, b Point) float64 {
dLat := rad(b.Lat - a.Lat)
dLon := rad(b.Lon - a.Lon)
q := math.Sin(dLat/2)*math.Sin(dLat/2) +
math.Cos(rad(a.Lat))*math.Cos(rad(b.Lat))*math.Sin(dLon/2)*math.Sin(dLon/2)
return 2 * R * math.Asin(math.Sqrt(q))
}
// MinAt walking minutes at the given km/h, rounded up to whole minutes.
func MinAt(meters float64, kmh float64) int {
if meters <= 0 {
return 0
}
return int(math.Ceil(meters / 1000.0 / (kmh / 60.0)))
}
// distToSeg is the planar (equirect, local) distance from p to segment ab, in meters.
// Accurate to <0.1% over the extents we use (tens of km).
func distToSeg(p, a, b Point) float64 {
x := func(q Point) [2]float64 {
lat0 := rad(a.Lat)
return [2]float64{rad(q.Lon) * math.Cos(lat0) * R, rad(q.Lat) * R}
}
px, ax, bx := x(p), x(a), x(b)
dx, dy := bx[0]-ax[0], bx[1]-ax[1]
l2 := dx*dx + dy*dy
t := 0.0
if l2 > 0 {
t = ((px[0]-ax[0])*dx + (px[1]-ax[1])*dy) / l2
if t < 0 {
t = 0
} else if t > 1 {
t = 1
}
}
cx, cy := ax[0]+t*dx, ax[1]+t*dy
return math.Hypot(px[0]-cx, px[1]-cy)
}
// DistToPolylineMeters is the minimum distance from p to any segment of the polyline.
func DistToPolylineMeters(p Point, poly []Point) float64 {
best := float64(1e18)
for i := 0; i+1 < len(poly); i++ {
if d := distToSeg(p, poly[i], poly[i+1]); d < best {
best = d
}
}
if len(poly) == 1 {
best = Meters(p, poly[0])
}
return best
}