116 lines
2.4 KiB
Go
116 lines
2.4 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
|
|
"golang.org/x/time/rate"
|
|
"googlemaps.github.io/maps"
|
|
)
|
|
|
|
type Maps struct {
|
|
around Location
|
|
cache Cache
|
|
}
|
|
|
|
func NewMapsOf(ctx context.Context, town string) (*Maps, error) {
|
|
m := &Maps{
|
|
cache: NewCache(ctx),
|
|
}
|
|
var err error
|
|
m.around, err = m.textSearchOne(ctx, town)
|
|
return m, err
|
|
}
|
|
|
|
var rps = 2
|
|
var mapsLimit = 200
|
|
var mapsLimiter = rate.NewLimiter(rate.Limit(rps), 1)
|
|
|
|
type Location struct {
|
|
Name string
|
|
Lat float64
|
|
Lng float64
|
|
Address string
|
|
}
|
|
|
|
func (m *Maps) textSearchOne(ctx context.Context, query string) (Location, error) {
|
|
results, err := m.Search(ctx, query)
|
|
if err != nil {
|
|
return Location{}, err
|
|
} else if len(results) < 1 {
|
|
return Location{}, fmt.Errorf("no results for %q", query)
|
|
}
|
|
return results[0], nil
|
|
}
|
|
|
|
func (m *Maps) Search(ctx context.Context, query string) ([]Location, error) {
|
|
cacheK := path.Join(fmt.Sprint(m.around), query)
|
|
if b, err := m.cache.Get(ctx, cacheK); err == nil {
|
|
var locations []Location
|
|
log.Printf("cache hit for %q", cacheK)
|
|
if err := json.Unmarshal(b, &locations); err == nil {
|
|
return locations, nil
|
|
}
|
|
}
|
|
log.Printf("cache miss for %q", cacheK)
|
|
|
|
locations := []Location{}
|
|
nextToken := ""
|
|
for len(locations) < mapsLimit {
|
|
results, err := m._textSearch(ctx, query, nextToken)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, result := range results.Results {
|
|
locations = append(locations, Location{
|
|
Name: result.Name,
|
|
Lat: result.Geometry.Location.Lat,
|
|
Lng: result.Geometry.Location.Lng,
|
|
Address: result.FormattedAddress,
|
|
})
|
|
}
|
|
nextToken = results.NextPageToken
|
|
if nextToken == "" {
|
|
break
|
|
}
|
|
}
|
|
|
|
cacheV, _ := json.Marshal(locations)
|
|
m.cache.Set(ctx, cacheK, cacheV)
|
|
|
|
return locations, ctx.Err()
|
|
}
|
|
|
|
func (m *Maps) _textSearch(ctx context.Context, query string, nextToken string) (maps.PlacesSearchResponse, error) {
|
|
mapsLimiter.Wait(ctx)
|
|
|
|
var location *maps.LatLng
|
|
radius := uint(250)
|
|
if m.around != (Location{}) {
|
|
radius = 250
|
|
location = &maps.LatLng{
|
|
Lat: m.around.Lat,
|
|
Lng: m.around.Lng,
|
|
}
|
|
}
|
|
|
|
return m.client().TextSearch(ctx, &maps.TextSearchRequest{
|
|
Query: query,
|
|
Location: location,
|
|
Radius: radius,
|
|
PageToken: nextToken,
|
|
})
|
|
}
|
|
|
|
func (*Maps) client() *maps.Client {
|
|
c, err := maps.NewClient(maps.WithAPIKey(os.Getenv("GOOGLE_PLACES_API_KEY")))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return c
|
|
}
|