fuzzyworld

find-stone-beer / commit

Added Bing Maps route distance calculation as a second dist check, including the driving distance with the results.

577ed62e170d3a1b824a72a66f39c4210e2b5433 · Pavel Safronov · 2023-08-26 23:41:51 -0700 · parents 2fc1f374a0db

Added Bing Maps route distance calculation as a second dist check, including the driving distance with the results.
 bing-maps.go | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 main.go      |  15 ++++++-
 2 files changed, 158 insertions(+), 2 deletions(-)

diff --git a/bing-maps.go b/bing-maps.go
new file mode 100644
index 0000000..ab7b403
--- /dev/null
+++ b/bing-maps.go
@@ -0,0 +1,145 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+
+	"github.com/jftuga/geodist"
+)
+
+const (
+	BingMapsKeyEnvVar = "BING_MAPS_KEY"
+)
+
+func GetDistBetweenPoints(a, b geodist.Coord) (float64, error) {
+	dist, err := _getDistBetweenPoints(a.Lat, a.Lon, b.Lat, b.Lon)
+	return dist, err
+}
+
+func _getDistBetweenPoints(lat1, lon1, lat2, lon2 float64) (float64, error) {
+	// http://dev.virtualearth.net/REST/v1/Routes?wayPoint.1={wayPoint1}&viaWaypoint.2={viaWaypoint2}&waypoint.3={waypoint3}&wayPoint.n={waypointN}&heading={heading}&optimize={optimize}&avoid={avoid}&distanceBeforeFirstTurn={distanceBeforeFirstTurn}&routeAttributes={routeAttributes}&timeType={timeType}&dateTime={dateTime}&maxSolutions={maxSolutions}&tolerances={tolerances}&distanceUnit={distanceUnit}&key={BingMapsKey}
+
+	bingMapsKey, found := os.LookupEnv(BingMapsKeyEnvVar)
+	if !found {
+		return 0, fmt.Errorf("missing environment variable: %s", BingMapsKeyEnvVar)
+	}
+
+	url := fmt.Sprintf("http://dev.virtualearth.net/REST/v1/Routes?wayPoint.1=%f,%f&wayPoint.2=%f,%f&distanceUnit=mi&key=%s", lat1, lon1, lat2, lon2, bingMapsKey)
+	data, err := getCachedUrl(
+		url,
+		fmt.Sprintf("bing-maps-%f,%f,%f,%f", lat1, lon1, lat2, lon2),
+		"2006-01", // cache this data for 1 month
+	)
+	if err != nil {
+		return 0, err
+	}
+
+	autoGenerated := autoGenerated{}
+	err = json.Unmarshal(data, &autoGenerated)
+	if err != nil {
+		return 0, err
+	}
+	dist := autoGenerated.ResourceSets[0].Resources[0].RouteLegs[0].TravelDistance
+	return dist, nil
+}
+
+// https://mholt.github.io/json-to-go/
+type autoGenerated struct {
+	AuthenticationResultCode string `json:"authenticationResultCode"`
+	BrandLogoURI             string `json:"brandLogoUri"`
+	Copyright                string `json:"copyright"`
+	ResourceSets             []struct {
+		EstimatedTotal int `json:"estimatedTotal"`
+		Resources      []struct {
+			Type         string    `json:"__type"`
+			Bbox         []float64 `json:"bbox"`
+			ID           string    `json:"id"`
+			DistanceUnit string    `json:"distanceUnit"`
+			DurationUnit string    `json:"durationUnit"`
+			RouteLegs    []struct {
+				ActualEnd struct {
+					Type        string    `json:"type"`
+					Coordinates []float64 `json:"coordinates"`
+				} `json:"actualEnd"`
+				ActualStart struct {
+					Type        string    `json:"type"`
+					Coordinates []float64 `json:"coordinates"`
+				} `json:"actualStart"`
+				AlternateVias  []any  `json:"alternateVias"`
+				Description    string `json:"description"`
+				EndTime        string `json:"endTime"`
+				ItineraryItems []struct {
+					CompassDirection string `json:"compassDirection"`
+					Details          []struct {
+						CompassDegrees   int      `json:"compassDegrees"`
+						EndPathIndices   []int    `json:"endPathIndices"`
+						LocationCodes    []string `json:"locationCodes"`
+						ManeuverType     string   `json:"maneuverType"`
+						Mode             string   `json:"mode"`
+						Names            []string `json:"names"`
+						RoadType         string   `json:"roadType"`
+						StartPathIndices []int    `json:"startPathIndices"`
+					} `json:"details"`
+					Exit        string `json:"exit"`
+					IconType    string `json:"iconType"`
+					Instruction struct {
+						FormattedText any    `json:"formattedText"`
+						ManeuverType  string `json:"maneuverType"`
+						Text          string `json:"text"`
+					} `json:"instruction"`
+					IsRealTimeTransit bool `json:"isRealTimeTransit"`
+					ManeuverPoint     struct {
+						Type        string    `json:"type"`
+						Coordinates []float64 `json:"coordinates"`
+					} `json:"maneuverPoint"`
+					RealTimeTransitDelay int     `json:"realTimeTransitDelay"`
+					SideOfStreet         string  `json:"sideOfStreet"`
+					TollZone             string  `json:"tollZone"`
+					TransitTerminus      string  `json:"transitTerminus"`
+					TravelDistance       float64 `json:"travelDistance"`
+					TravelDuration       int     `json:"travelDuration"`
+					TravelMode           string  `json:"travelMode"`
+					Hints                []struct {
+						HintType string `json:"hintType"`
+						Text     string `json:"text"`
+					} `json:"hints,omitempty"`
+				} `json:"itineraryItems"`
+				RouteRegion  string `json:"routeRegion"`
+				RouteSubLegs []struct {
+					EndWaypoint struct {
+						Type               string    `json:"type"`
+						Coordinates        []float64 `json:"coordinates"`
+						Description        string    `json:"description"`
+						IsVia              bool      `json:"isVia"`
+						LocationIdentifier string    `json:"locationIdentifier"`
+						RoutePathIndex     int       `json:"routePathIndex"`
+					} `json:"endWaypoint"`
+					StartWaypoint struct {
+						Type               string    `json:"type"`
+						Coordinates        []float64 `json:"coordinates"`
+						Description        string    `json:"description"`
+						IsVia              bool      `json:"isVia"`
+						LocationIdentifier string    `json:"locationIdentifier"`
+						RoutePathIndex     int       `json:"routePathIndex"`
+					} `json:"startWaypoint"`
+					TravelDistance float64 `json:"travelDistance"`
+					TravelDuration int     `json:"travelDuration"`
+				} `json:"routeSubLegs"`
+				StartTime      string  `json:"startTime"`
+				TravelDistance float64 `json:"travelDistance"`
+				TravelDuration int     `json:"travelDuration"`
+				TravelMode     string  `json:"travelMode"`
+			} `json:"routeLegs"`
+			TrafficCongestion     string  `json:"trafficCongestion"`
+			TrafficDataUsed       string  `json:"trafficDataUsed"`
+			TravelDistance        float64 `json:"travelDistance"`
+			TravelDuration        int     `json:"travelDuration"`
+			TravelDurationTraffic int     `json:"travelDurationTraffic"`
+			TravelMode            string  `json:"travelMode"`
+		} `json:"resources"`
+	} `json:"resourceSets"`
+	StatusCode        int    `json:"statusCode"`
+	StatusDescription string `json:"statusDescription"`
+	TraceID           string `json:"traceId"`
+}
diff --git a/main.go b/main.go
index 848f128..30b9351 100644
--- a/main.go
+++ b/main.go
@@ -40,7 +40,7 @@ func main() {
 	}
 
 	// load other settings from env vars
