b3e9b723a4
This commit makes corrections to the initialization of the dests array to ensure it properly allocates space for each destination. It also fixes the response handling by correctly extending the response structure after the loop, improving overall stability and accuracy of the distance matrix request handling.
243 lines
6.7 KiB
Go
243 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"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"
|
|
)
|
|
|
|
type config struct {
|
|
MapsApiKey string `env:"MAPS_API_KEY"`
|
|
Port int `env:"PORT" envDefault:"80"`
|
|
}
|
|
|
|
type location struct {
|
|
Lat float64 `json:"latitude"`
|
|
Long float64 `json:"longitude"`
|
|
}
|
|
|
|
type address struct {
|
|
Address string `json:"address"`
|
|
}
|
|
|
|
type distance struct {
|
|
Text string `json:"text"`
|
|
Value int `json:"value"`
|
|
}
|
|
|
|
type duration struct {
|
|
Text string `json:"text"`
|
|
Value float64 `json:"value"`
|
|
}
|
|
|
|
type destination struct {
|
|
Destination string `json:"destination"`
|
|
Distance distance `json:"distance"`
|
|
Duration duration `json:"duration"`
|
|
}
|
|
|
|
type origin struct {
|
|
Origin string `json:"origin"`
|
|
Destinations []destination `json:"destinations"`
|
|
}
|
|
|
|
type distanceResponse struct {
|
|
Origins []origin `json:"origins"`
|
|
}
|
|
|
|
func main() {
|
|
cfg := config{}
|
|
err := env.Parse(&cfg)
|
|
if err != nil {
|
|
fmt.Printf("%+v\n", err)
|
|
}
|
|
fmt.Printf("%+v\n", cfg)
|
|
|
|
mapsClient, err := maps.NewClient(maps.WithAPIKey(cfg.MapsApiKey))
|
|
if err != nil {
|
|
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, 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 {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
fn(w, r, client)
|
|
}
|
|
}
|
|
|
|
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.Waypoint.GetAddress()}
|
|
}
|
|
writeResponse(distanceResponse{Origins: x}, w)
|
|
return
|
|
}
|
|
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,
|
|
Units: routingpb.Units_METRIC,
|
|
}
|
|
response, err := client.ComputeRouteMatrix(ctx, matrixRequest)
|
|
if err != nil {
|
|
slog.With("error", err, "part", "handleDistanceMatrixRequest").Error("compute matrix")
|
|
w.WriteHeader(400)
|
|
return
|
|
}
|
|
|
|
res := distanceResponse{}
|
|
dests := make([][]destination, len(origins))
|
|
for i := range dests {
|
|
dests[i] = make([]destination, len(destinations))
|
|
}
|
|
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][*elem.DestinationIndex] = 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 {
|
|
slog.With("error", err, "part", "handleDistanceMatrixRequest").Error("marshal response error")
|
|
w.WriteHeader(404)
|
|
} else {
|
|
_, _ = w.Write(response)
|
|
}
|
|
}
|
|
|
|
func handleAddressRequest(w http.ResponseWriter, r *http.Request, client *maps.Client) {
|
|
p := strings.Split(r.URL.Path[len("/address/"):], ",")
|
|
lat, _ := strconv.ParseFloat(p[0], 64)
|
|
lng, _ := strconv.ParseFloat(p[1], 64)
|
|
req := &maps.GeocodingRequest{
|
|
LatLng: &maps.LatLng{Lat: lat, Lng: lng},
|
|
}
|
|
if result, err := client.Geocode(context.Background(), req); err != nil {
|
|
slog.With("error", err, "part", "handleAddressRequest").Error("geocode")
|
|
w.WriteHeader(400)
|
|
} else {
|
|
if len(result) > 0 {
|
|
l := address{
|
|
Address: result[0].FormattedAddress,
|
|
}
|
|
if response, err := json.Marshal(l); err != nil {
|
|
slog.With("error", err, "part", "handleAddressRequest").Error("marshal response error")
|
|
w.WriteHeader(404)
|
|
} else {
|
|
_, _ = w.Write(response)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleLatLongRequest(w http.ResponseWriter, r *http.Request, client *maps.Client) {
|
|
req := &maps.GeocodingRequest{
|
|
Address: r.URL.Path[len("/latlong/"):],
|
|
}
|
|
if result, err := client.Geocode(context.Background(), req); err != nil {
|
|
w.WriteHeader(400)
|
|
} else {
|
|
if len(result) > 0 {
|
|
l := location{
|
|
Lat: result[0].Geometry.Location.Lat,
|
|
Long: result[0].Geometry.Location.Lng,
|
|
}
|
|
if response, err := json.Marshal(l); err != nil {
|
|
w.WriteHeader(500)
|
|
} else {
|
|
_, _ = w.Write(response)
|
|
}
|
|
} else {
|
|
w.WriteHeader(404)
|
|
}
|
|
}
|
|
}
|