-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgeocoding.go
More file actions
217 lines (191 loc) · 9.03 KB
/
Copy pathgeocoding.go
File metadata and controls
217 lines (191 loc) · 9.03 KB
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package location
import "context"
// GeocodeOptions is the normalized input to Geocoding.Geocode.
type GeocodeOptions struct {
Address string
// Language is an optional BCP-47 language tag ("" = provider default).
Language string
// CountryFilter is an optional hard filter of ISO 3166-1 alpha-2 codes.
CountryFilter []string
Passthrough *Passthrough
}
// ReverseGeocodeOptions is the normalized input to Geocoding.ReverseGeocode.
type ReverseGeocodeOptions struct {
Location LatLng
Language string
Passthrough *Passthrough
}
// AutocompleteOptions is the normalized input to Geocoding.Autocomplete.
type AutocompleteOptions struct {
Input string
// Location and Radius bias results toward a point (ignored by some providers).
Location *LatLng
Radius *float64
Language string
// CountryFilter is an optional hard filter of ISO 3166-1 alpha-2 codes, the
// same vocabulary as GeocodeOptions.CountryFilter. All five geocoders support
// one natively; each connector translates it into that vendor's parameter
// (Google includedRegionCodes, Mapbox country, TomTom countrySet, Esri
// countryCode, HERE in=countryCode with alpha-3 codes).
//
// Two provider behaviours are worth knowing. Google takes ccTLD codes rather
// than ISO — the two disagree on the United Kingdom (GB → uk), which the
// connector translates — and setting the filter also stops Google returning
// *query* predictions, so it changes which kinds of suggestion come back.
// HERE requires the filter to be accompanied by a Location.
CountryFilter []string
// SessionToken groups this keystroke with the rest of one user interaction,
// closed by the PlaceDetails call carrying the SAME value.
//
// Google-only here: Places Autocomplete is billed per SESSION when a token
// ties the keystroke requests to the details call that closes them, and per
// REQUEST when it does not — so omitting it on a keystroke-driven UI multiplies
// the bill by the number of characters typed. Google documents a v4 UUID.
//
// The wrapper holds no state and cannot correlate the calls, so generating and
// threading the value is the caller's job. Ignored by every other provider.
// (Mapbox needs the same concept but only on PlaceDetails, because its suggest
// leg generates its own token — see PlaceDetailsOptions.SessionToken.)
SessionToken string
Passthrough *Passthrough
}
// Viewport is the recommended map viewport for a geocode candidate.
type Viewport struct {
Southwest LatLng `json:"southwest"`
Northeast LatLng `json:"northeast"`
}
// GeocodeCandidate is one normalized geocoding result.
type GeocodeCandidate struct {
FormattedAddress string `json:"formattedAddress"`
Location LatLng `json:"location"`
PlaceID string `json:"placeId,omitempty"`
Viewport *Viewport `json:"viewport,omitempty"`
}
// GeocodeResult is the normalized forward-geocoding response.
type GeocodeResult struct {
Candidates []GeocodeCandidate `json:"candidates"`
Raw any `json:"raw"`
}
// ReverseGeocodeResult mirrors GeocodeResult (a list, even for providers whose
// reverse endpoint natively returns a single result).
type ReverseGeocodeResult struct {
Candidates []GeocodeCandidate `json:"candidates"`
Raw any `json:"raw"`
}
// AutocompletePrediction is one normalized autocomplete suggestion.
type AutocompletePrediction struct {
Description string `json:"description"`
PlaceID string `json:"placeId,omitempty"`
// StructuredFormat is the prediction split into its primary and secondary
// parts, when the provider returns them as distinct fields.
//
// This is what lets a UI render the usual two-line suggestion — the place name
// above a greyed-out address — without guessing where to split Description.
// Splitting on the first comma is the workaround this replaces, and it breaks
// on names containing commas and on locales that order the address differently.
//
// Never synthesized. Non-nil only when the provider supplies a genuinely
// distinct main part: Google (structuredFormat.mainText), Mapbox (name /
// place_formatted), HERE (title / address.label), TomTom (poi.name /
// address.freeformAddress — absent for street results, which have no poi.name).
// Esri returns a single flat text field and is the genuine gap.
//
// So nil means "this provider/row has no distinct main part", and Description
// remains the thing to render.
StructuredFormat *AutocompleteStructuredFormat `json:"structuredFormat,omitempty"`
}
// AutocompleteStructuredFormat is the two display parts of a prediction.
//
// SecondaryText is empty when the provider has a main part but no address —
// HERE's query-type suggestions are exactly that shape, and emitting a fabricated
// value there would render as a blank second line.
type AutocompleteStructuredFormat struct {
MainText string `json:"mainText"`
SecondaryText string `json:"secondaryText,omitempty"`
}
// PlaceDetailsOptions is the input for a place-details lookup: resolve a PlaceID
// from an autocomplete prediction into a full candidate.
//
// This is deliberately ONE operation rather than two. "Place details" and "geocode
// by place id" are the same vendor call — every provider resolves its own opaque id
// to the same address+coordinates payload — so splitting them would put two names
// on one request.
//
// PlaceID must come from the SAME provider's Autocomplete: these ids are
// provider-scoped and not interchangeable.
type PlaceDetailsOptions struct {
PlaceID string
Language string
// SessionToken closes a billable session opened by Autocomplete. Honoured by
// Mapbox and Google; ignored by every other provider.
//
// Both vendors bill the autocomplete leg per *session* rather than per request,
// and a session is only one session when every call carries the SAME token:
// - Mapbox: a suggest call plus the retrieve that follows count as ONE
// billable Search Box session. Sent as `session_token`.
// - Google: the keystroke requests plus the details call that closes them
// count as ONE session; without a token each keystroke is billed as its own
// request. Sent as `sessionToken`.
//
// Omitting it, or passing a fresh one, turns a single user interaction into
// several billed ones. The wrapper holds no state and cannot correlate the
// calls, so threading it is the caller's job.
SessionToken string
// Include names optional output fields to fetch. Empty means nothing extra.
Include []PlaceDetailsInclude
Passthrough *Passthrough
}
// PlaceDetailsInclude is an opt-in token for an optional place-details output
// field, mirroring RoutingInclude.
type PlaceDetailsInclude string
const (
// IncludePlaceName populates PlaceDetailsResult.Name.
//
// On Google this adds displayName to the MANDATORY field mask, which selects the
// Pro SKU tier — the reason it is opt-in rather than always requested. Free on
// HERE/Mapbox/TomTom; unavailable on Esri.
IncludePlaceName PlaceDetailsInclude = "name"
)
// includes reports whether the caller opted into a given optional output field.
func (o PlaceDetailsOptions) includes(token PlaceDetailsInclude) bool {
for _, t := range o.Include {
if t == token {
return true
}
}
return false
}
// PlaceDetailsResult is the normalized place-details response.
//
// It returns a full GeocodeCandidate rather than a new shape, because that is what
// the operation resolves to and reusing it means a caller can feed the result
// straight into whatever already consumes geocode candidates.
type PlaceDetailsResult struct {
Candidate GeocodeCandidate `json:"candidate"`
// Name is the place's display name, when the provider returns one distinct from
// the formatted address (e.g. "Blue Bottle Coffee" vs its street address).
//
// Empty on providers that only return an address (Esri), and on Google unless
// IncludePlaceName was requested — its Place Details SKU tier is driven by the
// field mask, and displayName is a Pro-tier field. Note this is the OPPOSITE of
// Compute Routes, whose SKU is feature-driven: check per API, do not generalize.
Name string `json:"name,omitempty"`
Raw any `json:"raw"`
}
// AutocompleteResult is the normalized autocomplete response.
type AutocompleteResult struct {
Predictions []AutocompletePrediction `json:"predictions"`
Raw any `json:"raw"`
}
type geocodingConnector interface {
geocode(ctx context.Context, opts GeocodeOptions) (*GeocodeResult, error)
reverseGeocode(ctx context.Context, opts ReverseGeocodeOptions) (*ReverseGeocodeResult, error)
autocomplete(ctx context.Context, opts AutocompleteOptions) (*AutocompleteResult, error)
// placeDetails resolves a provider placeId to a full candidate.
//
// Added directly to this interface rather than as a separate capability, which
// the PHP sibling needed and the TS sibling worked around with an optional
// method: this interface is UNEXPORTED, so no consumer implements it and adding
// a method breaks nobody. Same additive outcome, no ceremony required.
placeDetails(ctx context.Context, opts PlaceDetailsOptions) (*PlaceDetailsResult, error)
}