@chaosity/location-client 0.1.10 → 0.1.11
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
CHANGED
|
@@ -28,6 +28,9 @@ npm install @chaosity/location-client
|
|
|
28
28
|
- **AWS SDK Commands**: Full access to all AWS Location Service commands
|
|
29
29
|
- **Data Type Utilities**: Built-in GeoJSON conversion utilities
|
|
30
30
|
- **MapLibre Integration**: Adapter for MapLibre GL Geocoder and `createTransformRequest` helper
|
|
31
|
+
- **Map Style Control**: Fetch and customize map style descriptors with terrain, 3D buildings, traffic, and more
|
|
32
|
+
- **Map Language**: Switch map label language client-side with zero API calls
|
|
33
|
+
- **POI Layer Control**: Toggle point-of-interest categories on/off by layer
|
|
31
34
|
- **Server Utilities**: `getClientConfig()` with auto-env detection and token caching
|
|
32
35
|
|
|
33
36
|
## Quick Start
|
|
@@ -84,22 +87,61 @@ const response = await client.send(
|
|
|
84
87
|
|
|
85
88
|
### MapLibre Map Integration
|
|
86
89
|
|
|
87
|
-
Use `createTransformRequest` to
|
|
90
|
+
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:
|
|
88
91
|
|
|
89
92
|
```typescript
|
|
90
|
-
import { createTransformRequest } from '@chaosity/location-client'
|
|
93
|
+
import { fetchMapStyle, createTransformRequest } from '@chaosity/location-client'
|
|
91
94
|
import maplibregl from 'maplibre-gl'
|
|
92
95
|
|
|
96
|
+
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
97
|
+
colorScheme: 'Dark',
|
|
98
|
+
terrain: 'Terrain3D',
|
|
99
|
+
buildings: 'Buildings3D',
|
|
100
|
+
language: 'fr',
|
|
101
|
+
})
|
|
102
|
+
|
|
93
103
|
const map = new maplibregl.Map({
|
|
94
104
|
container: 'map',
|
|
95
|
-
style
|
|
105
|
+
style,
|
|
96
106
|
center: [-123.12, 49.28],
|
|
97
107
|
zoom: 10,
|
|
108
|
+
maxPitch: 85,
|
|
98
109
|
transformRequest: createTransformRequest(apiUrl, getToken),
|
|
99
110
|
})
|
|
100
111
|
```
|
|
101
112
|
|
|
102
|
-
|
|
113
|
+
### Switching Map Language
|
|
114
|
+
|
|
115
|
+
Change map label language instantly on the client side — no API calls needed:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { applyMapLanguage } from '@chaosity/location-client'
|
|
119
|
+
|
|
120
|
+
// Apply after the style is loaded
|
|
121
|
+
map.once('style.load', () => applyMapLanguage(map, 'fr'))
|
|
122
|
+
|
|
123
|
+
// Or reapply whenever the user changes language
|
|
124
|
+
applyMapLanguage(map, 'ja')
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Controlling POI Layers
|
|
128
|
+
|
|
129
|
+
Toggle point-of-interest categories on or off:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { setPoiVisibility, setAllPoiVisibility, POI_CATEGORIES } from '@chaosity/location-client'
|
|
133
|
+
|
|
134
|
+
// Hide transit POIs
|
|
135
|
+
setPoiVisibility(map, 'transit', false)
|
|
136
|
+
|
|
137
|
+
// Hide multiple categories at once
|
|
138
|
+
setPoiVisibility(map, ['shopping', 'business'], false)
|
|
139
|
+
|
|
140
|
+
// Hide all POIs
|
|
141
|
+
setAllPoiVisibility(map, false)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Available categories: `food_drink`, `entertainment`, `sights`, `transit`, `accommodations`, `leisure`, `shopping`, `business`, `facilities`, `areas`, `parks`.
|
|
103
145
|
|
|
104
146
|
### MapLibre Geocoder Integration
|
|
105
147
|
|
|
@@ -172,6 +214,70 @@ import { createTransformRequest } from '@chaosity/location-client'
|
|
|
172
214
|
const transformRequest = createTransformRequest(apiUrl, () => currentToken)
|
|
173
215
|
```
|
|
174
216
|
|
|
217
|
+
#### fetchMapStyle
|
|
218
|
+
|
|
219
|
+
Fetches the map style descriptor with Bearer auth and applies optional language to the descriptor JSON before MapLibre processes it (eliminates the visual flash that occurs when modifying layers post-load).
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { fetchMapStyle } from '@chaosity/location-client'
|
|
223
|
+
|
|
224
|
+
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
225
|
+
colorScheme: 'Dark',
|
|
226
|
+
terrain: 'Terrain3D',
|
|
227
|
+
buildings: 'Buildings3D',
|
|
228
|
+
contourDensity: 'Medium',
|
|
229
|
+
traffic: 'All',
|
|
230
|
+
travelModes: ['Truck', 'Transit'],
|
|
231
|
+
language: 'fr',
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const map = new maplibregl.Map({ style, transformRequest: createTransformRequest(apiUrl, getToken) })
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### buildMapStyleUrl
|
|
238
|
+
|
|
239
|
+
Builds the style descriptor URL without fetching. Useful when you want to pass the URL directly to MapLibre (e.g. without pre-applying language).
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import { buildMapStyleUrl } from '@chaosity/location-client'
|
|
243
|
+
|
|
244
|
+
const url = buildMapStyleUrl(apiUrl, 'Standard', { colorScheme: 'Dark', terrain: 'Hillshade' })
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### MapStyleOptions
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
interface MapStyleOptions {
|
|
251
|
+
colorScheme?: 'Light' | 'Dark'
|
|
252
|
+
politicalView?: string // ISO 3166-1 alpha-3 (e.g. 'IND', 'TUR')
|
|
253
|
+
terrain?: 'Hillshade' | 'Terrain3D'
|
|
254
|
+
buildings?: 'Buildings3D'
|
|
255
|
+
contourDensity?: 'Medium' // Only 'Medium' is supported by the AWS SDK
|
|
256
|
+
traffic?: 'All'
|
|
257
|
+
travelModes?: Array<'Truck' | 'Transit'>
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### applyMapLanguage
|
|
262
|
+
|
|
263
|
+
Modifies symbol layer `text-field` expressions on an existing map to display labels in the specified language. No API call — operates entirely on the client.
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { applyMapLanguage } from '@chaosity/location-client'
|
|
267
|
+
|
|
268
|
+
applyMapLanguage(map, 'fr')
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### POI Layer Control
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { setPoiVisibility, setAllPoiVisibility, POI_CATEGORIES } from '@chaosity/location-client'
|
|
275
|
+
import type { PoiCategory } from '@chaosity/location-client'
|
|
276
|
+
|
|
277
|
+
setPoiVisibility(map, 'transit', false)
|
|
278
|
+
setAllPoiVisibility(map, false)
|
|
279
|
+
```
|
|
280
|
+
|
|
175
281
|
#### Available Commands
|
|
176
282
|
|
|
177
283
|
All AWS Location Service commands from `@aws-sdk/client-geo-places`:
|
|
@@ -243,6 +349,14 @@ const connector = new LocationServiceConnector({
|
|
|
243
349
|
const result = await connector.send(new SuggestCommand({ QueryText: 'Vancouver' }))
|
|
244
350
|
```
|
|
245
351
|
|
|
352
|
+
## Cache-Friendly Position Rounding
|
|
353
|
+
|
|
354
|
+
`BiasPosition` coordinates are automatically rounded to 2 decimal places (~1.1 km grid) before each API request. This maximizes cache hits across nearby users without affecting result quality — bias is approximate by nature.
|
|
355
|
+
|
|
356
|
+
`QueryPosition` (reverse geocode) retains full precision since it represents an exact point the user selected.
|
|
357
|
+
|
|
358
|
+
This is handled transparently in both `GeoPlacesClient` and `LocationServiceConnector` — no action needed in application code.
|
|
359
|
+
|
|
246
360
|
## Logging
|
|
247
361
|
|
|
248
362
|
The library uses the `debug` package for optional verbose logging:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
2
|
import { AutocompleteCommand, GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SearchNearbyCommand, SearchTextCommand, SuggestCommand, } from '@aws-sdk/client-geo-places';
|
|
3
|
+
import { roundPositionFields } from '../utils/roundPosition';
|
|
3
4
|
const log = debug('location-client:api');
|
|
4
5
|
const logError = debug('location-client:api:error');
|
|
5
6
|
// Minification-safe endpoint map — uses constructor identity, not class name strings
|
|
@@ -29,7 +30,7 @@ export class GeoPlacesClient {
|
|
|
29
30
|
const endpoint = this.getEndpoint(command);
|
|
30
31
|
const url = `${this.clientConfig.apiUrl}${endpoint}`;
|
|
31
32
|
const commandName = command.constructor.name;
|
|
32
|
-
const input = command.input;
|
|
33
|
+
const input = roundPositionFields(command.input);
|
|
33
34
|
// Prefer getToken callback (live ref) over static token string
|
|
34
35
|
const token = this.clientConfig.getToken?.() ?? this.clientConfig.token;
|
|
35
36
|
log('Sending %s to %s', commandName, endpoint);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AutocompleteCommand, GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SearchNearbyCommand, SearchTextCommand, SuggestCommand, } from '@aws-sdk/client-geo-places';
|
|
2
2
|
import debug from 'debug';
|
|
3
3
|
import { getClientConfig } from './getClientConfig';
|
|
4
|
+
import { roundPositionFields } from '../utils/roundPosition';
|
|
4
5
|
const log = debug('location-client:connector');
|
|
5
6
|
/**
|
|
6
7
|
* LocationServiceConnector - Server-side connector for Location Service API
|
|
@@ -55,7 +56,7 @@ export class LocationServiceConnector {
|
|
|
55
56
|
const endpoint = this.getEndpoint(command);
|
|
56
57
|
const url = `${config.apiUrl}${endpoint}`;
|
|
57
58
|
const commandName = command.constructor.name;
|
|
58
|
-
const input = command.input;
|
|
59
|
+
const input = roundPositionFields(command.input);
|
|
59
60
|
log('Sending %s request to %s', commandName, endpoint);
|
|
60
61
|
const startTime = Date.now();
|
|
61
62
|
// Merge headers: user headers, then system headers override
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Round coordinate arrays in command inputs for better API cache hits.
|
|
3
|
+
*
|
|
4
|
+
* BiasPosition is rounded to 2 decimal places (~1.1 km precision) —
|
|
5
|
+
* sufficient for spatial biasing while maximizing cache reuse across
|
|
6
|
+
* nearby users.
|
|
7
|
+
*
|
|
8
|
+
* QueryPosition (reverse geocode) keeps full precision since it
|
|
9
|
+
* represents an exact point the user clicked or their device reported.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Shallow-clone the input and round position arrays that benefit from caching.
|
|
13
|
+
* Returns the original object if no position fields are present.
|
|
14
|
+
*/
|
|
15
|
+
export declare function roundPositionFields<T extends Record<string, any>>(input: T): T;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Round coordinate arrays in command inputs for better API cache hits.
|
|
3
|
+
*
|
|
4
|
+
* BiasPosition is rounded to 2 decimal places (~1.1 km precision) —
|
|
5
|
+
* sufficient for spatial biasing while maximizing cache reuse across
|
|
6
|
+
* nearby users.
|
|
7
|
+
*
|
|
8
|
+
* QueryPosition (reverse geocode) keeps full precision since it
|
|
9
|
+
* represents an exact point the user clicked or their device reported.
|
|
10
|
+
*/
|
|
11
|
+
const BIAS_DECIMALS = 2; // ~1.1 km grid
|
|
12
|
+
const BIAS_FACTOR = 10 ** BIAS_DECIMALS;
|
|
13
|
+
/** Known position field names and whether they should be rounded */
|
|
14
|
+
const POSITION_FIELDS = {
|
|
15
|
+
BiasPosition: true, // geocode, autocomplete, search — round for cache
|
|
16
|
+
QueryPosition: false, // reverse geocode — keep full precision
|
|
17
|
+
};
|
|
18
|
+
function roundCoord(value) {
|
|
19
|
+
return Math.round(value * BIAS_FACTOR) / BIAS_FACTOR;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Shallow-clone the input and round position arrays that benefit from caching.
|
|
23
|
+
* Returns the original object if no position fields are present.
|
|
24
|
+
*/
|
|
25
|
+
export function roundPositionFields(input) {
|
|
26
|
+
if (!input || typeof input !== 'object')
|
|
27
|
+
return input;
|
|
28
|
+
let modified = false;
|
|
29
|
+
const result = { ...input };
|
|
30
|
+
for (const [field, shouldRound] of Object.entries(POSITION_FIELDS)) {
|
|
31
|
+
const value = result[field];
|
|
32
|
+
if (!shouldRound || !Array.isArray(value) || value.length < 2)
|
|
33
|
+
continue;
|
|
34
|
+
result[field] = value.map(roundCoord);
|
|
35
|
+
modified = true;
|
|
36
|
+
}
|
|
37
|
+
return modified ? result : input;
|
|
38
|
+
}
|