@chaosity/location-client 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +264 -42
- package/dist/adapters/GeoPlaces.d.ts +30 -3
- package/dist/adapters/GeoPlaces.js +5 -1
- package/dist/cjs/adapters/GeoPlaces.d.ts +30 -3
- package/dist/cjs/adapters/GeoPlaces.js +5 -4
- package/dist/cjs/client/GeoPlacesClient.d.ts +16 -4
- package/dist/cjs/client/GeoPlacesClient.js +24 -10
- package/dist/cjs/client/commands.d.ts +159 -0
- package/dist/cjs/client/commands.js +108 -0
- package/dist/cjs/errors/LocationServiceException.d.ts +28 -1
- package/dist/cjs/errors/LocationServiceException.js +31 -2
- package/dist/cjs/index.d.ts +5 -3
- package/dist/cjs/index.js +17 -1
- package/dist/cjs/maps/mapEnums.d.ts +82 -4
- package/dist/cjs/maps/mapEnums.js +98 -5
- package/dist/cjs/maps/mapStyle.d.ts +87 -10
- package/dist/cjs/maps/mapStyle.js +28 -5
- package/dist/cjs/maps/staticMap.d.ts +40 -0
- package/dist/cjs/maps/staticMap.js +4 -0
- package/dist/cjs/server/LocationServiceConnector.d.ts +23 -5
- package/dist/cjs/server/LocationServiceConnector.js +32 -14
- package/dist/cjs/transport/endpoints.js +3 -0
- package/dist/cjs/transport/http.d.ts +2 -2
- package/dist/cjs/transport/http.js +2 -2
- package/dist/cjs/types/index.d.ts +6 -5
- package/dist/cjs/utils/tokenClaims.d.ts +37 -13
- package/dist/cjs/utils/tokenClaims.js +36 -14
- package/dist/client/GeoPlacesClient.d.ts +16 -4
- package/dist/client/GeoPlacesClient.js +24 -10
- package/dist/client/commands.d.ts +159 -0
- package/dist/client/commands.js +97 -0
- package/dist/errors/LocationServiceException.d.ts +28 -1
- package/dist/errors/LocationServiceException.js +30 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +7 -2
- package/dist/maps/mapEnums.d.ts +82 -4
- package/dist/maps/mapEnums.js +97 -4
- package/dist/maps/mapStyle.d.ts +87 -10
- package/dist/maps/mapStyle.js +28 -5
- package/dist/maps/staticMap.d.ts +40 -0
- package/dist/maps/staticMap.js +4 -0
- package/dist/server/LocationServiceConnector.d.ts +23 -5
- package/dist/server/LocationServiceConnector.js +32 -14
- package/dist/transport/endpoints.js +3 -0
- package/dist/transport/http.d.ts +2 -2
- package/dist/transport/http.js +2 -2
- package/dist/types/index.d.ts +6 -5
- package/dist/utils/tokenClaims.d.ts +37 -13
- package/dist/utils/tokenClaims.js +36 -14
- package/package.json +1 -1
- package/dist/cjs/utils/roundPosition.d.ts +0 -66
- package/dist/cjs/utils/roundPosition.js +0 -109
- package/dist/utils/roundPosition.d.ts +0 -66
- package/dist/utils/roundPosition.js +0 -104
package/README.md
CHANGED
|
@@ -38,9 +38,10 @@ npm install maplibre-gl @maplibre/maplibre-gl-geocoder
|
|
|
38
38
|
|
|
39
39
|
- **Custom Authentication**: Uses Bearer tokens instead of AWS SigV4
|
|
40
40
|
- **AWS SDK Commands**: Full access to all AWS Location Service commands
|
|
41
|
+
- **Address Verification**: `verifyAddress(placeId)` returns the one Places result you may store — see [Verifying an address](#verifying-an-address)
|
|
41
42
|
- **Data Type Utilities**: Built-in GeoJSON conversion utilities
|
|
42
43
|
- **MapLibre Integration**: Adapter for MapLibre GL Geocoder and `createTransformRequest` helper
|
|
43
|
-
- **Map Style Control**: Fetch and customize map style descriptors
|
|
44
|
+
- **Map Style Control**: Fetch and customize map style descriptors — color scheme, points of interest, label language, and terrain, 3D buildings, traffic and more as [plan features](#plan-features)
|
|
44
45
|
- **Map Language**: Switch map label language client-side with zero API calls
|
|
45
46
|
- **POI Layer Control**: Toggle point-of-interest categories on/off by layer
|
|
46
47
|
- **Server Utilities**: `getClientConfig()` with auto-env detection and token caching
|
|
@@ -101,10 +102,78 @@ const client = new GeoPlacesClient({
|
|
|
101
102
|
})
|
|
102
103
|
|
|
103
104
|
const response: SuggestCommandOutput = await client.send(
|
|
104
|
-
new SuggestCommand({
|
|
105
|
+
new SuggestCommand({
|
|
106
|
+
QueryText: 'Vancouver',
|
|
107
|
+
MaxResults: 5,
|
|
108
|
+
// Suggest takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
|
|
109
|
+
BiasPosition: [-123.1207, 49.2827],
|
|
110
|
+
}),
|
|
105
111
|
)
|
|
106
112
|
```
|
|
107
113
|
|
|
114
|
+
### Verifying an address
|
|
115
|
+
|
|
116
|
+
`POST /address/verify` resolves one PlaceId to the full place record plus
|
|
117
|
+
`verified`. The PlaceId can come from any suggestion, autocomplete, geocode or
|
|
118
|
+
place result, a unit's included. The answer is **the one Places result you may
|
|
119
|
+
store**; every other result is for display only. The exception is a place in
|
|
120
|
+
Japan, which may not be stored at all.
|
|
121
|
+
|
|
122
|
+
The flow is suggest, then an optional unit pick, then verify at submit:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import {
|
|
126
|
+
GetPlaceCommand,
|
|
127
|
+
SuggestCommand,
|
|
128
|
+
type GetPlaceCommandOutput,
|
|
129
|
+
type SuggestCommandOutput,
|
|
130
|
+
} from '@chaosity/location-client'
|
|
131
|
+
|
|
132
|
+
// 1. While the person types: suggestions, for display.
|
|
133
|
+
const suggestions: SuggestCommandOutput = await client.send(
|
|
134
|
+
// Suggest takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
|
|
135
|
+
new SuggestCommand({
|
|
136
|
+
QueryText: '100 George St, Sydney',
|
|
137
|
+
BiasPosition: [151.2093, -33.8688],
|
|
138
|
+
}),
|
|
139
|
+
)
|
|
140
|
+
const picked = suggestions.ResultItems?.[0]?.Place?.PlaceId
|
|
141
|
+
|
|
142
|
+
// 2. Optionally, offer the building's units. Each has its own PlaceId.
|
|
143
|
+
const building: GetPlaceCommandOutput = await client.send(
|
|
144
|
+
new GetPlaceCommand({
|
|
145
|
+
PlaceId: picked!,
|
|
146
|
+
AdditionalFeatures: ['SecondaryAddresses'],
|
|
147
|
+
}),
|
|
148
|
+
)
|
|
149
|
+
const unit = building.SecondaryAddresses?.[0]?.PlaceId
|
|
150
|
+
|
|
151
|
+
// 3. At submit, once: verify the PlaceId that was chosen.
|
|
152
|
+
const answer = await client.verifyAddress(unit ?? picked!)
|
|
153
|
+
if (answer.verified) {
|
|
154
|
+
// `answer` is the record you may keep.
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- `verified` is `true` for a `PointAddress`, or a `SecondaryAddress` (a unit).
|
|
159
|
+
It is `false` for anything else: an interpolated address, a street, a
|
|
160
|
+
locality, a point of interest. A `false` is still a 200, so the call
|
|
161
|
+
resolves; it does not throw.
|
|
162
|
+
- **Every verify is billed, whether or not the address verifies.** Call it
|
|
163
|
+
once per chosen PlaceId, at submit. Never call it per keystroke, or on every
|
|
164
|
+
pick.
|
|
165
|
+
- Only the `PlaceId` is sent. `VerifyAddressCommandInput` has no other field,
|
|
166
|
+
because the service would drop `Language`, `PoliticalView` and
|
|
167
|
+
`AdditionalFeatures`. A repeat verify of the same PlaceId may be answered
|
|
168
|
+
from the service's own store, and is billed all the same.
|
|
169
|
+
- Keep the PlaceId you sent beside the answer. The answer's own `PlaceId` can
|
|
170
|
+
differ, and for a unit it does. The service does not accept that one back,
|
|
171
|
+
while the one you sent verifies again.
|
|
172
|
+
- `client.verifyAddress(placeId)` is
|
|
173
|
+
`client.send(new VerifyAddressCommand({ PlaceId: placeId }))`, typed as
|
|
174
|
+
`VerifyAddressResponse`. `connector.verifyAddress` does the same on the
|
|
175
|
+
server.
|
|
176
|
+
|
|
108
177
|
### MapLibre Map Integration
|
|
109
178
|
|
|
110
179
|
Use `fetchMapStyle` to fetch a style descriptor with authentication and optional customization, and `createTransformRequest` to attach Bearer tokens to all subsequent tile/glyph/sprite requests:
|
|
@@ -118,8 +187,6 @@ import maplibregl from 'maplibre-gl'
|
|
|
118
187
|
|
|
119
188
|
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
120
189
|
colorScheme: 'Dark',
|
|
121
|
-
terrain: 'Terrain3D',
|
|
122
|
-
buildings: 'Buildings3D',
|
|
123
190
|
language: 'fr',
|
|
124
191
|
})
|
|
125
192
|
|
|
@@ -128,11 +195,20 @@ const map = new maplibregl.Map({
|
|
|
128
195
|
style,
|
|
129
196
|
center: [-123.12, 49.28],
|
|
130
197
|
zoom: 10,
|
|
131
|
-
maxPitch: 85,
|
|
132
198
|
transformRequest: createTransformRequest(apiUrl, getToken),
|
|
133
199
|
})
|
|
134
200
|
```
|
|
135
201
|
|
|
202
|
+
3D terrain and buildings need the `terrain` and `buildings` plan features — see [Plan features](#plan-features):
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
206
|
+
terrain: 'Terrain3D',
|
|
207
|
+
buildings: 'Buildings3D',
|
|
208
|
+
})
|
|
209
|
+
// then `maxPitch: 85` on the map, so the camera can tilt to see them
|
|
210
|
+
```
|
|
211
|
+
|
|
136
212
|
### Switching Map Language
|
|
137
213
|
|
|
138
214
|
Change map label language instantly on the client side — no API calls needed:
|
|
@@ -170,6 +246,74 @@ setAllPoiVisibility(map, false)
|
|
|
170
246
|
|
|
171
247
|
Available categories: `food_drink`, `entertainment`, `sights`, `transit`, `accommodations`, `leisure`, `shopping`, `business`, `facilities`, `areas`, `parks`.
|
|
172
248
|
|
|
249
|
+
### Plan features
|
|
250
|
+
|
|
251
|
+
Some options are features of the application's plan. An application whose plan
|
|
252
|
+
does not include one is refused **403 `FeatureNotEntitledException`** before
|
|
253
|
+
anything is fetched upstream, so the refusal is not billed, and the message
|
|
254
|
+
names each refused feature and the option that asked for it:
|
|
255
|
+
|
|
256
|
+
> This application's plan does not include the map feature terrain (terrain=Terrain3D).
|
|
257
|
+
|
|
258
|
+
| Feature | What asks for it |
|
|
259
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
260
|
+
| `satellite` | the `Satellite` and `Hybrid` styles, and a static map with no `style` — Satellite is its default |
|
|
261
|
+
| `terrain` | `terrain` (`Hillshade`, `Terrain3D`) |
|
|
262
|
+
| `buildings` | `buildings` |
|
|
263
|
+
| `contours` | `contourDensity` |
|
|
264
|
+
| `traffic` | `traffic` |
|
|
265
|
+
| `travel-modes` | `travelModes` |
|
|
266
|
+
| `political-view` | `politicalView`, on a style or a static map |
|
|
267
|
+
| `rich place data` | `AdditionalFeatures` `Access`, `Contact`, `Phonemes` or `TimeZone` on a Places command, and the `GeoPlaces` details that ask for them |
|
|
268
|
+
|
|
269
|
+
Everything else is open to every plan: the Standard and Monochrome styles,
|
|
270
|
+
`colorScheme`, `poiDensity`, `poiCategories`, a label language, and the other
|
|
271
|
+
`AdditionalFeatures`. Which plan includes which feature is on the
|
|
272
|
+
[pricing page](https://chaosity.cloud/pricing), and deliberately not here: it can
|
|
273
|
+
change without a release of this package. In the source, every gated option and
|
|
274
|
+
value carries a `@planFeature` tag naming its feature, so your editor shows it.
|
|
275
|
+
|
|
276
|
+
The token carries no feature list, so there is nothing to check beforehand — the
|
|
277
|
+
403 is how you find out, and it is typed:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import {
|
|
281
|
+
LocationServiceException,
|
|
282
|
+
fetchMapStyle,
|
|
283
|
+
} from '@chaosity/location-client'
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
map.setStyle(await fetchMapStyle(apiUrl, 'Standard', getToken, options))
|
|
287
|
+
} catch (err) {
|
|
288
|
+
if (err instanceof LocationServiceException && err.isFeatureNotEntitled) {
|
|
289
|
+
showMessage(err.message) // names the refused feature
|
|
290
|
+
} else throw err
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`isAuth` is true for it too, as for every 401 and 403 — but so it is for bad
|
|
295
|
+
credentials, an `Origin` the application does not allow, and a route outside the
|
|
296
|
+
plan, which all need different fixes. Branch on `isFeatureNotEntitled`, or on
|
|
297
|
+
`code` against the exported `FEATURE_NOT_ENTITLED`.
|
|
298
|
+
|
|
299
|
+
**What MapLibre fetches for itself is refused outside this package.**
|
|
300
|
+
`fetchMapStyle`, `fetchStaticMap` and the Places commands reject with the typed
|
|
301
|
+
error above. A style URL from `buildMapStyleUrl` handed to `setStyle`, and every
|
|
302
|
+
tile, is fetched by MapLibre, so a refusal there arrives as a map `error` event:
|
|
303
|
+
`error.status` is 403 and `error.body` is a `Blob` holding the same
|
|
304
|
+
`{ message, code }` JSON.
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { FEATURE_NOT_ENTITLED } from '@chaosity/location-client'
|
|
308
|
+
|
|
309
|
+
map.on('error', async ({ error }) => {
|
|
310
|
+
const { status, body } = error as { status?: number; body?: Blob }
|
|
311
|
+
if (status !== 403 || !body) return
|
|
312
|
+
const { code, message } = JSON.parse(await body.text())
|
|
313
|
+
if (code === FEATURE_NOT_ENTITLED) showMessage(message)
|
|
314
|
+
})
|
|
315
|
+
```
|
|
316
|
+
|
|
173
317
|
### MapLibre Geocoder Integration
|
|
174
318
|
|
|
175
319
|
Requires the optional peers: `npm install maplibre-gl @maplibre/maplibre-gl-geocoder`
|
|
@@ -219,6 +363,22 @@ await client.send(command)
|
|
|
219
363
|
|
|
220
364
|
When `getToken` is provided, it is called on every request so token updates are reflected without recreating the client.
|
|
221
365
|
|
|
366
|
+
`client.getAppConfig()` — and `await connector.getAppConfig()` on the server —
|
|
367
|
+
returns the application's own settings as its access token carries them:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
{
|
|
371
|
+
allowedResources?: string[] // e.g. 'GET /maps/static/{fileName}'
|
|
372
|
+
allowedDomain?: string // the domain its requests must come from
|
|
373
|
+
countries?: string[] // ISO 3166-1 alpha-2, once a scope is configured
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
For display — "static maps are not in your plan", the domain to show beside an
|
|
378
|
+
`Origin not allowed` 403 — never for refusing or shaping a request. The token
|
|
379
|
+
can be up to fifteen minutes old, and the API reads all three fresh from the
|
|
380
|
+
application on every request.
|
|
381
|
+
|
|
222
382
|
`refreshToken` covers the case `getToken` cannot. `getToken` is synchronous —
|
|
223
383
|
MapLibre's `transformRequest` requires that — so it can only ever return the
|
|
224
384
|
token already in hand, and a token the API stops accepting **before** its `exp`
|
|
@@ -232,8 +392,8 @@ worth a second round trip. A 403 is never retried: a new token cannot fix an
|
|
|
232
392
|
#### Request options
|
|
233
393
|
|
|
234
394
|
Every call in this package takes the same options object — `client.send`,
|
|
235
|
-
`connector.send`, `
|
|
236
|
-
arrives as a `LocationServiceException`.
|
|
395
|
+
`connector.send`, `verifyAddress` on either, `fetchMapStyle` and
|
|
396
|
+
`fetchStaticMap` — and every failure arrives as a `LocationServiceException`.
|
|
237
397
|
|
|
238
398
|
```typescript
|
|
239
399
|
await client.send(command, {
|
|
@@ -245,12 +405,13 @@ await client.send(command, {
|
|
|
245
405
|
```
|
|
246
406
|
|
|
247
407
|
`overallTimeoutMs` is the one worth setting deliberately, and it defaults to
|
|
248
|
-
30 s. `timeoutMs` bounds an attempt, not a call:
|
|
249
|
-
|
|
250
|
-
the caller for about two minutes — past any Lambda
|
|
251
|
-
more time than the call has left, and a retry that
|
|
252
|
-
than the remaining budget is not made at all. You get
|
|
253
|
-
instead, `retryAfterMs` intact, so you can queue the
|
|
408
|
+
30 s. `timeoutMs` bounds an attempt, not a call: nothing bounds the
|
|
409
|
+
`Retry-After` a 429 carries, and honouring one of 60 s literally across two
|
|
410
|
+
retries would have blocked the caller for about two minutes — past any Lambda
|
|
411
|
+
budget. Now no attempt gets more time than the call has left, and a retry that
|
|
412
|
+
would have to wait longer than the remaining budget is not made at all. You get
|
|
413
|
+
the API's own error back instead, `retryAfterMs` intact, so you can queue the
|
|
414
|
+
work rather than guess.
|
|
254
415
|
|
|
255
416
|
The map helpers take them as a trailing argument:
|
|
256
417
|
|
|
@@ -264,7 +425,7 @@ const style = await fetchMapStyle(
|
|
|
264
425
|
)
|
|
265
426
|
const blob = await fetchStaticMap(
|
|
266
427
|
apiUrl,
|
|
267
|
-
{ width: 640, height: 400, center },
|
|
428
|
+
{ width: 640, height: 400, center, zoom: 14, style: 'Standard' },
|
|
268
429
|
getToken,
|
|
269
430
|
{ signal },
|
|
270
431
|
)
|
|
@@ -311,11 +472,7 @@ import { fetchMapStyle } from '@chaosity/location-client'
|
|
|
311
472
|
|
|
312
473
|
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
313
474
|
colorScheme: 'Dark',
|
|
314
|
-
|
|
315
|
-
buildings: 'Buildings3D',
|
|
316
|
-
contourDensity: 'Medium',
|
|
317
|
-
traffic: 'All',
|
|
318
|
-
travelModes: ['Truck', 'Transit'],
|
|
475
|
+
poiDensity: 'Sparse',
|
|
319
476
|
language: 'fr',
|
|
320
477
|
})
|
|
321
478
|
|
|
@@ -325,6 +482,22 @@ const map = new maplibregl.Map({
|
|
|
325
482
|
})
|
|
326
483
|
```
|
|
327
484
|
|
|
485
|
+
The overlays need the `terrain`, `buildings`, `contours`, `traffic` and `travel-modes` plan features — see [Plan features](#plan-features):
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
489
|
+
terrain: 'Terrain3D',
|
|
490
|
+
buildings: 'Buildings3D',
|
|
491
|
+
contourDensity: 'Medium',
|
|
492
|
+
traffic: 'All',
|
|
493
|
+
travelModes: ['Truck', 'Transit'],
|
|
494
|
+
})
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
This is the call that surfaces a refusal as `LocationServiceException`; a URL
|
|
498
|
+
MapLibre fetches for itself reports it as a map `error` event instead (see
|
|
499
|
+
[Plan features](#plan-features)).
|
|
500
|
+
|
|
328
501
|
#### buildMapStyleUrl
|
|
329
502
|
|
|
330
503
|
Builds the style descriptor URL without fetching. Useful when you want to pass the URL directly to MapLibre (e.g. without pre-applying language).
|
|
@@ -334,23 +507,26 @@ import { buildMapStyleUrl } from '@chaosity/location-client'
|
|
|
334
507
|
|
|
335
508
|
const url = buildMapStyleUrl(apiUrl, 'Standard', {
|
|
336
509
|
colorScheme: 'Dark',
|
|
337
|
-
terrain: 'Hillshade',
|
|
338
510
|
})
|
|
339
511
|
```
|
|
340
512
|
|
|
341
513
|
#### MapStyleOptions
|
|
342
514
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
515
|
+
The options, the values each accepts, and which of them are plan features are
|
|
516
|
+
documented on the type — `MapStyleOptions` in `src/maps/mapStyle.ts`, with the
|
|
517
|
+
value lists in `src/maps/mapEnums.ts` — and your editor shows them as you type.
|
|
518
|
+
They are not copied here, because the copy that used to be here drifted from the
|
|
519
|
+
source.
|
|
520
|
+
|
|
521
|
+
Every accepted value is exported as an array (`POI_DENSITIES`,
|
|
522
|
+
`STYLE_POI_CATEGORIES`, `TRAFFIC_MODES`, …) so a picker can be built from it. A
|
|
523
|
+
list tagged `@planFeature` — `MAP_STYLES`, `TERRAINS`, `TRAFFIC_MODES` and the
|
|
524
|
+
others in [Plan features](#plan-features) — holds values some plans are refused,
|
|
525
|
+
so leave those out of a picker or mark them. Values are case sensitive, and some
|
|
526
|
+
combinations are the API's to refuse — for example `traffic: 'All'` on
|
|
527
|
+
Satellite. There is no `language` here:
|
|
528
|
+
`fetchMapStyle` takes `language` separately and applies it to the descriptor
|
|
529
|
+
itself, because the service's style descriptor has no language parameter.
|
|
354
530
|
|
|
355
531
|
#### applyMapLanguage
|
|
356
532
|
|
|
@@ -378,10 +554,12 @@ setAllPoiVisibility(map, false)
|
|
|
378
554
|
|
|
379
555
|
#### Available Commands
|
|
380
556
|
|
|
381
|
-
|
|
557
|
+
The Amazon Location Places commands, with the SDK's inputs minus the two
|
|
558
|
+
fields below:
|
|
382
559
|
|
|
383
560
|
```typescript
|
|
384
561
|
import {
|
|
562
|
+
AutocompleteCommand,
|
|
385
563
|
SuggestCommand,
|
|
386
564
|
GeocodeCommand,
|
|
387
565
|
ReverseGeocodeCommand,
|
|
@@ -391,6 +569,29 @@ import {
|
|
|
391
569
|
} from '@chaosity/location-client'
|
|
392
570
|
```
|
|
393
571
|
|
|
572
|
+
**Two SDK fields are not accepted: `IntendedUse` and `Key`.** The service
|
|
573
|
+
removes both from every request — `IntendedUse` would choose the price bucket,
|
|
574
|
+
and `Key` would bill an Amazon Location key that is not the service's — so the
|
|
575
|
+
commands exported here are typed without them, and passing either is a compile
|
|
576
|
+
error rather than a field that is sent and silently ignored. The exported
|
|
577
|
+
`<Name>CommandInput` and `<Name>Request` types are narrowed the same way. At
|
|
578
|
+
runtime nothing is removed: the request body is your input, unchanged, and a
|
|
579
|
+
field cast past the type is stripped by the service all the same.
|
|
580
|
+
|
|
581
|
+
**`AdditionalFeatures` `Access`, `Contact`, `Phonemes` and `TimeZone` are rich
|
|
582
|
+
place data**, a plan feature. On a plan without it the request
|
|
583
|
+
is refused 403 `FeatureNotEntitledException` — see
|
|
584
|
+
[Plan features](#plan-features). `SecondaryAddresses`, `Intersections`,
|
|
585
|
+
`CrossReferences` and `Core` are open to every plan.
|
|
586
|
+
|
|
587
|
+
`VerifyAddressCommand` is this package's own: `POST /address/verify` has no
|
|
588
|
+
SDK command. Its input is `{ PlaceId }` and nothing else, and it answers a
|
|
589
|
+
`VerifyAddressResponse` — see [Verifying an address](#verifying-an-address).
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
592
|
+
import { VerifyAddressCommand } from '@chaosity/location-client'
|
|
593
|
+
```
|
|
594
|
+
|
|
394
595
|
#### Data Type Utilities
|
|
395
596
|
|
|
396
597
|
GeoJSON conversion utilities from `@aws/amazon-location-utilities-datatypes`:
|
|
@@ -460,7 +661,11 @@ const connector = new LocationServiceConnector({
|
|
|
460
661
|
})
|
|
461
662
|
|
|
462
663
|
const result = await connector.send(
|
|
463
|
-
new SuggestCommand({
|
|
664
|
+
new SuggestCommand({
|
|
665
|
+
QueryText: 'Vancouver',
|
|
666
|
+
// Suggest takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
|
|
667
|
+
BiasPosition: [-123.1207, 49.2827],
|
|
668
|
+
}),
|
|
464
669
|
)
|
|
465
670
|
```
|
|
466
671
|
|
|
@@ -485,17 +690,30 @@ const connector = new LocationServiceConnector({
|
|
|
485
690
|
})
|
|
486
691
|
```
|
|
487
692
|
|
|
488
|
-
##
|
|
693
|
+
## Coordinates Are Sent As You Supply Them
|
|
694
|
+
|
|
695
|
+
Both `GeoPlacesClient` and `LocationServiceConnector` put your command input on
|
|
696
|
+
the wire unchanged. `BiasPosition` and `QueryPosition` arrive at the service at
|
|
697
|
+
the precision you passed.
|
|
489
698
|
|
|
490
|
-
`BiasPosition`
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
result quality — bias is approximate by nature.
|
|
699
|
+
Earlier versions rounded `BiasPosition` to a grid — 3 decimal places by
|
|
700
|
+
default — so that nearby callers could share a cached upstream answer. Requests
|
|
701
|
+
are no longer cached, so the rounding had nothing left to share and only
|
|
702
|
+
lowered the precision the geocoder worked from.
|
|
495
703
|
|
|
496
|
-
|
|
704
|
+
That is not a coarser result, it is a different one. A 3 dp grid moves a
|
|
705
|
+
coordinate by up to ~70 m, depending where in its cell the coordinate falls,
|
|
706
|
+
and the places a search returns change well inside that distance: measured
|
|
707
|
+
against this service, a bias moved ~70 m returned a different set of nearby
|
|
708
|
+
places, not the same set in a different order. If you were relying on the
|
|
709
|
+
rounding to group nearby requests, round before you call.
|
|
497
710
|
|
|
498
|
-
|
|
711
|
+
One coordinate is normalised: a static map's `center`, `bounding-box` and
|
|
712
|
+
`bounded-positions` are rounded to six decimals — ~10 cm, below one pixel of a
|
|
713
|
+
raster render. That is this library's choice, well inside what the service
|
|
714
|
+
accepts: at most fourteen decimals per number, and at most 36 characters for
|
|
715
|
+
the pair. A value straight from `map.getCenter()` carries fifteen or sixteen
|
|
716
|
+
decimals and fails the first of those. A format rule, not a precision policy.
|
|
499
717
|
|
|
500
718
|
## Logging
|
|
501
719
|
|
|
@@ -520,7 +738,11 @@ Full TypeScript support with types from AWS SDK:
|
|
|
520
738
|
import type { SuggestCommandOutput } from '@chaosity/location-client'
|
|
521
739
|
|
|
522
740
|
const response: SuggestCommandOutput = await client.send(
|
|
523
|
-
new SuggestCommand({
|
|
741
|
+
new SuggestCommand({
|
|
742
|
+
QueryText: 'Vancouver',
|
|
743
|
+
// Suggest takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
|
|
744
|
+
BiasPosition: [-123.1207, 49.2827],
|
|
745
|
+
}),
|
|
524
746
|
)
|
|
525
747
|
```
|
|
526
748
|
|
|
@@ -25,15 +25,42 @@ import type { GeoPlacesClient } from '../client/GeoPlacesClient.js';
|
|
|
25
25
|
* Worth knowing before enabling `contact`: for a street address it costs
|
|
26
26
|
* Advanced and returns no contact field at all — only points of interest carry
|
|
27
27
|
* one. On an address-completion flow that is 3x the price for nothing.
|
|
28
|
+
*
|
|
29
|
+
* `access`, `contact` and `timeZone` are also RICH PLACE DATA, a feature of the
|
|
30
|
+
* application's plan (#55). On a plan without it, the lookup is refused 403
|
|
31
|
+
* `FeatureNotEntitledException` — `isFeatureNotEntitled` on the error — and
|
|
32
|
+
* nothing is returned or billed. `secondaryAddresses` is open to every plan.
|
|
33
|
+
* Which plans include it: https://chaosity.cloud/pricing.
|
|
28
34
|
*/
|
|
29
35
|
export interface GeoPlacesDetailOptions {
|
|
30
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Entrance/exit points. Moves the request to the Advanced bucket.
|
|
38
|
+
*
|
|
39
|
+
* Refused on a plan without it: 403 `FeatureNotEntitledException` (see
|
|
40
|
+
* `FEATURE_NOT_ENTITLED`).
|
|
41
|
+
*
|
|
42
|
+
* @planFeature rich place data
|
|
43
|
+
*/
|
|
31
44
|
access?: boolean;
|
|
32
45
|
/** Unit and sub-address detail. Stays in the Core bucket — free to enable. */
|
|
33
46
|
secondaryAddresses?: boolean;
|
|
34
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Phone/website, POIs only. Moves the request to the Advanced bucket.
|
|
49
|
+
*
|
|
50
|
+
* Refused on a plan without it: 403 `FeatureNotEntitledException` (see
|
|
51
|
+
* `FEATURE_NOT_ENTITLED`).
|
|
52
|
+
*
|
|
53
|
+
* @planFeature rich place data
|
|
54
|
+
*/
|
|
35
55
|
contact?: boolean;
|
|
36
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* IANA zone and offset. Moves the request to the Advanced bucket.
|
|
58
|
+
*
|
|
59
|
+
* Refused on a plan without it: 403 `FeatureNotEntitledException` (see
|
|
60
|
+
* `FEATURE_NOT_ENTITLED`).
|
|
61
|
+
*
|
|
62
|
+
* @planFeature rich place data
|
|
63
|
+
*/
|
|
37
64
|
timeZone?: boolean;
|
|
38
65
|
}
|
|
39
66
|
export interface GeoPlacesOptions {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GetPlaceAdditionalFeature, } from '@aws-sdk/client-geo-places';
|
|
2
2
|
import { geocodeResponseToFeatureCollection, getPlaceResponseToFeatureCollection, reverseGeocodeResponseToFeatureCollection, } from '@aws/amazon-location-utilities-datatypes';
|
|
3
3
|
import debug from 'debug';
|
|
4
|
+
import {
|
|
5
|
+
// The package's own narrowed commands, not the SDK's: an input typed with
|
|
6
|
+
// the SDK's would admit IntendedUse and Key, which the service strips (#40).
|
|
7
|
+
GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SuggestCommand, } from '../client/commands.js';
|
|
4
8
|
const log = debug('location-client:geocoder');
|
|
5
9
|
/**
|
|
6
10
|
* Give the control the fields it renders, not just the ones GeoJSON needs.
|
|
@@ -25,15 +25,42 @@ import type { GeoPlacesClient } from '../client/GeoPlacesClient.js';
|
|
|
25
25
|
* Worth knowing before enabling `contact`: for a street address it costs
|
|
26
26
|
* Advanced and returns no contact field at all — only points of interest carry
|
|
27
27
|
* one. On an address-completion flow that is 3x the price for nothing.
|
|
28
|
+
*
|
|
29
|
+
* `access`, `contact` and `timeZone` are also RICH PLACE DATA, a feature of the
|
|
30
|
+
* application's plan (#55). On a plan without it, the lookup is refused 403
|
|
31
|
+
* `FeatureNotEntitledException` — `isFeatureNotEntitled` on the error — and
|
|
32
|
+
* nothing is returned or billed. `secondaryAddresses` is open to every plan.
|
|
33
|
+
* Which plans include it: https://chaosity.cloud/pricing.
|
|
28
34
|
*/
|
|
29
35
|
export interface GeoPlacesDetailOptions {
|
|
30
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Entrance/exit points. Moves the request to the Advanced bucket.
|
|
38
|
+
*
|
|
39
|
+
* Refused on a plan without it: 403 `FeatureNotEntitledException` (see
|
|
40
|
+
* `FEATURE_NOT_ENTITLED`).
|
|
41
|
+
*
|
|
42
|
+
* @planFeature rich place data
|
|
43
|
+
*/
|
|
31
44
|
access?: boolean;
|
|
32
45
|
/** Unit and sub-address detail. Stays in the Core bucket — free to enable. */
|
|
33
46
|
secondaryAddresses?: boolean;
|
|
34
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Phone/website, POIs only. Moves the request to the Advanced bucket.
|
|
49
|
+
*
|
|
50
|
+
* Refused on a plan without it: 403 `FeatureNotEntitledException` (see
|
|
51
|
+
* `FEATURE_NOT_ENTITLED`).
|
|
52
|
+
*
|
|
53
|
+
* @planFeature rich place data
|
|
54
|
+
*/
|
|
35
55
|
contact?: boolean;
|
|
36
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* IANA zone and offset. Moves the request to the Advanced bucket.
|
|
58
|
+
*
|
|
59
|
+
* Refused on a plan without it: 403 `FeatureNotEntitledException` (see
|
|
60
|
+
* `FEATURE_NOT_ENTITLED`).
|
|
61
|
+
*
|
|
62
|
+
* @planFeature rich place data
|
|
63
|
+
*/
|
|
37
64
|
timeZone?: boolean;
|
|
38
65
|
}
|
|
39
66
|
export interface GeoPlacesOptions {
|
|
@@ -7,6 +7,7 @@ exports.GeoPlaces = void 0;
|
|
|
7
7
|
const client_geo_places_1 = require("@aws-sdk/client-geo-places");
|
|
8
8
|
const amazon_location_utilities_datatypes_1 = require("@aws/amazon-location-utilities-datatypes");
|
|
9
9
|
const debug_1 = __importDefault(require("debug"));
|
|
10
|
+
const commands_js_1 = require("../client/commands.js");
|
|
10
11
|
const log = (0, debug_1.default)('location-client:geocoder');
|
|
11
12
|
/**
|
|
12
13
|
* Give the control the fields it renders, not just the ones GeoJSON needs.
|
|
@@ -99,7 +100,7 @@ class GeoPlaces {
|
|
|
99
100
|
: config.countries.split(','),
|
|
100
101
|
};
|
|
101
102
|
}
|
|
102
|
-
const response = (await this.client.send(new
|
|
103
|
+
const response = (await this.client.send(new commands_js_1.GeocodeCommand(commandInput)));
|
|
103
104
|
const converted = (0, amazon_location_utilities_datatypes_1.geocodeResponseToFeatureCollection)(response, {
|
|
104
105
|
flattenProperties: true,
|
|
105
106
|
});
|
|
@@ -120,7 +121,7 @@ class GeoPlaces {
|
|
|
120
121
|
MaxResults: config.limit || 1,
|
|
121
122
|
Language: this.normalizeLanguage(config.language),
|
|
122
123
|
};
|
|
123
|
-
const response = (await this.client.send(new
|
|
124
|
+
const response = (await this.client.send(new commands_js_1.ReverseGeocodeCommand(commandInput)));
|
|
124
125
|
const converted = (0, amazon_location_utilities_datatypes_1.reverseGeocodeResponseToFeatureCollection)(response, {
|
|
125
126
|
flattenProperties: true,
|
|
126
127
|
});
|
|
@@ -169,7 +170,7 @@ class GeoPlaces {
|
|
|
169
170
|
}
|
|
170
171
|
: {}),
|
|
171
172
|
};
|
|
172
|
-
const response = (await this.client.send(new
|
|
173
|
+
const response = (await this.client.send(new commands_js_1.SuggestCommand(commandInput)));
|
|
173
174
|
const suggestions = { suggestions: [] };
|
|
174
175
|
for (const item of response.ResultItems ?? []) {
|
|
175
176
|
const text = item.Title;
|
|
@@ -187,7 +188,7 @@ class GeoPlaces {
|
|
|
187
188
|
// lookup in the Advanced bucket at $1.50/1k; the default now sends none
|
|
188
189
|
// and stays in Core at $0.50. Callers that want the detail ask for it.
|
|
189
190
|
const additionalFeatures = this.detailFeatures();
|
|
190
|
-
const command = new
|
|
191
|
+
const command = new commands_js_1.GetPlaceCommand({
|
|
191
192
|
PlaceId: config.query,
|
|
192
193
|
Language: this.normalizeLanguage(config.language),
|
|
193
194
|
...(additionalFeatures ? { AdditionalFeatures: additionalFeatures } : {}),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RequestOptions } from '../transport/http.js';
|
|
2
2
|
import type { ClientConfig } from '../types/index.js';
|
|
3
3
|
import type { AppConfigClaims } from '../utils/tokenClaims.js';
|
|
4
|
+
import type { VerifyAddressResponse } from './commands.js';
|
|
4
5
|
export type SendOptions = RequestOptions;
|
|
5
6
|
/**
|
|
6
7
|
* GeoPlacesClient — AWS Location Service compatible client with custom auth.
|
|
@@ -18,17 +19,19 @@ export declare class GeoPlacesClient {
|
|
|
18
19
|
constructor(config: ClientConfig);
|
|
19
20
|
/**
|
|
20
21
|
* This application's own configuration, as carried on the access token
|
|
21
|
-
* (api#65)
|
|
22
|
+
* (api#65): the routes it may call, the domain its requests must come from,
|
|
23
|
+
* and the countries it is scoped to (#40).
|
|
22
24
|
*
|
|
23
25
|
* Provided so an application can SHOW its own settings: populate a country
|
|
24
26
|
* selector with the markets it actually serves, label a settings screen,
|
|
25
27
|
* and so on. Being a few minutes stale is cosmetic for that.
|
|
26
28
|
*
|
|
27
29
|
* It is not an entitlement check. See AppConfigClaims for why acting on
|
|
28
|
-
*
|
|
30
|
+
* any of it client-side makes requests fail that would otherwise succeed.
|
|
29
31
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
+
* Every token the API issues carries `allowedResources` and `allowedDomain`;
|
|
33
|
+
* `countries` only once a scope is configured in the portal. Returns `{}`
|
|
34
|
+
* for a token carrying none of them.
|
|
32
35
|
*/
|
|
33
36
|
getAppConfig(): AppConfigClaims;
|
|
34
37
|
/** Prefer the getToken callback (live ref) over a static token string. */
|
|
@@ -46,5 +49,14 @@ export declare class GeoPlacesClient {
|
|
|
46
49
|
* retry loop. Every failure throws LocationServiceException.
|
|
47
50
|
*/
|
|
48
51
|
send<TInput, TOutput>(command: TInput, options?: SendOptions): Promise<TOutput>;
|
|
52
|
+
/**
|
|
53
|
+
* Verify a PlaceId: `send(new VerifyAddressCommand({ PlaceId }))`, typed
|
|
54
|
+
* (#54). Resolves the full place record plus `verified`, and resolves a
|
|
55
|
+
* `verified: false` too — see VerifyAddressResponse for what may be stored.
|
|
56
|
+
*
|
|
57
|
+
* Billed per call, whether or not the address verifies: call it once per
|
|
58
|
+
* chosen PlaceId, at submit, never per keystroke.
|
|
59
|
+
*/
|
|
60
|
+
verifyAddress(placeId: string, options?: SendOptions): Promise<VerifyAddressResponse>;
|
|
49
61
|
private dispatch;
|
|
50
62
|
}
|