Documentation
¶
Overview ¶
Package chargers serves the chargers.pmtiles archive from the rivolt API: the entire ~10 MB file is fetched once at startup (or first request), parsed in-memory, and queries against it run fully server-side. This replaces the SPA's per-tile fan-out over the same archive — for long corridors that was ~hundreds of HTTPS range requests per planner re-render, saturating the browser's per-origin socket pool and feeling sluggish even after the upstream tile server moved to tmpfs.
Wire path: tiles nginx pod serves /chargers.pmtiles → rivolt fetches it once via the in-cluster Service → keeps a *[]byte in memory → answers /api/maps/chargers-along with a flat JSON list.
Index ¶
- Constants
- Variables
- func PointQueryPath(lat, lon, radiusKm float64) [][2]float64
- func ThinPath(path [][2]float64, max int) [][2]float64
- type Archive
- func (a *Archive) LoadedAt() time.Time
- func (a *Archive) Nearest(lat, lon, maxM float64, filter Filter) (POI, bool, error)
- func (a *Archive) QueryCorridor(path [][2]float64, filter Filter, opts QueryCorridorOptions) ([]POI, error)
- func (a *Archive) Refresh(ctx context.Context) (changed bool, err error)
- func (a *Archive) Reload(ctx context.Context) error
- func (a *Archive) Within(lat, lon, maxM float64, filter Filter) ([]NearPOI, error)
- type Filter
- type NearPOI
- type POI
- type QueryCorridorOptions
Constants ¶
const ( // MaxPathPoints is the most route points a scan walks; a longer polyline (up to MaxRoutePoints) is thinned to it. MaxPathPoints = 5000 // MaxCorridorKm is the widest search band accepted (half-width). MaxCorridorKm = 50.0 )
Limits on a corridor query, so one request cannot ask for a continent of tiles.
const MaxRoutePoints = 200_000
MaxRoutePoints is the longest route polyline an API caller may send. A long drive's polyline has tens of thousands of points (a 1,300 km route is about 25,000), far over MaxPathPoints; the handler thins it first.
Variables ¶
var ( // ErrBadPath is a path with a coordinate that is not a number. ErrBadPath = errors.New("chargers: path has a coordinate that is not a number") // ErrQueryTooLarge is a path with too many points or spanning too many tiles. ErrQueryTooLarge = errors.New("chargers: query is too large") )
Functions ¶
func PointQueryPath ¶ added in v0.40.0
PointQueryPath is the path to give QueryCorridor to ask for everything within radiusKm of a point (use CorridorKm = radiusKm, and filter the answer by distance if the exact circle matters). QueryCorridor is a band along a path and drops a station whose nearest point on the path is an end of it, so a tiny path around a point, or the point twice, finds nothing at all: only a station in a thin strip beside it would pass. A north-south segment through the point, longer than the radius on each side, keeps every station within the radius inside the band.
func ThinPath ¶ added in v0.39.2
ThinPath returns path with points dropped so that at most max remain, keeping the first and the last and spacing the rest evenly along the route, so the shape follows the road to within about the spacing (a few hundred metres on a continental route, against a corridor of kilometres). A path that is already short enough, or has a coordinate that is not a number (QueryCorridor refuses that), is returned as it is.
Types ¶
type Archive ¶
type Archive struct {
// contains filtered or unexported fields
}
Archive holds the entire chargers PMTiles file in memory plus a decoded root directory. Leaf directories are decoded lazily and cached in leafDirs. Safe for concurrent readers; the only writer is Reload.
func New ¶
New returns an empty archive; the caller drives Reload to populate it (so startup wiring can defer the fetch until the URL is known).
func (*Archive) LoadedAt ¶
LoadedAt returns the timestamp of the last successful Reload, or the zero Time if the archive has not yet been loaded.
func (*Archive) Nearest ¶ added in v0.32.0
Nearest returns the closest charger passing filter within maxM meters of (lat, lon). Used to attribute a recorded charging session to the station (and network) it happened at. Searches the point's tile and its eight neighbours, so maxM must stay well under a z14 tile (~2.4 km at the equator). Concurrent-safe.
func (*Archive) QueryCorridor ¶
func (a *Archive) QueryCorridor(path [][2]float64, filter Filter, opts QueryCorridorOptions) ([]POI, error)
QueryCorridor returns all chargers passing `filter` within `corridorKm` of any non-endpoint point on `path`. Concurrent-safe. The query is validated first (see planCorridor); ErrBadPath and ErrQueryTooLarge say why it was refused, and a done opts.Context stops it early with the context's error.
func (*Archive) Refresh ¶ added in v0.38.8
Refresh reloads the archive only when the file behind the URL changed (a conditional GET on the ETag of the loaded copy), so a rebuilt archive is picked up without a restart. changed is false when the server says 304; a server that sends no ETag gets the full file each time (and reports changed). A failed fetch leaves the loaded archive in place.
func (*Archive) Reload ¶
Reload fetches the .pmtiles file from a.url, parses the header and root directory, and atomically swaps the live state. Existing in-flight queries continue to read the previous bytes via their captured RLock; the next query sees the new state.
func (*Archive) Within ¶ added in v0.33.0
Within returns every charger passing filter within maxM meters of (lat, lon), nearest first: the whole site, so a caller can tell a co-located one (a Supercharger beside RAN) from a single network. Same tile search and maxM limit as Nearest. Concurrent-safe.
type POI ¶
type POI struct {
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
Name string `json:"name,omitempty"`
IsDCFC bool `json:"isDCFC"`
IsL2 bool `json:"isL2"`
MaxPowerKW float64 `json:"maxPowerKW,omitempty"`
FacilityType string `json:"facilityType,omitempty"`
EVNetwork string `json:"evNetwork,omitempty"`
EVPricing string `json:"evPricing,omitempty"`
EntityID string `json:"entityID,omitempty"`
DCFCCount int `json:"dcfcCount,omitempty"`
L2Count int `json:"l2Count,omitempty"`
SocketTesla bool `json:"socketTeslaSupercharger,omitempty"`
SocketCCS1 bool `json:"socketType1Combo,omitempty"`
SocketChademo bool `json:"socketChademo,omitempty"`
// SocketJ1772 and SocketNEMA1450 are the AC connectors the listing flags
// (the archive's socket:type1 and socket:nema_1450).
SocketJ1772 bool `json:"socketJ1772,omitempty"`
SocketNEMA1450 bool `json:"socketNema1450,omitempty"`
// The campground fields come from the Recreation.gov (RIDB) step of the
// archive build; zero on every NREL-only station. HookupAmps is the
// highest electric hookup of any campsite, ElectricSites of Sites the
// number with power. AsOf is the export's date ("2026-10-20").
HookupAmps int `json:"hookupAmps,omitempty"`
ElectricSites int `json:"electricSites,omitempty"`
Sites int `json:"sites,omitempty"`
Reservable bool `json:"reservable,omitempty"`
Source string `json:"source,omitempty"`
// RIDBID is the Recreation.gov FacilityID of the campground, set on a
// station that came from, or was merged with, a RIDB record.
RIDBID string `json:"ridbID,omitempty"`
AsOf string `json:"asof,omitempty"`
}
POI is one charging-station feature as the SPA expects it. Fields mirror the SPA's POI type in web/src/lib/poi.ts so the renderer code is unchanged. Names are camelCase on the JSON wire to match the existing TS type.
func (POI) IsCampground ¶ added in v0.38.9
IsCampground reports whether poi is a campground or RV park with a way to plug in: a listed hookup (HookupAmps), a Level 2 stall or a NEMA 14-50.
type QueryCorridorOptions ¶
type QueryCorridorOptions struct {
CorridorKm float64
EndpointTrimMeter float64
MinPowerKW float64
// If true, includes destinations that project onto the route's
// endpoint segment. Off by default (matches SPA behaviour).
IncludeEndpoints bool
// Context, when set, ends the query early once it is done (a client that
// went away, a request timeout).
Context context.Context
}
QueryCorridorOptions tunes the corridor scan; zero values pick the SPA defaults.