@chaosity/location-client 0.9.0 → 0.11.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 +307 -37
- 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 +18 -4
- 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 +25 -5
- 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 +29 -3
- package/dist/cjs/utils/tokenClaims.js +28 -3
- package/dist/client/GeoPlacesClient.d.ts +16 -4
- package/dist/client/GeoPlacesClient.js +18 -4
- 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 +25 -5
- 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 +29 -3
- package/dist/utils/tokenClaims.js +28 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -34,13 +34,20 @@ npm install maplibre-gl
|
|
|
34
34
|
npm install maplibre-gl @maplibre/maplibre-gl-geocoder
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
The map helpers need `maplibre-gl` 6.4.1 or a later 6.x release. Earlier
|
|
38
|
+
releases carry
|
|
39
|
+
[GHSA-jrc7-96c5-q579](https://github.com/advisories/GHSA-jrc7-96c5-q579), an
|
|
40
|
+
XSS in the attribution control, and none of them has a fix. MapLibre 6 needs
|
|
41
|
+
its worker set up once under a bundler: see [The MapLibre worker](#the-maplibre-worker).
|
|
42
|
+
|
|
37
43
|
## Key Features
|
|
38
44
|
|
|
39
45
|
- **Custom Authentication**: Uses Bearer tokens instead of AWS SigV4
|
|
40
46
|
- **AWS SDK Commands**: Full access to all AWS Location Service commands
|
|
47
|
+
- **Address Verification**: `verifyAddress(placeId)` returns the one Places result you may store — see [Verifying an address](#verifying-an-address)
|
|
41
48
|
- **Data Type Utilities**: Built-in GeoJSON conversion utilities
|
|
42
49
|
- **MapLibre Integration**: Adapter for MapLibre GL Geocoder and `createTransformRequest` helper
|
|
43
|
-
- **Map Style Control**: Fetch and customize map style descriptors
|
|
50
|
+
- **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
51
|
- **Map Language**: Switch map label language client-side with zero API calls
|
|
45
52
|
- **POI Layer Control**: Toggle point-of-interest categories on/off by layer
|
|
46
53
|
- **Server Utilities**: `getClientConfig()` with auto-env detection and token caching
|
|
@@ -101,10 +108,78 @@ const client = new GeoPlacesClient({
|
|
|
101
108
|
})
|
|
102
109
|
|
|
103
110
|
const response: SuggestCommandOutput = await client.send(
|
|
104
|
-
new SuggestCommand({
|
|
111
|
+
new SuggestCommand({
|
|
112
|
+
QueryText: 'Vancouver',
|
|
113
|
+
MaxResults: 5,
|
|
114
|
+
// Suggest takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
|
|
115
|
+
BiasPosition: [-123.1207, 49.2827],
|
|
116
|
+
}),
|
|
105
117
|
)
|
|
106
118
|
```
|
|
107
119
|
|
|
120
|
+
### Verifying an address
|
|
121
|
+
|
|
122
|
+
`POST /address/verify` resolves one PlaceId to the full place record plus
|
|
123
|
+
`verified`. The PlaceId can come from any suggestion, autocomplete, geocode or
|
|
124
|
+
place result, a unit's included. The answer is **the one Places result you may
|
|
125
|
+
store**; every other result is for display only. The exception is a place in
|
|
126
|
+
Japan, which may not be stored at all.
|
|
127
|
+
|
|
128
|
+
The flow is suggest, then an optional unit pick, then verify at submit:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import {
|
|
132
|
+
GetPlaceCommand,
|
|
133
|
+
SuggestCommand,
|
|
134
|
+
type GetPlaceCommandOutput,
|
|
135
|
+
type SuggestCommandOutput,
|
|
136
|
+
} from '@chaosity/location-client'
|
|
137
|
+
|
|
138
|
+
// 1. While the person types: suggestions, for display.
|
|
139
|
+
const suggestions: SuggestCommandOutput = await client.send(
|
|
140
|
+
// Suggest takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
|
|
141
|
+
new SuggestCommand({
|
|
142
|
+
QueryText: '100 George St, Sydney',
|
|
143
|
+
BiasPosition: [151.2093, -33.8688],
|
|
144
|
+
}),
|
|
145
|
+
)
|
|
146
|
+
const picked = suggestions.ResultItems?.[0]?.Place?.PlaceId
|
|
147
|
+
|
|
148
|
+
// 2. Optionally, offer the building's units. Each has its own PlaceId.
|
|
149
|
+
const building: GetPlaceCommandOutput = await client.send(
|
|
150
|
+
new GetPlaceCommand({
|
|
151
|
+
PlaceId: picked!,
|
|
152
|
+
AdditionalFeatures: ['SecondaryAddresses'],
|
|
153
|
+
}),
|
|
154
|
+
)
|
|
155
|
+
const unit = building.SecondaryAddresses?.[0]?.PlaceId
|
|
156
|
+
|
|
157
|
+
// 3. At submit, once: verify the PlaceId that was chosen.
|
|
158
|
+
const answer = await client.verifyAddress(unit ?? picked!)
|
|
159
|
+
if (answer.verified) {
|
|
160
|
+
// `answer` is the record you may keep.
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- `verified` is `true` for a `PointAddress`, or a `SecondaryAddress` (a unit).
|
|
165
|
+
It is `false` for anything else: an interpolated address, a street, a
|
|
166
|
+
locality, a point of interest. A `false` is still a 200, so the call
|
|
167
|
+
resolves; it does not throw.
|
|
168
|
+
- **Every verify is billed, whether or not the address verifies.** Call it
|
|
169
|
+
once per chosen PlaceId, at submit. Never call it per keystroke, or on every
|
|
170
|
+
pick.
|
|
171
|
+
- Only the `PlaceId` is sent. `VerifyAddressCommandInput` has no other field,
|
|
172
|
+
because the service would drop `Language`, `PoliticalView` and
|
|
173
|
+
`AdditionalFeatures`. A repeat verify of the same PlaceId may be answered
|
|
174
|
+
from the service's own store, and is billed all the same.
|
|
175
|
+
- Keep the PlaceId you sent beside the answer. The answer's own `PlaceId` can
|
|
176
|
+
differ, and for a unit it does. The service does not accept that one back,
|
|
177
|
+
while the one you sent verifies again.
|
|
178
|
+
- `client.verifyAddress(placeId)` is
|
|
179
|
+
`client.send(new VerifyAddressCommand({ PlaceId: placeId }))`, typed as
|
|
180
|
+
`VerifyAddressResponse`. `connector.verifyAddress` does the same on the
|
|
181
|
+
server.
|
|
182
|
+
|
|
108
183
|
### MapLibre Map Integration
|
|
109
184
|
|
|
110
185
|
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:
|
|
@@ -114,12 +189,15 @@ import {
|
|
|
114
189
|
fetchMapStyle,
|
|
115
190
|
createTransformRequest,
|
|
116
191
|
} from '@chaosity/location-client'
|
|
117
|
-
import maplibregl from 'maplibre-gl'
|
|
192
|
+
import * as maplibregl from 'maplibre-gl'
|
|
193
|
+
import 'maplibre-gl/dist/maplibre-gl.css'
|
|
194
|
+
// Vite. For other bundlers, see "The MapLibre worker" below.
|
|
195
|
+
import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url'
|
|
196
|
+
|
|
197
|
+
maplibregl.setWorkerUrl(workerUrl)
|
|
118
198
|
|
|
119
199
|
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
120
200
|
colorScheme: 'Dark',
|
|
121
|
-
terrain: 'Terrain3D',
|
|
122
|
-
buildings: 'Buildings3D',
|
|
123
201
|
language: 'fr',
|
|
124
202
|
})
|
|
125
203
|
|
|
@@ -128,11 +206,72 @@ const map = new maplibregl.Map({
|
|
|
128
206
|
style,
|
|
129
207
|
center: [-123.12, 49.28],
|
|
130
208
|
zoom: 10,
|
|
131
|
-
maxPitch: 85,
|
|
132
209
|
transformRequest: createTransformRequest(apiUrl, getToken),
|
|
133
210
|
})
|
|
134
211
|
```
|
|
135
212
|
|
|
213
|
+
3D terrain and buildings need the `terrain` and `buildings` plan features — see [Plan features](#plan-features):
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
217
|
+
terrain: 'Terrain3D',
|
|
218
|
+
buildings: 'Buildings3D',
|
|
219
|
+
})
|
|
220
|
+
// then `maxPitch: 85` on the map, so the camera can tilt to see them
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### The MapLibre worker
|
|
224
|
+
|
|
225
|
+
MapLibre 6 loads and parses its tiles in a Web Worker, and it finds the
|
|
226
|
+
worker's file from its own module URL. A bundler rewrites that URL, so an
|
|
227
|
+
application built with one sets the worker's URL once, before the first map.
|
|
228
|
+
Without it the map mounts, draws no tile, and logs "Worker failed to load".
|
|
229
|
+
|
|
230
|
+
With Vite, import the worker's URL, as in the example above:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import * as maplibregl from 'maplibre-gl'
|
|
234
|
+
import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url'
|
|
235
|
+
|
|
236
|
+
maplibregl.setWorkerUrl(workerUrl)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
With Next.js, serve the worker and the chunk it imports from `public/`. Copy
|
|
240
|
+
them before every build and dev run:
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
// scripts/copy-maplibre-worker.mjs
|
|
244
|
+
import { copyFileSync, mkdirSync } from 'node:fs'
|
|
245
|
+
import { createRequire } from 'node:module'
|
|
246
|
+
import path from 'node:path'
|
|
247
|
+
|
|
248
|
+
const pkg = createRequire(import.meta.url).resolve('maplibre-gl/package.json')
|
|
249
|
+
const dist = path.join(path.dirname(pkg), 'dist')
|
|
250
|
+
const dest = path.join(process.cwd(), 'public', 'maplibre')
|
|
251
|
+
mkdirSync(dest, { recursive: true })
|
|
252
|
+
for (const file of ['maplibre-gl-worker.mjs', 'maplibre-gl-shared.mjs']) {
|
|
253
|
+
copyFileSync(path.join(dist, file), path.join(dest, file))
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
"scripts": {
|
|
259
|
+
"predev": "node scripts/copy-maplibre-worker.mjs",
|
|
260
|
+
"prebuild": "node scripts/copy-maplibre-worker.mjs"
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Then, in the client component that builds the map:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import * as maplibregl from 'maplibre-gl'
|
|
268
|
+
|
|
269
|
+
maplibregl.setWorkerUrl('/maplibre/maplibre-gl-worker.mjs')
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Other bundlers, and loading MapLibre from a CDN, are covered in MapLibre's own
|
|
273
|
+
[installation guide](https://maplibre.org/maplibre-gl-js/docs/#installation).
|
|
274
|
+
|
|
136
275
|
### Switching Map Language
|
|
137
276
|
|
|
138
277
|
Change map label language instantly on the client side — no API calls needed:
|
|
@@ -170,6 +309,74 @@ setAllPoiVisibility(map, false)
|
|
|
170
309
|
|
|
171
310
|
Available categories: `food_drink`, `entertainment`, `sights`, `transit`, `accommodations`, `leisure`, `shopping`, `business`, `facilities`, `areas`, `parks`.
|
|
172
311
|
|
|
312
|
+
### Plan features
|
|
313
|
+
|
|
314
|
+
Some options are features of the application's plan. An application whose plan
|
|
315
|
+
does not include one is refused **403 `FeatureNotEntitledException`** before
|
|
316
|
+
anything is fetched upstream, so the refusal is not billed, and the message
|
|
317
|
+
names each refused feature and the option that asked for it:
|
|
318
|
+
|
|
319
|
+
> This application's plan does not include the map feature terrain (terrain=Terrain3D).
|
|
320
|
+
|
|
321
|
+
| Feature | What asks for it |
|
|
322
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
323
|
+
| `satellite` | the `Satellite` and `Hybrid` styles, and a static map with no `style` — Satellite is its default |
|
|
324
|
+
| `terrain` | `terrain` (`Hillshade`, `Terrain3D`) |
|
|
325
|
+
| `buildings` | `buildings` |
|
|
326
|
+
| `contours` | `contourDensity` |
|
|
327
|
+
| `traffic` | `traffic` |
|
|
328
|
+
| `travel-modes` | `travelModes` |
|
|
329
|
+
| `political-view` | `politicalView`, on a style or a static map |
|
|
330
|
+
| `rich place data` | `AdditionalFeatures` `Access`, `Contact`, `Phonemes` or `TimeZone` on a Places command, and the `GeoPlaces` details that ask for them |
|
|
331
|
+
|
|
332
|
+
Everything else is open to every plan: the Standard and Monochrome styles,
|
|
333
|
+
`colorScheme`, `poiDensity`, `poiCategories`, a label language, and the other
|
|
334
|
+
`AdditionalFeatures`. Which plan includes which feature is on the
|
|
335
|
+
[pricing page](https://chaosity.cloud/pricing), and deliberately not here: it can
|
|
336
|
+
change without a release of this package. In the source, every gated option and
|
|
337
|
+
value carries a `@planFeature` tag naming its feature, so your editor shows it.
|
|
338
|
+
|
|
339
|
+
The token carries no feature list, so there is nothing to check beforehand — the
|
|
340
|
+
403 is how you find out, and it is typed:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
import {
|
|
344
|
+
LocationServiceException,
|
|
345
|
+
fetchMapStyle,
|
|
346
|
+
} from '@chaosity/location-client'
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
map.setStyle(await fetchMapStyle(apiUrl, 'Standard', getToken, options))
|
|
350
|
+
} catch (err) {
|
|
351
|
+
if (err instanceof LocationServiceException && err.isFeatureNotEntitled) {
|
|
352
|
+
showMessage(err.message) // names the refused feature
|
|
353
|
+
} else throw err
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
`isAuth` is true for it too, as for every 401 and 403 — but so it is for bad
|
|
358
|
+
credentials, an `Origin` the application does not allow, and a route outside the
|
|
359
|
+
plan, which all need different fixes. Branch on `isFeatureNotEntitled`, or on
|
|
360
|
+
`code` against the exported `FEATURE_NOT_ENTITLED`.
|
|
361
|
+
|
|
362
|
+
**What MapLibre fetches for itself is refused outside this package.**
|
|
363
|
+
`fetchMapStyle`, `fetchStaticMap` and the Places commands reject with the typed
|
|
364
|
+
error above. A style URL from `buildMapStyleUrl` handed to `setStyle`, and every
|
|
365
|
+
tile, is fetched by MapLibre, so a refusal there arrives as a map `error` event:
|
|
366
|
+
`error.status` is 403 and `error.body` is a `Blob` holding the same
|
|
367
|
+
`{ message, code }` JSON.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { FEATURE_NOT_ENTITLED } from '@chaosity/location-client'
|
|
371
|
+
|
|
372
|
+
map.on('error', async ({ error }) => {
|
|
373
|
+
const { status, body } = error as { status?: number; body?: Blob }
|
|
374
|
+
if (status !== 403 || !body) return
|
|
375
|
+
const { code, message } = JSON.parse(await body.text())
|
|
376
|
+
if (code === FEATURE_NOT_ENTITLED) showMessage(message)
|
|
377
|
+
})
|
|
378
|
+
```
|
|
379
|
+
|
|
173
380
|
### MapLibre Geocoder Integration
|
|
174
381
|
|
|
175
382
|
Requires the optional peers: `npm install maplibre-gl @maplibre/maplibre-gl-geocoder`
|
|
@@ -177,7 +384,8 @@ Requires the optional peers: `npm install maplibre-gl @maplibre/maplibre-gl-geoc
|
|
|
177
384
|
```typescript
|
|
178
385
|
import { GeoPlacesClient, GeoPlaces } from '@chaosity/location-client'
|
|
179
386
|
import MaplibreGeocoder from '@maplibre/maplibre-gl-geocoder'
|
|
180
|
-
import
|
|
387
|
+
import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css'
|
|
388
|
+
import * as maplibregl from 'maplibre-gl'
|
|
181
389
|
|
|
182
390
|
// GeoPlaces adapter takes a GeoPlacesClient instance and the map
|
|
183
391
|
const client = new GeoPlacesClient({ apiUrl, token })
|
|
@@ -219,6 +427,22 @@ await client.send(command)
|
|
|
219
427
|
|
|
220
428
|
When `getToken` is provided, it is called on every request so token updates are reflected without recreating the client.
|
|
221
429
|
|
|
430
|
+
`client.getAppConfig()` — and `await connector.getAppConfig()` on the server —
|
|
431
|
+
returns the application's own settings as its access token carries them:
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
{
|
|
435
|
+
allowedResources?: string[] // e.g. 'GET /maps/static/{fileName}'
|
|
436
|
+
allowedDomain?: string // the domain its requests must come from
|
|
437
|
+
countries?: string[] // ISO 3166-1 alpha-2, once a scope is configured
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
For display — "static maps are not in your plan", the domain to show beside an
|
|
442
|
+
`Origin not allowed` 403 — never for refusing or shaping a request. The token
|
|
443
|
+
can be up to fifteen minutes old, and the API reads all three fresh from the
|
|
444
|
+
application on every request.
|
|
445
|
+
|
|
222
446
|
`refreshToken` covers the case `getToken` cannot. `getToken` is synchronous —
|
|
223
447
|
MapLibre's `transformRequest` requires that — so it can only ever return the
|
|
224
448
|
token already in hand, and a token the API stops accepting **before** its `exp`
|
|
@@ -232,8 +456,8 @@ worth a second round trip. A 403 is never retried: a new token cannot fix an
|
|
|
232
456
|
#### Request options
|
|
233
457
|
|
|
234
458
|
Every call in this package takes the same options object — `client.send`,
|
|
235
|
-
`connector.send`, `
|
|
236
|
-
arrives as a `LocationServiceException`.
|
|
459
|
+
`connector.send`, `verifyAddress` on either, `fetchMapStyle` and
|
|
460
|
+
`fetchStaticMap` — and every failure arrives as a `LocationServiceException`.
|
|
237
461
|
|
|
238
462
|
```typescript
|
|
239
463
|
await client.send(command, {
|
|
@@ -245,12 +469,13 @@ await client.send(command, {
|
|
|
245
469
|
```
|
|
246
470
|
|
|
247
471
|
`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
|
|
472
|
+
30 s. `timeoutMs` bounds an attempt, not a call: nothing bounds the
|
|
473
|
+
`Retry-After` a 429 carries, and honouring one of 60 s literally across two
|
|
474
|
+
retries would have blocked the caller for about two minutes — past any Lambda
|
|
475
|
+
budget. Now no attempt gets more time than the call has left, and a retry that
|
|
476
|
+
would have to wait longer than the remaining budget is not made at all. You get
|
|
477
|
+
the API's own error back instead, `retryAfterMs` intact, so you can queue the
|
|
478
|
+
work rather than guess.
|
|
254
479
|
|
|
255
480
|
The map helpers take them as a trailing argument:
|
|
256
481
|
|
|
@@ -264,7 +489,7 @@ const style = await fetchMapStyle(
|
|
|
264
489
|
)
|
|
265
490
|
const blob = await fetchStaticMap(
|
|
266
491
|
apiUrl,
|
|
267
|
-
{ width: 640, height: 400, center },
|
|
492
|
+
{ width: 640, height: 400, center, zoom: 14, style: 'Standard' },
|
|
268
493
|
getToken,
|
|
269
494
|
{ signal },
|
|
270
495
|
)
|
|
@@ -311,20 +536,29 @@ import { fetchMapStyle } from '@chaosity/location-client'
|
|
|
311
536
|
|
|
312
537
|
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
313
538
|
colorScheme: 'Dark',
|
|
539
|
+
poiDensity: 'Sparse',
|
|
540
|
+
language: 'fr',
|
|
541
|
+
})
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Hand `style` to `new maplibregl.Map`, with the worker set, as in [MapLibre Map Integration](#maplibre-map-integration).
|
|
545
|
+
|
|
546
|
+
The overlays need the `terrain`, `buildings`, `contours`, `traffic` and `travel-modes` plan features — see [Plan features](#plan-features):
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
314
550
|
terrain: 'Terrain3D',
|
|
315
551
|
buildings: 'Buildings3D',
|
|
316
552
|
contourDensity: 'Medium',
|
|
317
553
|
traffic: 'All',
|
|
318
554
|
travelModes: ['Truck', 'Transit'],
|
|
319
|
-
language: 'fr',
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
const map = new maplibregl.Map({
|
|
323
|
-
style,
|
|
324
|
-
transformRequest: createTransformRequest(apiUrl, getToken),
|
|
325
555
|
})
|
|
326
556
|
```
|
|
327
557
|
|
|
558
|
+
This is the call that surfaces a refusal as `LocationServiceException`; a URL
|
|
559
|
+
MapLibre fetches for itself reports it as a map `error` event instead (see
|
|
560
|
+
[Plan features](#plan-features)).
|
|
561
|
+
|
|
328
562
|
#### buildMapStyleUrl
|
|
329
563
|
|
|
330
564
|
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 +568,26 @@ import { buildMapStyleUrl } from '@chaosity/location-client'
|
|
|
334
568
|
|
|
335
569
|
const url = buildMapStyleUrl(apiUrl, 'Standard', {
|
|
336
570
|
colorScheme: 'Dark',
|
|
337
|
-
terrain: 'Hillshade',
|
|
338
571
|
})
|
|
339
572
|
```
|
|
340
573
|
|
|
341
574
|
#### MapStyleOptions
|
|
342
575
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
576
|
+
The options, the values each accepts, and which of them are plan features are
|
|
577
|
+
documented on the type — `MapStyleOptions` in `src/maps/mapStyle.ts`, with the
|
|
578
|
+
value lists in `src/maps/mapEnums.ts` — and your editor shows them as you type.
|
|
579
|
+
They are not copied here, because the copy that used to be here drifted from the
|
|
580
|
+
source.
|
|
581
|
+
|
|
582
|
+
Every accepted value is exported as an array (`POI_DENSITIES`,
|
|
583
|
+
`STYLE_POI_CATEGORIES`, `TRAFFIC_MODES`, …) so a picker can be built from it. A
|
|
584
|
+
list tagged `@planFeature` — `MAP_STYLES`, `TERRAINS`, `TRAFFIC_MODES` and the
|
|
585
|
+
others in [Plan features](#plan-features) — holds values some plans are refused,
|
|
586
|
+
so leave those out of a picker or mark them. Values are case sensitive, and some
|
|
587
|
+
combinations are the API's to refuse — for example `traffic: 'All'` on
|
|
588
|
+
Satellite. There is no `language` here:
|
|
589
|
+
`fetchMapStyle` takes `language` separately and applies it to the descriptor
|
|
590
|
+
itself, because the service's style descriptor has no language parameter.
|
|
354
591
|
|
|
355
592
|
#### applyMapLanguage
|
|
356
593
|
|
|
@@ -378,10 +615,12 @@ setAllPoiVisibility(map, false)
|
|
|
378
615
|
|
|
379
616
|
#### Available Commands
|
|
380
617
|
|
|
381
|
-
|
|
618
|
+
The Amazon Location Places commands, with the SDK's inputs minus the two
|
|
619
|
+
fields below:
|
|
382
620
|
|
|
383
621
|
```typescript
|
|
384
622
|
import {
|
|
623
|
+
AutocompleteCommand,
|
|
385
624
|
SuggestCommand,
|
|
386
625
|
GeocodeCommand,
|
|
387
626
|
ReverseGeocodeCommand,
|
|
@@ -391,6 +630,29 @@ import {
|
|
|
391
630
|
} from '@chaosity/location-client'
|
|
392
631
|
```
|
|
393
632
|
|
|
633
|
+
**Two SDK fields are not accepted: `IntendedUse` and `Key`.** The service
|
|
634
|
+
removes both from every request — `IntendedUse` would choose the price bucket,
|
|
635
|
+
and `Key` would bill an Amazon Location key that is not the service's — so the
|
|
636
|
+
commands exported here are typed without them, and passing either is a compile
|
|
637
|
+
error rather than a field that is sent and silently ignored. The exported
|
|
638
|
+
`<Name>CommandInput` and `<Name>Request` types are narrowed the same way. At
|
|
639
|
+
runtime nothing is removed: the request body is your input, unchanged, and a
|
|
640
|
+
field cast past the type is stripped by the service all the same.
|
|
641
|
+
|
|
642
|
+
**`AdditionalFeatures` `Access`, `Contact`, `Phonemes` and `TimeZone` are rich
|
|
643
|
+
place data**, a plan feature. On a plan without it the request
|
|
644
|
+
is refused 403 `FeatureNotEntitledException` — see
|
|
645
|
+
[Plan features](#plan-features). `SecondaryAddresses`, `Intersections`,
|
|
646
|
+
`CrossReferences` and `Core` are open to every plan.
|
|
647
|
+
|
|
648
|
+
`VerifyAddressCommand` is this package's own: `POST /address/verify` has no
|
|
649
|
+
SDK command. Its input is `{ PlaceId }` and nothing else, and it answers a
|
|
650
|
+
`VerifyAddressResponse` — see [Verifying an address](#verifying-an-address).
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
import { VerifyAddressCommand } from '@chaosity/location-client'
|
|
654
|
+
```
|
|
655
|
+
|
|
394
656
|
#### Data Type Utilities
|
|
395
657
|
|
|
396
658
|
GeoJSON conversion utilities from `@aws/amazon-location-utilities-datatypes`:
|
|
@@ -460,7 +722,11 @@ const connector = new LocationServiceConnector({
|
|
|
460
722
|
})
|
|
461
723
|
|
|
462
724
|
const result = await connector.send(
|
|
463
|
-
new SuggestCommand({
|
|
725
|
+
new SuggestCommand({
|
|
726
|
+
QueryText: 'Vancouver',
|
|
727
|
+
// Suggest takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
|
|
728
|
+
BiasPosition: [-123.1207, 49.2827],
|
|
729
|
+
}),
|
|
464
730
|
)
|
|
465
731
|
```
|
|
466
732
|
|
|
@@ -533,7 +799,11 @@ Full TypeScript support with types from AWS SDK:
|
|
|
533
799
|
import type { SuggestCommandOutput } from '@chaosity/location-client'
|
|
534
800
|
|
|
535
801
|
const response: SuggestCommandOutput = await client.send(
|
|
536
|
-
new SuggestCommand({
|
|
802
|
+
new SuggestCommand({
|
|
803
|
+
QueryText: 'Vancouver',
|
|
804
|
+
// Suggest takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
|
|
805
|
+
BiasPosition: [-123.1207, 49.2827],
|
|
806
|
+
}),
|
|
537
807
|
)
|
|
538
808
|
```
|
|
539
809
|
|
|
@@ -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 } : {}),
|