- 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
70 lines
2.0 KiB
Go
70 lines
2.0 KiB
Go
package plan
|
|
|
|
import (
|
|
"maps/router/internal/geo"
|
|
"maps/router/internal/route"
|
|
)
|
|
|
|
// Corridor is the spatial constraint for "along the route" search
|
|
// (DESIGN.md R2). It is a buffer band around the routed polyline.
|
|
//
|
|
// Quality depends on the router: local OSRM returns full geometry
|
|
// (exact corridor); hosted Valhalla does not, so we fall back to a
|
|
// band around the great-circle chord (coarse, flagged).
|
|
type Corridor struct {
|
|
Polyline []geo.Point // may be nil → chord fallback
|
|
Origin geo.Point
|
|
Dest geo.Point
|
|
BufferM float64 // band half-width
|
|
FromRoute *route.Route
|
|
}
|
|
|
|
// NewCorridor wraps a computed route. BufferM is the corridor half-width.
|
|
func NewCorridor(r *route.Route, bufferM float64) *Corridor {
|
|
c := &Corridor{
|
|
Polyline: r.Geometry,
|
|
BufferM: bufferM,
|
|
FromRoute: r,
|
|
}
|
|
if len(r.Legs) > 0 {
|
|
// endpoints aren't in the Route struct; caller sets them
|
|
}
|
|
return c
|
|
}
|
|
|
|
// SetEndpoints records A and C (needed for the chord fallback).
|
|
func (c *Corridor) SetEndpoints(a, dst geo.Point) {
|
|
c.Origin = a
|
|
c.Dest = dst
|
|
}
|
|
|
|
// Exact reports whether the corridor is built on the true route
|
|
// geometry (vs the chord fallback).
|
|
func (c *Corridor) Exact() bool { return len(c.Polyline) >= 2 }
|
|
|
|
// Contains reports whether p lies within the corridor band.
|
|
func (c *Corridor) Contains(p geo.Point) bool {
|
|
if c.Exact() {
|
|
return geo.DistToPolylineMeters(p, c.Polyline) <= c.BufferM
|
|
}
|
|
// Chord fallback: distance to the A→C great-circle chord.
|
|
return geo.DistToPolylineMeters(p, []geo.Point{c.Origin, c.Dest}) <= c.BufferM
|
|
}
|
|
|
|
// ChordSample returns a sampled chord polyline (for map drawing when
|
|
// the router gave no geometry).
|
|
func (c *Corridor) ChordSample(n int) []geo.Point {
|
|
if c.Exact() {
|
|
return c.Polyline
|
|
}
|
|
poly := make([]geo.Point, 0, n+1)
|
|
for i := 0; i <= n; i++ {
|
|
t := float64(i) / float64(n)
|
|
poly = append(poly, geo.Point{
|
|
Lat: c.Origin.Lat + t*(c.Dest.Lat-c.Origin.Lat),
|
|
Lon: c.Origin.Lon + t*(c.Dest.Lon-c.Origin.Lon),
|
|
})
|
|
}
|
|
return poly
|
|
}
|