-	var rangeMiles = getEnvVarInteger(RANGE_MILES_ENV_VAR, 10)
+	var rangeMiles = getEnvVarInteger(RANGE_MILES_ENV_VAR, 15)
 	var maxDeliveryWindowDays = getEnvVarInteger(MAX_DELIVERY_DAYS_ENV_VAR, 14)
 
 	// descriptions of the beers and packaging
@@ -74,6 +74,7 @@ func main() {
 	relevantSellers := []relevantSeller{}
 
 	for _, seller := range arrogantBastardSellers {
+		// quick distance check, gives us the crowd-sourced distance, which is the shortest distance between two points, so using this as a range check is a good and cheap first pass
 		dist, _, err := geodist.VincentyDistance(coords, seller.Pos)
 		if err != nil {
 			panic(err)
@@ -82,6 +83,16 @@ func main() {
 			continue
 		}
 
+		// calculate route distance, which is the actual distance you would drive, using Bing Maps API
+		// the results are cached for the month, for each set of coordinates, so this is a stable cached operation
+		dist, err = GetDistBetweenPoints(coords, seller.Pos)
+		if err != nil {
+			panic(err)
+		}
+		if dist > float64(rangeMiles) {
+			continue
+		}
+
 		sellerInfo, err := getSellerInfo(seller.Nid)
 		if err != nil {
 			panic(err)
@@ -100,7 +111,7 @@ func main() {
 				continue
 			}
 			mapsLink := getGoogleMapsLink(seller.Pos)
-			message := fmt.Sprintf("<a href='%s'>%s</a> - delivered %d days ago\n", mapsLink, seller.Title, daysSinceDelivery)
+			message := fmt.Sprintf("<a href='%s'>%s (%.1fm)</a> - delivered %d days ago\n", mapsLink, seller.Title, dist, daysSinceDelivery)
 
 			relevantSellers = append(relevantSellers, relevantSeller{
 				Name:            seller.Title,