summaryrefslogtreecommitdiffstats
path: root/photon/photon.go
blob: 44b4b926cea8061a9f9eea3056acf0be60ce3925 (plain) (blame)
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 Photon 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
}