From b4817546dfb95729465f19641b99050463aa7f32 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 18 Feb 2026 04:48:12 +0000 Subject: [PATCH] Show routing demonstration on radar page. --- html/static/js/map-routing.js | 274 ++++++++++++++++++++++++------- tomtom/.air.toml | 2 +- tomtom/example/route-gps/main.go | 31 ++++ tomtom/example/route/main.go | 52 ------ tomtom/geocode.go | 99 +++++++++++ tomtom/geojson.go | 27 +++ tomtom/route.go | 4 + tomtom/tomtom.go | 14 +- tomtom/types.go | 18 +- 9 files changed, 392 insertions(+), 129 deletions(-) create mode 100644 tomtom/example/route-gps/main.go delete mode 100644 tomtom/example/route/main.go create mode 100644 tomtom/geocode.go create mode 100644 tomtom/geojson.go diff --git a/html/static/js/map-routing.js b/html/static/js/map-routing.js index 4c307eb2..4eb06225 100644 --- a/html/static/js/map-routing.js +++ b/html/static/js/map-routing.js @@ -61,38 +61,239 @@ class MapRouting extends HTMLElement { }); */ this._map = new maplibregl.Map({ + center: { + lat: 36.351947895503585, + lng: -119.31857880996313, + }, container: mapElement, style: "https://tiles.stadiamaps.com/styles/alidade_smooth.json", // Style URL; see our documentation for more options - center: [-122.4194, 37.7749], // San Francisco coordinates - zoom: 12, - }); - // Sample GeoJSON for a driving route in San Francisco + }).fitBounds( + [ + { lat: 36.33870557056423, lng: -119.35466592321588 }, + { lat: 36.36630172845781, lng: -119.28771302024407 }, + ], + { + padding: { top: 10, bottom: 10, left: 10, right: 10 }, + }, + ); const routeData = { type: "Feature", properties: {}, geometry: { type: "LineString", coordinates: [ - [-122.4194, 37.7749], // Start: San Francisco downtown - [-122.4211, 37.7837], - [-122.4156, 37.7908], - [-122.4089, 37.7973], - [-122.3998, 37.8015], - [-122.3919, 37.8057], - [-122.3873, 37.8067], // End: Fisherman's Wharf area + [-119.31104, 36.3419], + [-119.31005, 36.34185], + [-119.30905, 36.34183], + [-119.30815, 36.34181], + [-119.30778, 36.34182], + [-119.30755, 36.34184], + [-119.30678, 36.34188], + [-119.30656, 36.34188], + [-119.30618, 36.34187], + [-119.3056, 36.34187], + [-119.3056, 36.34277], + [-119.30561, 36.34345], + [-119.3056, 36.34362], + [-119.30562, 36.34523], + [-119.30563, 36.34627], + [-119.30563, 36.3473], + [-119.30563, 36.3483], + [-119.30566, 36.3501], + [-119.30565, 36.35052], + [-119.30566, 36.3508], + [-119.30567, 36.35129], + [-119.30567, 36.35191], + [-119.30569, 36.35228], + [-119.30573, 36.35276], + [-119.30575, 36.35306], + [-119.30574, 36.35338], + [-119.30574, 36.35625], + [-119.30574, 36.35641], + [-119.30574, 36.35651], + [-119.30572, 36.35806], + [-119.30513, 36.35806], + [-119.30353, 36.35805], + [-119.30352, 36.35752], + [-119.30393, 36.35753], + [-119.30438, 36.35753], + [-119.30438, 36.35753], + [-119.3046, 36.35753], + [-119.30512, 36.35753], + [-119.3052, 36.35751], + [-119.30524, 36.35746], + [-119.30524, 36.35696], + [-119.30521, 36.3569], + [-119.30509, 36.35688], + [-119.3046, 36.35688], + [-119.30394, 36.35687], + [-119.30308, 36.35687], + [-119.3024, 36.35687], + [-119.30181, 36.35687], + [-119.30175, 36.35689], + [-119.30173, 36.35695], + [-119.30173, 36.35721], + [-119.30133, 36.35721], + [-119.30134, 36.3565], + [-119.30191, 36.3565], + [-119.30249, 36.3565], + [-119.30345, 36.3565], + [-119.30492, 36.35651], + [-119.30509, 36.35651], + [-119.30528, 36.35651], + [-119.30574, 36.35651], + [-119.30574, 36.35641], + [-119.30574, 36.35625], + [-119.30574, 36.35338], + [-119.30575, 36.35306], + [-119.30573, 36.35276], + [-119.30569, 36.35228], + [-119.30567, 36.35191], + [-119.30567, 36.35129], + [-119.30566, 36.3508], + [-119.30565, 36.35052], + [-119.30566, 36.3501], + [-119.30597, 36.3501], + [-119.30613, 36.35009], + [-119.30629, 36.35008], + [-119.30642, 36.35007], + [-119.30688, 36.35001], + [-119.30721, 36.34992], + [-119.30754, 36.34984], + [-119.30817, 36.34955], + [-119.30851, 36.34946], + [-119.30906, 36.34933], + [-119.30917, 36.34932], + [-119.30949, 36.34928], + [-119.31007, 36.34928], + [-119.31152, 36.34928], + [-119.31195, 36.34928], + [-119.3124, 36.34928], + [-119.31337, 36.3493], + [-119.31354, 36.3493], + [-119.31374, 36.3493], + [-119.31391, 36.3493], + [-119.31417, 36.34932], + [-119.31426, 36.34932], + [-119.31456, 36.34933], + [-119.31484, 36.34933], + [-119.31505, 36.34933], + [-119.31528, 36.34931], + [-119.31654, 36.34921], + [-119.31692, 36.3492], + [-119.31708, 36.34921], + [-119.31786, 36.34921], + [-119.31867, 36.34918], + [-119.31972, 36.34917], + [-119.32087, 36.34918], + [-119.32228, 36.34917], + [-119.32246, 36.34917], + [-119.32263, 36.34916], + [-119.32313, 36.34915], + [-119.32339, 36.34916], + [-119.32375, 36.34918], + [-119.324, 36.34917], + [-119.3241, 36.34922], + [-119.32555, 36.34923], + [-119.32625, 36.34923], + [-119.32706, 36.34922], + [-119.32722, 36.34915], + [-119.32777, 36.34917], + [-119.32776, 36.34811], + [-119.32776, 36.3475], + [-119.32775, 36.34709], + [-119.32772, 36.34709], + [-119.32712, 36.34709], + [-119.32713, 36.34759], + [-119.32713, 36.3477], + [-119.32708, 36.34776], + [-119.327, 36.34782], + [-119.327, 36.34782], + [-119.32708, 36.34776], + [-119.32713, 36.3477], + [-119.32713, 36.34759], + [-119.32712, 36.34709], + [-119.32772, 36.34709], + [-119.32775, 36.34709], + [-119.32776, 36.3475], + [-119.32776, 36.34811], + [-119.32777, 36.34917], + [-119.32824, 36.34917], + [-119.32845, 36.34917], + [-119.32885, 36.34917], + [-119.33003, 36.34918], + [-119.33057, 36.34918], + [-119.33075, 36.34918], + [-119.3309, 36.34918], + [-119.33099, 36.34922], + [-119.33116, 36.34922], + [-119.33126, 36.34925], + [-119.33195, 36.34926], + [-119.33197, 36.34976], + [-119.33198, 36.35], + [-119.33199, 36.35024], + [-119.33203, 36.35129], + [-119.33201, 36.35191], + [-119.33202, 36.35275], + [-119.33202, 36.35279], + [-119.33202, 36.353], + [-119.33203, 36.35327], + [-119.33204, 36.35457], + [-119.33205, 36.35516], + [-119.33205, 36.35532], + [-119.33205, 36.3556], + [-119.33205, 36.35601], + [-119.33198, 36.35611], + [-119.33197, 36.35633], + [-119.33197, 36.35641], + [-119.33197, 36.35657], + [-119.33199, 36.35746], + [-119.33199, 36.35756], + [-119.33202, 36.35785], + [-119.33203, 36.35815], + [-119.33203, 36.35865], + [-119.33203, 36.35903], + [-119.3321, 36.35914], + [-119.3321, 36.35923], + [-119.33209, 36.35952], + [-119.33211, 36.36154], + [-119.33194, 36.36154], + [-119.33114, 36.36153], + [-119.33029, 36.36154], + [-119.32824, 36.36153], + [-119.32824, 36.36165], + [-119.32826, 36.36241], + [-119.32826, 36.36262], + [-119.3283, 36.36284], ], }, }; + const stopData = { + type: "Feature", + geometry: { + type: "MultiPoint", + coordinates: [ + [-119.31104, 36.3419], + [-119.30438, 36.35753], + [-119.327, 36.34782], + [-119.3283, 36.36284], + ], + }, + properties: {}, + }; // Add map controls this._map.addControl(new maplibregl.NavigationControl()); // Wait for the map to load this._map.on("load", () => { - // Add the route source this._map.addSource("route", { type: "geojson", data: routeData, }); + this._map.addSource("stops", { + type: "geojson", + data: stopData, + }); // Add a layer to display the route this._map.addLayer({ @@ -110,56 +311,13 @@ class MapRouting extends HTMLElement { }, }); - // Add start point - this._map.addSource("start-point", { - type: "geojson", - data: { - type: "Feature", - geometry: { - type: "Point", - coordinates: routeData.geometry.coordinates[0], - }, - properties: { - description: "Start", - }, - }, - }); - this._map.addLayer({ - id: "start-point", + id: "stops", type: "circle", - source: "start-point", + source: "stops", paint: { "circle-radius": 8, - "circle-color": "#3bb2d0", - }, - }); - - // Add end point - this._map.addSource("end-point", { - type: "geojson", - data: { - type: "Feature", - geometry: { - type: "Point", - coordinates: - routeData.geometry.coordinates[ - routeData.geometry.coordinates.length - 1 - ], - }, - properties: { - description: "End", - }, - }, - }); - - this._map.addLayer({ - id: "end-point", - type: "circle", - source: "end-point", - paint: { - "circle-radius": 8, - "circle-color": "#f30", + "circle-color": "#f00", }, }); }); diff --git a/tomtom/.air.toml b/tomtom/.air.toml index ae997d6c..7c5738f8 100644 --- a/tomtom/.air.toml +++ b/tomtom/.air.toml @@ -5,7 +5,7 @@ tmp_dir = "tmp" [build] args_bin = [] bin = "./tmp/main" - cmd = "go build -o ./tmp/main ./example/route" + cmd = "go build -o ./tmp/main ./example/geocode-and-route" delay = 1000 exclude_dir = ["assets", "tmp", "vendor", "testdata"] exclude_file = [] diff --git a/tomtom/example/route-gps/main.go b/tomtom/example/route-gps/main.go new file mode 100644 index 00000000..de222f36 --- /dev/null +++ b/tomtom/example/route-gps/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "log" + + "github.com/Gleipnir-Technology/nidus-sync/tomtom" +) + +func main() { + client := tomtom.NewClient() + + // Example 1: Calculate a simple route + traffic := false + routeRequest := &tomtom.CalculateRouteRequest{ + Locations: []tomtom.Point{ + tomtom.P(52.50931, 13.42936), + tomtom.P(52.50274, 13.43872), + }, + Traffic: &traffic, + TravelMode: tomtom.TravelModeCar, + RouteType: tomtom.RouteTypeFastest, + } + + routeResp, err := client.CalculateRoute(routeRequest) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Route distance: %d meters\n", routeResp.Routes[0].Summary.LengthInMeters) +} diff --git a/tomtom/example/route/main.go b/tomtom/example/route/main.go deleted file mode 100644 index bdb645bb..00000000 --- a/tomtom/example/route/main.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "fmt" - "log" - - "github.com/Gleipnir-Technology/nidus-sync/tomtom" -) - -func main() { - client := tomtom.NewClient() - - // Example 1: Calculate a simple route - traffic := false - routeRequest := &tomtom.CalculateRouteRequest{ - Locations: []tomtom.Point{ - tomtom.P(52.50931, 13.42936), - tomtom.P(52.50274, 13.43872), - }, - Traffic: &traffic, - TravelMode: tomtom.TravelModeCar, - RouteType: tomtom.RouteTypeFastest, - } - - routeResp, err := client.CalculateRoute(routeRequest) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("Route distance: %d meters\n", routeResp.Routes[0].Summary.LengthInMeters) - - // Example 2: Calculate reachable range - /* - energyBudget := 43.0 - rangeRequest := &tomtom.CalculateReachableRangeRequest{ - Params: tomtom.CalculateReachableRangeParams{ - VersionNumber: 1, - Origin: "52.50931,13.42936", - ContentType: "json", - EnergyBudgetInkWh: &energyBudget, - VehicleEngineType: "electric", - }, - } - - rangeResp, err := client.CalculateReachableRange(rangeRequest) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("Reachable range includes %d points in the polygon\n", len(rangeResp.Polygon.Exterior)) - */ -} diff --git a/tomtom/geocode.go b/tomtom/geocode.go new file mode 100644 index 00000000..ef46ea7f --- /dev/null +++ b/tomtom/geocode.go @@ -0,0 +1,99 @@ +package tomtom + +import ( + "fmt" +) + +type PointShort struct { + Latitude float64 `json:"lat"` + Longitude float64 `json:"lon"` +} + +func (ps PointShort) AsPoint() Point { + return Point{ + Latitude: ps.Latitude, + Longitude: ps.Longitude, + } +} + +type GeocodeResult struct { + Type string `json:"type"` + ID string `json:"id"` + Score float64 `json:"score"` + Dist float64 `json:"dist"` + MatchConfidence MatchConfidence `json:"matchConfidence"` + Address Address `json:"address"` + Position PointShort `json:"position"` + Viewport Viewport `json:"viewport"` + EntryPoints []EntryPoint `json:"entryPoints"` +} + +// MatchConfidence represents the confidence score for a match +type MatchConfidence struct { + Score float64 `json:"score"` +} + +// Address contains detailed address information +type Address struct { + StreetNumber string `json:"streetNumber"` + StreetName string `json:"streetName"` + Municipality string `json:"municipality"` + CountrySecondarySubdivision string `json:"countrySecondarySubdivision"` + CountrySubdivision string `json:"countrySubdivision"` + CountrySubdivisionName string `json:"countrySubdivisionName"` + CountrySubdivisionCode string `json:"countrySubdivisionCode"` + PostalCode string `json:"postalCode"` + ExtendedPostalCode string `json:"extendedPostalCode"` + CountryCode string `json:"countryCode"` + Country string `json:"country"` + CountryCodeISO3 string `json:"countryCodeISO3"` + FreeformAddress string `json:"freeformAddress"` + LocalName string `json:"localName"` +} + +// Viewport defines a geographic bounding box +type Viewport struct { + TopLeftPoint PointShort `json:"topLeftPoint"` + BtmRightPoint PointShort `json:"btmRightPoint"` +} + +// EntryPoint contains information about a point of entry to a location +type EntryPoint struct { + Type string `json:"type"` + Position PointShort `json:"position"` +} +type GeocodeSummary struct { + Query string `json:"query"` + QueryType string `json:"queryType"` + QueryTime uint `json:"queryTime"` + NumResults uint `json:"numResults"` + Offset uint `json:"offset"` + TotalResults uint `json:"totalResults"` + FuzzyLevel uint `json:"fuzzyLevel"` + GeoBias PointShort `json:"geoBias"` +} +type GeocodeResponse struct { + Summary GeocodeSummary `json:"summary"` + Results []GeocodeResult `json:"results"` +} + +// CalculateRoute sends a route calculation request to TomTom API +func (c *TomTom) Geocode(address string) (*GeocodeResponse, error) { + var result GeocodeResponse + + resp, err := c.client.R(). + SetResult(&result). + SetPathParam("address", address). + SetPathParam("urlBase", c.urlBase). + SetQueryParam("key", c.APIKey). + SetQueryParam("storeResult", "false"). + Get("https://{urlBase}/search/2/geocode/{address}.json") + if err != nil { + return nil, fmt.Errorf("calculate route get: %w", err) + } + if !resp.IsSuccess() { + return nil, fmt.Errorf("calculate route status: %d", resp.Status) + } + + return &result, nil +} diff --git a/tomtom/geojson.go b/tomtom/geojson.go new file mode 100644 index 00000000..0879acae --- /dev/null +++ b/tomtom/geojson.go @@ -0,0 +1,27 @@ +package tomtom + +import ( + "fmt" + "strings" +) + +// Convert a slice of points to GeoJSON +func PolylineToGeoJSON(polyline []Point) string { + var sb strings.Builder + + sb.WriteString(`{"type":"LineString","coordinates":[`) + + for i, point := range polyline { + // IMPORTANT: GeoJSON uses [longitude, latitude] order! + sb.WriteString(fmt.Sprintf("[%g,%g]", point.Longitude, point.Latitude)) + + // Add comma if not the last point + if i < len(polyline)-1 { + sb.WriteString(",") + } + } + + sb.WriteString("]}") + + return sb.String() +} diff --git a/tomtom/route.go b/tomtom/route.go index eeb83374..ae0534bc 100644 --- a/tomtom/route.go +++ b/tomtom/route.go @@ -68,6 +68,10 @@ func (sgr CalculateRouteRequest) toQueryParams() (url.Values, error) { return query.Values(sgr) } +type CalculateRouteResponse struct { + Routes []Route `json:"routes"` +} + // CalculateRoute sends a route calculation request to TomTom API func (c *TomTom) CalculateRoute(req *CalculateRouteRequest) (*CalculateRouteResponse, error) { /*url, err := req.BuildURL(c.APIKey) diff --git a/tomtom/tomtom.go b/tomtom/tomtom.go index 8c1b43ce..0d529bb0 100644 --- a/tomtom/tomtom.go +++ b/tomtom/tomtom.go @@ -14,11 +14,8 @@ type TomTom struct { } func NewClient() *TomTom { - //logger := NewLogger(log.Logger) - //r := resty.New().SetLogger(logger).SetDebug(true) - r := resty.New().SetDebug(true) api_key := os.Getenv("TOMTOM_API_KEY") - //r := resty.New() + r := resty.New() return &TomTom{ APIKey: api_key, client: r, @@ -29,3 +26,12 @@ func NewClient() *TomTom { func (s *TomTom) Close() { s.client.Close() } + +func (s *TomTom) SetDebug(enabled bool) { + s.client.Close() + if enabled { + s.client = resty.New().SetDebug(true) + } else { + s.client = resty.New().SetDebug(false) + } +} diff --git a/tomtom/types.go b/tomtom/types.go index 17d2c88b..29d48bdb 100644 --- a/tomtom/types.go +++ b/tomtom/types.go @@ -68,24 +68,14 @@ type Point struct { Longitude float64 `json:"longitude"` } -type Polyline struct { - Points []Point `json:"points"` -} - type Leg struct { - Summary Summary `json:"summary"` - Points []Point `json:"points,omitempty"` - Polyline Polyline `json:"polyline,omitempty"` + Summary Summary `json:"summary"` + Points []Point `json:"points,omitempty"` } type Route struct { - Summary Summary `json:"summary"` - Legs []Leg `json:"legs,omitempty"` - Polyline Polyline `json:"polyline,omitempty"` -} - -type CalculateRouteResponse struct { - Routes []Route `json:"routes"` + Summary Summary `json:"summary"` + Legs []Leg `json:"legs,omitempty"` } // CalculateReachableRange API structures