-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
google.clj
158 lines (132 loc) · 4.33 KB
/
google.clj
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
(ns geocoder.google
(:require [clojure.spec.alpha :as s]
[clojure.string :as str]
[geocoder.util :refer [fetch-json format-location]]
[inflections.core :refer [underscore]]))
;; Results
(s/def ::types (s/coll-of string?))
(s/def ::global-code string?)
(s/def ::compound-code string?)
(s/def ::plus-code (s/keys :req-un [::compound-code ::global-code]))
(s/def ::place-id string?)
(s/def ::partial-match boolean?)
(s/def ::lng (s/and number? #(>= % -180.0) #(<= % 180.0)))
(s/def ::lat (s/and number? #(>= % -90.0) #(<= % 90.0)))
(s/def ::southwest (s/keys :req-un [::lat ::lng]))
(s/def ::northeast (s/keys :req-un [::lat ::lng]))
(s/def ::viewport (s/keys :req-un [::northeast ::southwest]))
(s/def ::location-type string?)
(s/def ::location (s/keys :req-un [::lat ::lng]))
(s/def ::geometry
(s/keys :req-un [::location ::location-type ::viewport]))
(s/def ::formatted-address string?)
(s/def ::short-name string?)
(s/def ::long-name string?)
(s/def ::address-components
(s/coll-of (s/keys :req-un [::long-name ::short-name ::types])))
(s/def ::result
(s/keys :req-un
[::address-components
::formatted-address
::geometry]
:opt-un
[::place-id
::types
::partial-match
::plus-code]))
(s/def ::results
(s/nilable (s/coll-of ::result :gen-max 3)))
;; Geocoder
(s/def ::sensor boolean?)
(s/def ::language string?)
(s/def ::key any?)
(s/def ::query-params (s/keys :req-un [::key ::language ::sensor]))
(s/def ::url string?)
(s/def ::request-method keyword?)
(s/def ::geocoder
(s/keys :req-un [::query-params ::request-method ::url]))
(defn- extract-type
"Extract the address type from response."
[response type]
(let [type (underscore (name type))]
(->> (:address-components response)
(filter #(contains? (set (:types %)) type) )
(first))))
(defn long-name
"Extract the long name of the address type from response."
[response type]
(:long-name (extract-type response type)))
(defn short-name
"Extract the short name of the address type from response."
[response type]
(:short-name (extract-type response type)))
(defn city
"Returns the city of `address`."
[address]
(:long-name (extract-type address :locality)))
(defn country
"Returns the country of `address`."
[address]
{:name (long-name address :country)
:iso-3166-1-alpha-2 (some-> (short-name address :country) str/lower-case)})
(defn location
"Returns the geographical location of `address`."
[address]
(:location (:geometry address)))
(defn street-name
"Returns the street name of `address`."
[address]
(long-name address :route))
(defn street-number
"Returns the street number of `address`."
[address]
(long-name address :street-number))
(defn postal-code
"Returns the postal code of `address`."
[address]
(long-name address :postal-code))
(defn region
"Returns the region of `address`."
[address]
(long-name address :administrative-area-level-1))
(defn- request [request]
(let [{:keys [error-message status] :as response} (fetch-json request)]
(if (contains? #{"OK" "ZERO_RESULTS"} status)
response
(throw (ex-info (str "Geocode request failed: " error-message) response)))))
(defn geocode-address
"Geocode an address."
[geocoder address & [opts]]
(when-not (str/blank? address)
(-> (update-in geocoder [:query-params] #(merge %1 opts))
(assoc-in [:query-params :address] address)
(request)
:results
not-empty)))
(s/fdef geocode-address
:args (s/cat :geocoder ::geocoder
:address string?
:opts (s/? (s/nilable map?)))
:ret ::results)
(defn geocode-location
"Geocode a geographical location."
[geocoder location & [opts]]
(-> (update-in geocoder [:query-params] #(merge %1 opts))
(assoc-in [:query-params :latlng] (format-location location))
(request)
:results
not-empty))
(s/fdef geocode-location
:args (s/cat :geocoder ::geocoder
:location ::location
:opts (s/? (s/nilable map?)))
:ret ::results)
(defn geocoder
"Returns a new Google Maps geocoder."
[& [{:keys [api-key language sensor?]}]]
{:request-method :get
:url "https://maps.google.com/maps/api/geocode/json"
:query-params
{:key api-key
:language (or language "en")
:sensor (or sensor? false)}})