refactor: update dependencies and improve distance matrix handling

Updates Go module dependencies to the latest compatible versions, 
including the integration of Cloud Maps routing API. Refactors the 
distance matrix handling to utilize the new routing client, 
ensuring proper request formation and error handling for lat/lng 
parsing. This enhances functionality and maintains compatibility 
with updated libraries.
This commit is contained in:
2025-02-15 19:59:36 +01:00
parent 90dbf82d69
commit 53c89cb7e4
3 changed files with 276 additions and 59 deletions
+100 -43
View File
@@ -4,12 +4,18 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"io"
"log/slog"
"net/http"
"strconv"
"strings"
"cloud.google.com/go/maps/routing/apiv2"
"cloud.google.com/go/maps/routing/apiv2/routingpb"
"github.com/caarlos0/env"
"google.golang.org/api/option"
"google.golang.org/genproto/googleapis/type/latlng"
"google.golang.org/grpc/metadata"
"googlemaps.github.io/maps"
)
@@ -60,15 +66,25 @@ func main() {
}
fmt.Printf("%+v\n", cfg)
client, err := maps.NewClient(maps.WithAPIKey(cfg.MapsApiKey))
mapsClient, err := maps.NewClient(maps.WithAPIKey(cfg.MapsApiKey))
if err != nil {
log.Fatalf("fatal error: %s", err)
slog.With("error", err).Error("new maps client")
return
}
routesClient, err := routing.NewRoutesClient(context.Background(), option.WithAPIKey(cfg.MapsApiKey))
if err != nil {
slog.With("error", err).Error("new routes client")
return
}
http.HandleFunc("/latlong/", makeHandler(handleLatLongRequest, client))
http.HandleFunc("/address/", makeHandler(handleAddressRequest, client))
http.HandleFunc("/distance/", makeHandler(handleDistanceMatrixRequest, client))
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", cfg.Port), nil))
http.HandleFunc("/latlong/", makeHandler(handleLatLongRequest, mapsClient))
http.HandleFunc("/address/", makeHandler(handleAddressRequest, mapsClient))
http.HandleFunc("/distance/", func(w http.ResponseWriter, r *http.Request) {
handleDistanceMatrixRequest(w, r, routesClient)
})
if err := http.ListenAndServe(fmt.Sprintf(":%d", cfg.Port), nil); err != nil {
slog.With("error", err).Error("listen and serve")
}
}
func makeHandler(fn func(http.ResponseWriter, *http.Request, *maps.Client), client *maps.Client) http.HandlerFunc {
@@ -77,57 +93,98 @@ func makeHandler(fn func(http.ResponseWriter, *http.Request, *maps.Client), clie
}
}
func handleDistanceMatrixRequest(w http.ResponseWriter, r *http.Request, client *maps.Client) {
origins := strings.Split(r.URL.Query().Get("origins"), "|")
func handleDistanceMatrixRequest(w http.ResponseWriter, r *http.Request, client *routing.RoutesClient) {
ctx := context.Background()
ctx = metadata.AppendToOutgoingContext(ctx, "X-Goog-Fieldmask", "originIndex,destinationIndex,duration,distanceMeters,status")
origins := make([]*routingpb.RouteMatrixOrigin, 0)
destinations := make([]*routingpb.RouteMatrixDestination, 0)
for _, origin := range strings.Split(r.URL.Query().Get("origins"), "|") {
origins = append(origins, &routingpb.RouteMatrixOrigin{
Waypoint: &routingpb.Waypoint{
LocationType: &routingpb.Waypoint_Address{Address: origin},
},
})
}
destinationsString := r.URL.Query().Get("destinations")
if destinationsString == "" {
x := make([]origin, len(origins))
for i, o := range origins {
x[i] = origin{Origin: o}
x[i] = origin{Origin: o.Waypoint.GetAddress()}
}
writeResponse(distanceResponse{Origins: x}, w)
return
}
destinations := strings.Split(destinationsString, "|")
req := &maps.DistanceMatrixRequest{
for _, destination := range strings.Split(destinationsString, "|") {
parts := strings.Split(destination, ",")
lat, err := strconv.ParseFloat(parts[0], 64)
if err != nil {
slog.With("error", err, "part", "handleDistanceMatrixRequest", "latlng", destination).Error("unable to parse latitude")
w.WriteHeader(400)
return
}
lng, err := strconv.ParseFloat(parts[1], 64)
if err != nil {
slog.With("error", err, "part", "handleDistanceMatrixRequest", "latlng", destination).Error("unable to parse longitude")
w.WriteHeader(400)
return
}
destinations = append(destinations, &routingpb.RouteMatrixDestination{
Waypoint: &routingpb.Waypoint{
LocationType: &routingpb.Waypoint_Location{Location: &routingpb.Location{LatLng: &latlng.LatLng{
Latitude: lat,
Longitude: lng,
}}},
},
})
}
matrixRequest := &routingpb.ComputeRouteMatrixRequest{
Origins: origins,
Destinations: destinations,
Language: "sv",
Units: maps.UnitsMetric,
Units: routingpb.Units_METRIC,
}
if result, err := client.DistanceMatrix(context.Background(), req); err != nil {
log.Fatalf("matrix fatal error: %s", err)
response, err := client.ComputeRouteMatrix(ctx, matrixRequest)
if err != nil {
slog.With("error", err, "part", "handleDistanceMatrixRequest").Error("compute matrix")
w.WriteHeader(400)
} else {
res := distanceResponse{}
for i, r := range result.Rows {
var dests []destination
for j, e := range r.Elements {
dests = append(dests, destination{
destinations[j],
distance{
Text: e.Distance.HumanReadable,
Value: e.Distance.Meters,
},
duration{
e.Duration.String(),
e.Duration.Minutes(),
},
})
}
res.Origins = append(res.Origins, origin{origins[i], dests})
}
writeResponse(res, w)
return
}
res := distanceResponse{}
dests := make([][]destination, len(origins))
for {
elem, err := response.Recv()
if err != nil {
if err == io.EOF {
break
}
slog.With("error", err, "part", "handleDistanceMatrixRequest").Error("fetch result")
w.WriteHeader(400)
return
}
ll := destinations[*elem.DestinationIndex].Waypoint.GetLocation().LatLng
dests[*elem.OriginIndex] = append(dests[*elem.OriginIndex], destination{
Destination: fmt.Sprintf("%f,%f", ll.Latitude, ll.Longitude),
Distance: distance{
Text: string(elem.DistanceMeters),
Value: int(elem.DistanceMeters),
},
Duration: duration{
elem.Duration.AsDuration().String(),
elem.Duration.AsDuration().Minutes(),
},
})
}
for i, dest := range dests {
res.Origins = append(res.Origins, origin{origins[i].Waypoint.GetAddress(), dest})
}
writeResponse(res, w)
}
func writeResponse(res distanceResponse, w http.ResponseWriter) {
if response, err := json.Marshal(res); err != nil {
log.Fatalf("fatal error: %s", err)
slog.With("error", err, "part", "handleDistanceMatrixRequest").Error("marshal response error")
w.WriteHeader(404)
} else {
_, _ = w.Write(response)
@@ -142,7 +199,7 @@ func handleAddressRequest(w http.ResponseWriter, r *http.Request, client *maps.C
LatLng: &maps.LatLng{Lat: lat, Lng: lng},
}
if result, err := client.Geocode(context.Background(), req); err != nil {
log.Fatalf("fatal error: %s", err)
slog.With("error", err, "part", "handleAddressRequest").Error("geocode")
w.WriteHeader(400)
} else {
if len(result) > 0 {
@@ -150,7 +207,7 @@ func handleAddressRequest(w http.ResponseWriter, r *http.Request, client *maps.C
Address: result[0].FormattedAddress,
}
if response, err := json.Marshal(l); err != nil {
log.Fatalf("fatal error: %s", err)
slog.With("error", err, "part", "handleAddressRequest").Error("marshal response error")
w.WriteHeader(404)
} else {
_, _ = w.Write(response)