200 lines
5.1 KiB
Go
200 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/jedib0t/go-pretty/v6/table"
|
|
"github.com/joho/godotenv"
|
|
"github.com/kr/pretty"
|
|
"github.com/xuri/excelize/v2"
|
|
"googlemaps.github.io/maps"
|
|
)
|
|
|
|
type Row struct {
|
|
PlaceID string `xlsx:"column(PlaceID)"`
|
|
Name string `xlsx:"column(Name)"`
|
|
Types []string `xlsx:"column(Types)"`
|
|
Address string `xlsx:"column(Address)"`
|
|
Website string `xlsx:"column(Website)"`
|
|
PhoneNumber string `xlsx:"column(PhoneNumber)"`
|
|
GoogleMapsUrl string `xlsx:"column(GoogleMapsUrl)"`
|
|
}
|
|
|
|
func main() {
|
|
fmt.Println("Reading ENV...")
|
|
err := godotenv.Load(".env")
|
|
if err != nil {
|
|
log.Fatalf("Error loading .env file")
|
|
}
|
|
|
|
fmt.Println("Creating maps api client...")
|
|
c, err := maps.NewClient(maps.WithAPIKey(os.Getenv("API_KEY")))
|
|
if err != nil {
|
|
log.Fatalf("error creating maps client: %s", err)
|
|
}
|
|
|
|
t := table.NewWriter()
|
|
t.AppendHeader(table.Row{"Name", "Address", "PhoneNumber"})
|
|
|
|
var rows []Row
|
|
|
|
fmt.Println("Searching places...")
|
|
foundPlaces := searchNearby(c)
|
|
|
|
for _, place := range foundPlaces {
|
|
details := searchDetails(c, place)
|
|
r := makeRow(place, details)
|
|
t.AppendRow(table.Row{r.Name, r.Address, r.PhoneNumber})
|
|
rows = append(rows, r)
|
|
}
|
|
|
|
fmt.Println(t.Render())
|
|
pretty.Println(len(rows))
|
|
|
|
createExcelSheet(rows)
|
|
}
|
|
|
|
func searchNearby(mapsClient *maps.Client) []maps.PlacesSearchResult {
|
|
mileInMeters, _ := strconv.ParseUint(os.Getenv("RADIUS_IN_METERS"), 10, strconv.IntSize)
|
|
Lat, _ := strconv.ParseFloat(os.Getenv("LAT"), 64)
|
|
Lng, _ := strconv.ParseFloat(os.Getenv("LNG"), 64)
|
|
latLon := maps.LatLng{
|
|
Lat: Lat,
|
|
Lng: Lng,
|
|
}
|
|
|
|
var foundPlaces []maps.PlacesSearchResult
|
|
var nextPageToken string = ""
|
|
|
|
MAX_RESULTS, _ := strconv.Atoi(os.Getenv("MAX_RESULTS"))
|
|
for next := true; next; next = (len(foundPlaces) <= MAX_RESULTS) && nextPageToken != "" {
|
|
mapRequest := maps.NearbySearchRequest{
|
|
Location: &latLon,
|
|
Radius: uint(mileInMeters),
|
|
PageToken: nextPageToken,
|
|
}
|
|
|
|
searchResponse, err := mapsClient.NearbySearch(context.Background(), &mapRequest)
|
|
if err != nil {
|
|
log.Fatalf("error searching places: %s", err)
|
|
}
|
|
|
|
nextPageToken = searchResponse.NextPageToken
|
|
for _, place := range searchResponse.Results {
|
|
if !shouldOmitPlace(place) {
|
|
foundPlaces = append(foundPlaces, place)
|
|
}
|
|
}
|
|
}
|
|
|
|
return foundPlaces
|
|
}
|
|
|
|
func findString(slice []string, condition func(string) bool) *string {
|
|
for _, element := range slice {
|
|
if condition(element) {
|
|
return &element
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func shouldOmitPlace(place maps.PlacesSearchResult) bool {
|
|
omitTypes := []string{"bar", "food", "restaurant", "store"}
|
|
requiredTypes := []string{"establishment"}
|
|
|
|
var hasOmitType bool = false
|
|
var hasRequredType bool = false
|
|
|
|
for _, placeType := range place.Types {
|
|
foundInOmit := findString(omitTypes, func(omitType string) bool {
|
|
return omitType == placeType
|
|
})
|
|
|
|
if foundInOmit != nil {
|
|
hasOmitType = true
|
|
break
|
|
}
|
|
|
|
foundInRequired := findString(requiredTypes, func(requiredType string) bool {
|
|
return requiredType == placeType
|
|
})
|
|
|
|
if foundInRequired != nil {
|
|
hasRequredType = true
|
|
}
|
|
}
|
|
|
|
return hasOmitType || !hasRequredType
|
|
}
|
|
|
|
func makeRow(place maps.PlacesSearchResult, details maps.PlaceDetailsResult) Row {
|
|
return Row{
|
|
PlaceID: place.PlaceID,
|
|
Name: place.Name,
|
|
Types: place.Types,
|
|
Address: details.FormattedAddress,
|
|
PhoneNumber: details.FormattedPhoneNumber,
|
|
Website: details.Website,
|
|
GoogleMapsUrl: details.URL,
|
|
}
|
|
}
|
|
|
|
func searchDetails(mapsClient *maps.Client, place maps.PlacesSearchResult) maps.PlaceDetailsResult {
|
|
desiredDetails := []maps.PlaceDetailsFieldMask{
|
|
"formatted_address",
|
|
"formatted_phone_number",
|
|
"website",
|
|
"url",
|
|
}
|
|
|
|
detailsRequest := maps.PlaceDetailsRequest{
|
|
PlaceID: place.PlaceID,
|
|
Fields: desiredDetails,
|
|
}
|
|
|
|
detailsResponse, err := mapsClient.PlaceDetails(context.Background(), &detailsRequest)
|
|
if err != nil {
|
|
log.Fatalf("error getting details for %s: %s", place.Name, err)
|
|
}
|
|
return detailsResponse
|
|
}
|
|
|
|
func createExcelSheet(rows []Row) {
|
|
|
|
fmt.Println("Writing XLSX...")
|
|
|
|
f := excelize.NewFile()
|
|
sheetName := os.Getenv("SHEET_NAME")
|
|
index, _ := f.NewSheet(sheetName)
|
|
f.SetActiveSheet(index)
|
|
|
|
excelHeaders := []string{"PlaceID", "Name", "Types", "Address", "Website", "PhoneNumber", "GoogleMapsUrl"}
|
|
for col, header := range excelHeaders {
|
|
cell := string(rune('A'+col)) + "1"
|
|
f.SetCellValue(sheetName, cell, header)
|
|
}
|
|
|
|
for i, row := range rows {
|
|
baseRow := i + 2
|
|
f.SetCellValue(sheetName, "A"+strconv.Itoa(baseRow), row.PlaceID)
|
|
f.SetCellValue(sheetName, "B"+strconv.Itoa(baseRow), row.Name)
|
|
f.SetCellValue(sheetName, "C"+strconv.Itoa(baseRow), strings.Join(row.Types, ","))
|
|
f.SetCellValue(sheetName, "D"+strconv.Itoa(baseRow), row.Address)
|
|
f.SetCellValue(sheetName, "E"+strconv.Itoa(baseRow), row.Website)
|
|
f.SetCellValue(sheetName, "F"+strconv.Itoa(baseRow), row.PhoneNumber)
|
|
f.SetCellValue(sheetName, "G"+strconv.Itoa(baseRow), row.GoogleMapsUrl)
|
|
}
|
|
|
|
if err := f.SaveAs(sheetName + ".xlsx"); err != nil {
|
|
fmt.Println(err)
|
|
} else {
|
|
fmt.Println("Finished")
|
|
}
|
|
}
|