1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
// Copyright © 2022 siddharth ravikumar <s@ricketyspace.net>
// SPDX-License-Identifier: ISC
// Functions for accessing the Photo gecoding API.
package photon
import (
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"strings"
"ricketyspace.net/peach/client"
)
// Coordinates.
type Coordinates struct {
Lat float32
Lng float32
Name string
}
// Represents a response from the Photon API.
type Response struct {
Features []Feature
}
// Represents features object in the Response.
type Feature struct {
Geometry Geometry
Properties Properties
}
// Represents geometry object in the Response.
type Geometry struct {
Coordinates []float32
}
// Represents properties object in the Response.
type Properties struct {
CountryCode string
Name string
State string
}
// Returns true of geocoding is possible.
func Enabled() bool {
return len(os.Getenv("PEACH_PHOTON_URL")) > 0
}
func Url() (*url.URL, error) {
if !Enabled() {
return nil, fmt.Errorf("geocoding not enabled")
}
pu, err := url.Parse(os.Getenv("PEACH_PHOTON_URL"))
if err != nil {
return nil, err
}
if len(pu.Path) < 1 || pu.Path == "/" {
pu.Path = "/api"
}
return pu, nil
}
// Returns a list of matching Coordinates for a given location.
func Geocode(location string) ([]Coordinates, error) {
mCoords := []Coordinates{} // Matching coordinates
location = strings.TrimSpace(location)
if len(location) < 2 {
return mCoords, fmt.Errorf("geocode: location invalid")
}
// Construct request.
u, err := Url()
if err != nil {
return mCoords, fmt.Errorf("geocode: %v", err)
}
q := url.Values{}
q.Add("q", location)
q.Add("osm_tag", "place:city")
q.Add("osm_tag", "place:town")
q.Add("limit", "10")
u.RawQuery = q.Encode()
// Make request.
resp, err := client.Get(u.String())
if err != nil {
return mCoords, fmt.Errorf("geocode: get: %v", err)
}
// Parse response body.
body, err := io.ReadAll(resp.Body)
if err != nil {
return mCoords, fmt.Errorf("geocode: body: %v", err)
}
// Check if the request failed.
if resp.StatusCode != 200 {
return mCoords, fmt.Errorf("geocode: %s", body)
}
// Unmarshal
r := new(Response)
err = json.Unmarshal(body, r)
if err != nil {
return mCoords, fmt.Errorf("geocode: decode: %v", err)
}
// Make matching coordinates list.
names := map[string]bool{}
for _, feature := range r.Features {
if feature.Properties.CountryCode != "US" {
continue // skip
}
c := Coordinates{}
c.Lat = feature.Geometry.Coordinates[1]
c.Lng = feature.Geometry.Coordinates[0]
c.Name = fmt.Sprintf("%s, %s",
feature.Properties.Name,
feature.Properties.State,
)
if names[c.Name] {
continue // skip.
}
names[c.Name] = true
mCoords = append(mCoords, c)
}
return mCoords, nil
}
|