bmlt-query-client 1.0.1
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/.eslintrc.json +23 -0
- package/PROJECT_SUMMARY.md +166 -0
- package/README.md +272 -0
- package/README.old.md +490 -0
- package/dist/app.js +1738 -0
- package/dist/app.js.map +1 -0
- package/package.json +62 -0
- package/test/setup.ts +16 -0
- package/vite.config.ts +35 -0
- package/vite.esm.config.ts +20 -0
package/README.old.md
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# BMLT Query Client
|
|
2
|
+
|
|
3
|
+
A comprehensive TypeScript client for querying BMLT (Basic Meeting List Tool) servers with built-in geocoding support using Nominatim. This client provides a modern, type-safe interface to all BMLT API endpoints with automatic rate limiting, retry logic, and geographic search capabilities.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **Full TypeScript support** with comprehensive type definitions
|
|
8
|
+
- 🌍 **Built-in geocoding** using Nominatim (replaces broken StringSearchIsAnAddress)
|
|
9
|
+
- 🔄 **Automatic retry logic** with exponential backoff
|
|
10
|
+
- ⚡ **Rate limiting** to respect API limits
|
|
11
|
+
- 🎯 **Fluent query builder** for complex searches
|
|
12
|
+
- 📡 **All BMLT endpoints** supported
|
|
13
|
+
- 🛡️ **Comprehensive error handling**
|
|
14
|
+
- 📖 **Extensive documentation** and examples
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install bmlt-query-client
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { BmltClient, Weekday, VenueType } from 'bmlt-query-client';
|
|
26
|
+
|
|
27
|
+
// Initialize the client with NYC demo server
|
|
28
|
+
const client = new BmltClient({
|
|
29
|
+
rootServerURL: 'https://latest.aws.bmlt.app/main_server'
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Search for meetings
|
|
33
|
+
const meetings = await client.searchMeetings({
|
|
34
|
+
weekdays: [Weekday.MONDAY, Weekday.WEDNESDAY],
|
|
35
|
+
venue_types: VenueType.VIRTUAL
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Search by address (with automatic geocoding)
|
|
39
|
+
const nearbyMeetings = await client.searchMeetingsByAddress({
|
|
40
|
+
address: 'Times Square, New York, NY',
|
|
41
|
+
radiusMiles: 2,
|
|
42
|
+
sortByDistance: true
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API Reference
|
|
47
|
+
|
|
48
|
+
### BmltClient
|
|
49
|
+
|
|
50
|
+
The main client class for interacting with BMLT servers.
|
|
51
|
+
|
|
52
|
+
#### Constructor Options
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
interface BmltClientOptions {
|
|
56
|
+
rootServerURL: string; // BMLT root server URL
|
|
57
|
+
defaultFormat?: BmltDataFormat; // Default response format (JSON)
|
|
58
|
+
timeout?: number; // Request timeout in ms (30000)
|
|
59
|
+
userAgent?: string; // Custom user agent
|
|
60
|
+
geocodingOptions?: GeocodeOptions; // Nominatim geocoding options
|
|
61
|
+
enableGeocoding?: boolean; // Enable geocoding service (true)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Basic Methods
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// Search for meetings with parameters
|
|
69
|
+
const meetings = await client.searchMeetings({
|
|
70
|
+
weekdays: [Weekday.SATURDAY, Weekday.SUNDAY],
|
|
71
|
+
venue_types: VenueType.IN_PERSON,
|
|
72
|
+
geo_width: 5,
|
|
73
|
+
lat_val: 40.7580, // Times Square
|
|
74
|
+
long_val: -73.9855
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Get meeting formats
|
|
78
|
+
const formats = await client.getFormats({
|
|
79
|
+
lang_enum: Language.ENGLISH
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Get service bodies
|
|
83
|
+
const serviceBodies = await client.getServiceBodies({
|
|
84
|
+
recursive: true
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Get server information
|
|
88
|
+
const serverInfo = await client.getServerInfo();
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Geographic Search Methods
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// Search by address (with geocoding)
|
|
95
|
+
const meetings = await client.searchMeetingsByAddress({
|
|
96
|
+
address: 'Central Park, New York, NY',
|
|
97
|
+
radiusKm: 3,
|
|
98
|
+
searchParams: {
|
|
99
|
+
venue_types: VenueType.IN_PERSON
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Search by coordinates
|
|
104
|
+
const meetings = await client.searchMeetingsByCoordinates(
|
|
105
|
+
{ latitude: 40.7614, longitude: -73.9776 }, // Times Square
|
|
106
|
+
2, // radius in miles
|
|
107
|
+
undefined, // radius in km
|
|
108
|
+
{ weekdays: Weekday.MONDAY }
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Geocode an address
|
|
112
|
+
const result = await client.geocodeAddress('Brooklyn Bridge, New York, NY');
|
|
113
|
+
console.log(result.coordinates); // { latitude: 40.7061, longitude: -73.9969 }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Query Builder
|
|
117
|
+
|
|
118
|
+
Use the fluent query builder for complex searches:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { MeetingQueryBuilder, QuickSearch } from 'bmlt-query-client';
|
|
122
|
+
|
|
123
|
+
const client = new BmltClient({ rootServerURL: 'https://latest.aws.bmlt.app/main_server' });
|
|
124
|
+
|
|
125
|
+
// Using the query builder
|
|
126
|
+
const meetings = await new MeetingQueryBuilder(client)
|
|
127
|
+
.virtualOnly()
|
|
128
|
+
.onWeekdays(Weekday.MONDAY, Weekday.WEDNESDAY, Weekday.FRIDAY)
|
|
129
|
+
.startingAfter(18, 0) // 6:00 PM
|
|
130
|
+
.endingBefore(21, 0) // 9:00 PM
|
|
131
|
+
.sortByDistance()
|
|
132
|
+
.paginate(20, 1)
|
|
133
|
+
.execute();
|
|
134
|
+
|
|
135
|
+
// Or execute with address geocoding
|
|
136
|
+
const nearbyMeetings = await new MeetingQueryBuilder(client)
|
|
137
|
+
.inPersonOnly()
|
|
138
|
+
.minimumDuration(1, 0) // At least 1 hour
|
|
139
|
+
.executeNearAddress('Central Park, New York, NY', 2); // 2 miles radius
|
|
140
|
+
|
|
141
|
+
// Quick search patterns
|
|
142
|
+
const quickSearch = new QuickSearch(client);
|
|
143
|
+
|
|
144
|
+
const todaysMeetings = await quickSearch.today().execute();
|
|
145
|
+
const weekendMeetings = await quickSearch.weekend().execute();
|
|
146
|
+
const eveningMeetings = await quickSearch.evening().execute();
|
|
147
|
+
const virtualMeetings = await quickSearch.virtual().execute();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Available Query Builder Methods
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Meeting IDs
|
|
154
|
+
.meetingIds(123) or .meetingIds([123, 456])
|
|
155
|
+
.meetingIds([123, 456], true) // exclude these IDs
|
|
156
|
+
|
|
157
|
+
// Time-based filters
|
|
158
|
+
.onWeekdays(Weekday.MONDAY, Weekday.FRIDAY)
|
|
159
|
+
.notOnWeekdays(Weekday.SATURDAY, Weekday.SUNDAY)
|
|
160
|
+
.startingAfter(18, 30) // 6:30 PM
|
|
161
|
+
.startingBefore(12, 0) // Before noon
|
|
162
|
+
.endingBefore(21, 0) // Before 9 PM
|
|
163
|
+
|
|
164
|
+
// Duration filters
|
|
165
|
+
.minimumDuration(1, 0) // At least 1 hour
|
|
166
|
+
.maximumDuration(2, 0) // At most 2 hours
|
|
167
|
+
|
|
168
|
+
// Venue types
|
|
169
|
+
.inPersonOnly()
|
|
170
|
+
.virtualOnly()
|
|
171
|
+
.hybridOnly()
|
|
172
|
+
.virtualOrHybrid()
|
|
173
|
+
.venueTypes(VenueType.IN_PERSON, VenueType.HYBRID)
|
|
174
|
+
|
|
175
|
+
// Formats
|
|
176
|
+
.formats([17, 54]) // Include these formats
|
|
177
|
+
.formats([11], true) // Exclude format 11
|
|
178
|
+
.anyFormat() // Use OR logic for formats
|
|
179
|
+
|
|
180
|
+
// Geographic
|
|
181
|
+
.nearCoordinates({ latitude: 40.7128, longitude: -74.0060 }, 10) // 10 miles
|
|
182
|
+
.sortByDistance()
|
|
183
|
+
|
|
184
|
+
// Text search
|
|
185
|
+
.searchText('Step Study')
|
|
186
|
+
|
|
187
|
+
// Service bodies
|
|
188
|
+
.serviceBodies([123, 456])
|
|
189
|
+
.includeChildServiceBodies()
|
|
190
|
+
|
|
191
|
+
// Sorting and pagination
|
|
192
|
+
.sortBy('meeting_name', 'start_time')
|
|
193
|
+
.sortByAlias(SortKey.WEEKDAY_STATE)
|
|
194
|
+
.paginate(25, 2) // 25 per page, page 2
|
|
195
|
+
|
|
196
|
+
// Response format
|
|
197
|
+
.selectFields('meeting_name', 'location_text', 'start_time')
|
|
198
|
+
.includeFormats()
|
|
199
|
+
.language(Language.SPANISH)
|
|
200
|
+
|
|
201
|
+
// Publication status
|
|
202
|
+
.includeUnpublished()
|
|
203
|
+
.unpublishedOnly()
|
|
204
|
+
|
|
205
|
+
// Utilities
|
|
206
|
+
.getParams() // Get current parameters
|
|
207
|
+
.reset() // Reset all parameters
|
|
208
|
+
.clone() // Clone the builder
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Error Handling
|
|
212
|
+
|
|
213
|
+
The client provides comprehensive error handling with specific error types:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { BmltQueryError, BmltErrorType } from 'bmlt-query-client';
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const meetings = await client.searchMeetings();
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (error instanceof BmltQueryError) {
|
|
222
|
+
console.log(`Error type: ${error.type}`);
|
|
223
|
+
console.log(`User message: ${error.getUserMessage()}`);
|
|
224
|
+
console.log(`Is retryable: ${error.isRetryable()}`);
|
|
225
|
+
|
|
226
|
+
if (error.isType(BmltErrorType.GEOCODING_ERROR)) {
|
|
227
|
+
console.log('Address could not be geocoded');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (error.isType(BmltErrorType.RATE_LIMIT_ERROR)) {
|
|
231
|
+
console.log('Rate limit exceeded, wait and retry');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Error Types
|
|
238
|
+
|
|
239
|
+
- `API_ERROR` - General API errors
|
|
240
|
+
- `NETWORK_ERROR` - Network connectivity issues
|
|
241
|
+
- `VALIDATION_ERROR` - Invalid input parameters
|
|
242
|
+
- `GEOCODING_ERROR` - Address geocoding failures
|
|
243
|
+
- `RATE_LIMIT_ERROR` - Rate limits exceeded
|
|
244
|
+
- `TIMEOUT_ERROR` - Request timeouts
|
|
245
|
+
- `AUTHENTICATION_ERROR` - Authentication failures
|
|
246
|
+
- `SERVER_ERROR` - Server-side errors (5xx)
|
|
247
|
+
- `CLIENT_ERROR` - Client-side errors (4xx)
|
|
248
|
+
- `CONFIGURATION_ERROR` - Invalid configuration
|
|
249
|
+
|
|
250
|
+
## Configuration
|
|
251
|
+
|
|
252
|
+
### Geocoding Options
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const client = new BmltClient({
|
|
256
|
+
rootServerURL: 'https://example.org',
|
|
257
|
+
geocodingOptions: {
|
|
258
|
+
// Retry options
|
|
259
|
+
retryCount: 3,
|
|
260
|
+
timeout: 10000,
|
|
261
|
+
userAgent: 'MyApp/1.0.0',
|
|
262
|
+
|
|
263
|
+
// Region bias (defaults to 'us')
|
|
264
|
+
countryCode: 'ca', // Bias results to Canada
|
|
265
|
+
viewbox: [-125, 25, -65, 50], // [minLon, minLat, maxLon, maxLat]
|
|
266
|
+
bounded: true, // Restrict results to viewbox
|
|
267
|
+
|
|
268
|
+
// Rate limiting (Nominatim allows 1 request per second)
|
|
269
|
+
intervalCap: 1, // Max requests per interval
|
|
270
|
+
interval: 1000, // Interval in ms
|
|
271
|
+
concurrency: 1 // Max concurrent requests
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Custom HTTP Configuration
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
const client = new BmltClient({
|
|
280
|
+
rootServerURL: 'https://example.org',
|
|
281
|
+
timeout: 60000, // 60 second timeout
|
|
282
|
+
userAgent: 'MyApp/2.0.0 (contact@example.com)'
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Region Bias for Geocoding
|
|
287
|
+
|
|
288
|
+
The geocoding service supports region bias to improve address matching accuracy:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// Default US bias
|
|
292
|
+
const usClient = new BmltClient({
|
|
293
|
+
rootServerURL: 'https://example.org'
|
|
294
|
+
// countryCode defaults to 'us'
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Canadian bias
|
|
298
|
+
const caClient = new BmltClient({
|
|
299
|
+
rootServerURL: 'https://example.org',
|
|
300
|
+
geocodingOptions: {
|
|
301
|
+
countryCode: 'ca'
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Geographic bounding box (North America)
|
|
306
|
+
const boundedClient = new BmltClient({
|
|
307
|
+
rootServerURL: 'https://example.org',
|
|
308
|
+
geocodingOptions: {
|
|
309
|
+
viewbox: [-140, 25, -50, 70], // [west, south, east, north]
|
|
310
|
+
bounded: true // Restrict results to this area
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Data Types
|
|
316
|
+
|
|
317
|
+
### Enums
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// Weekdays (1-7)
|
|
321
|
+
enum Weekday {
|
|
322
|
+
SUNDAY = 1, MONDAY = 2, TUESDAY = 3, WEDNESDAY = 4,
|
|
323
|
+
THURSDAY = 5, FRIDAY = 6, SATURDAY = 7
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Venue types
|
|
327
|
+
enum VenueType {
|
|
328
|
+
IN_PERSON = 1, VIRTUAL = 2, HYBRID = 3
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Response formats
|
|
332
|
+
enum BmltDataFormat {
|
|
333
|
+
JSON = 'json', JSONP = 'jsonp', TSML = 'tsml', CSV = 'csv'
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Languages
|
|
337
|
+
enum Language {
|
|
338
|
+
ENGLISH = 'en', GERMAN = 'de', SPANISH = 'es', FRENCH = 'fr',
|
|
339
|
+
ITALIAN = 'it', PORTUGUESE = 'pt', SWEDISH = 'sv', // ... and more
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Response Interfaces
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
interface Meeting {
|
|
347
|
+
id_bigint: string;
|
|
348
|
+
meeting_name: string;
|
|
349
|
+
weekday_tinyint: number;
|
|
350
|
+
venue_type: number;
|
|
351
|
+
start_time: string;
|
|
352
|
+
duration_time: string;
|
|
353
|
+
location_text: string;
|
|
354
|
+
latitude: number;
|
|
355
|
+
longitude: number;
|
|
356
|
+
// ... and many more fields
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
interface Format {
|
|
360
|
+
shared_id_bigint: string;
|
|
361
|
+
key_string: string;
|
|
362
|
+
name_string: string;
|
|
363
|
+
description_string: string;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
interface ServiceBody {
|
|
367
|
+
id: string;
|
|
368
|
+
name: string;
|
|
369
|
+
type: string;
|
|
370
|
+
// ... additional fields
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Advanced Usage
|
|
375
|
+
|
|
376
|
+
### Custom Geocoding Service
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { GeocodingService } from 'bmlt-query-client';
|
|
380
|
+
|
|
381
|
+
const geocoder = new GeocodingService({
|
|
382
|
+
retryCount: 5,
|
|
383
|
+
timeout: 15000,
|
|
384
|
+
userAgent: 'MyApp/1.0.0',
|
|
385
|
+
intervalCap: 1,
|
|
386
|
+
interval: 1000
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Batch geocode multiple addresses
|
|
390
|
+
const addresses = [
|
|
391
|
+
'123 Main St, Boston, MA',
|
|
392
|
+
'456 Oak Ave, Seattle, WA',
|
|
393
|
+
'789 Pine St, Portland, OR'
|
|
394
|
+
];
|
|
395
|
+
|
|
396
|
+
const results = await geocoder.batchGeocode(addresses);
|
|
397
|
+
results.forEach(result => {
|
|
398
|
+
if (result) {
|
|
399
|
+
console.log(`${result.display_name}: ${result.coordinates.latitude}, ${result.coordinates.longitude}`);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Reverse geocoding
|
|
404
|
+
const address = await geocoder.reverseGeocode({
|
|
405
|
+
latitude: 40.7128,
|
|
406
|
+
longitude: -74.0060
|
|
407
|
+
});
|
|
408
|
+
console.log(address.display_name); // "New York, NY, USA"
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Server Information and Capabilities
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// Get server info
|
|
415
|
+
const info = await client.getServerInfo();
|
|
416
|
+
console.log(`Server version: ${info.version}`);
|
|
417
|
+
console.log(`Available endpoints: ${info.availableEndpoints.join(', ')}`);
|
|
418
|
+
|
|
419
|
+
// Get coverage area
|
|
420
|
+
const coverage = await client.getCoverageArea();
|
|
421
|
+
console.log(`Coverage: ${coverage.north_latitude}, ${coverage.south_latitude}`);
|
|
422
|
+
|
|
423
|
+
// Get available field keys
|
|
424
|
+
const fieldKeys = await client.getFieldKeys();
|
|
425
|
+
fieldKeys.forEach(field => {
|
|
426
|
+
console.log(`${field.key}: ${field.description}`);
|
|
427
|
+
});
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Working with Changes
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
// Get changes in date range
|
|
434
|
+
const changes = await client.getChanges({
|
|
435
|
+
start_date: '2024-01-01',
|
|
436
|
+
end_date: '2024-01-31',
|
|
437
|
+
service_body_id: 123
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
changes.forEach(change => {
|
|
441
|
+
console.log(`${change.change_date}: ${change.change_description}`);
|
|
442
|
+
});
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Pagination
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
// Get all meetings with pagination
|
|
449
|
+
async function getAllMeetings() {
|
|
450
|
+
const allMeetings = [];
|
|
451
|
+
let pageNum = 1;
|
|
452
|
+
const pageSize = 100;
|
|
453
|
+
|
|
454
|
+
while (true) {
|
|
455
|
+
const meetings = await client.searchMeetings({
|
|
456
|
+
page_size: pageSize,
|
|
457
|
+
page_num: pageNum
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
if (meetings.length === 0) break;
|
|
461
|
+
|
|
462
|
+
allMeetings.push(...meetings);
|
|
463
|
+
pageNum++;
|
|
464
|
+
|
|
465
|
+
if (meetings.length < pageSize) break; // Last page
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return allMeetings;
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## Best Practices
|
|
473
|
+
|
|
474
|
+
1. **Rate Limiting**: The client includes built-in rate limiting for geocoding. For API requests, be mindful of server load.
|
|
475
|
+
|
|
476
|
+
2. **Error Handling**: Always handle errors appropriately and use the specific error types for better user experience.
|
|
477
|
+
|
|
478
|
+
3. **Caching**: Consider caching results for formats, service bodies, and server info as they don't change frequently.
|
|
479
|
+
|
|
480
|
+
4. **Pagination**: Use pagination for large result sets to avoid overwhelming the server.
|
|
481
|
+
|
|
482
|
+
5. **Geocoding**: The Nominatim service has usage policies. Be respectful and don't abuse it. Consider implementing your own caching layer for frequently geocoded addresses. The geocoding service always returns the first (most relevant) result and defaults to US region bias.
|
|
483
|
+
|
|
484
|
+
## License
|
|
485
|
+
|
|
486
|
+
MIT
|
|
487
|
+
|
|
488
|
+
## Contributing
|
|
489
|
+
|
|
490
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|