@nextgis/geocoder 1.17.0 → 1.18.3

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.
@@ -1,4 +1,4 @@
1
- /** Bundle of @nextgis/geocoder; version: 1.17.0; author: NextGIS */
1
+ /** Bundle of @nextgis/geocoder; version: 1.18.3; author: NextGIS */
2
2
  class CancelError extends Error {
3
3
  constructor() {
4
4
  super();
@@ -376,6 +376,47 @@ function full(val) {
376
376
  return typeof val === 'string' ? !!val : defined(val);
377
377
  }
378
378
 
379
+ function getBoundsCoordinates(b) {
380
+ const westNorth = [b[0], b[1]];
381
+ const eastNorth = [b[2], b[1]];
382
+ const eastSouth = [b[2], b[3]];
383
+ const westSouth = [b[0], b[3]];
384
+ return [westNorth, eastNorth, eastSouth, westSouth, westNorth];
385
+ }
386
+
387
+ function degrees2meters(lng, lat) {
388
+ lat = lat > 85.06 ? 85.06 : lat < -85.06 ? -85.06 : lat;
389
+ const x = (lng * 20037508.34) / 180;
390
+ let y = Math.log(Math.tan(((90 + lat) * Math.PI) / 360)) / (Math.PI / 180);
391
+ y = (y * 20037508.34) / 180;
392
+ return [x, y];
393
+ }
394
+ // export function degrees2meters2(lon: number, lat: number): [number, number] {
395
+ // const radius = 6378137.0;
396
+ // const x = (radius * lon * Math.PI) / 180.0;
397
+ // const y =
398
+ // radius * Math.log(Math.tan(Math.PI / 4.0 + (lat * Math.PI) / 360.0));
399
+ // return [x, y];
400
+ // }
401
+ // export function meters2degress2(x: number, y: number): LngLatArray {
402
+ // const radius = 6378137.0;
403
+ // const lon = (180 / Math.PI) * (x / radius);
404
+ // const lat = (360 / Math.PI) * (Math.atan(Math.exp(y / radius)) - Math.PI / 4);
405
+ // return [lon, lat];
406
+ // }
407
+ // const lon = -77.035974;
408
+ // const lat = 38.898717;
409
+ // console.log(degrees2meters(lon, lat), degrees2meters2(lon, lat));
410
+ // const x = -8575605.398443861;
411
+ // const y = 4707174.018280405;
412
+ // console.log(meters2degress(x, y), meters2degress2(x, y));
413
+
414
+ function isLngLatBoundsArray(array) {
415
+ return (Array.isArray(array) &&
416
+ array.length === 4 &&
417
+ array.every((x) => typeof x === 'number'));
418
+ }
419
+
379
420
  /**
380
421
  * Copy the values of all of the enumerable own properties from one or more source objects to a
381
422
  * target object. Returns the target object.
@@ -451,11 +492,29 @@ function objectRemoveEmpty(obj) {
451
492
  return newObj;
452
493
  }
453
494
 
495
+ function round(val, toFixed) {
496
+ const n = toFixed ? Number('1e+' + toFixed) : 1;
497
+ return Math.round((val + Number.EPSILON) * n) / n;
498
+ }
499
+
500
+ function isObject$1(val) {
501
+ return Object.prototype.toString.call(val) === '[object Object]';
502
+ }
503
+ function isArray(val) {
504
+ return Object.prototype.toString.call(val) === '[object Array]';
505
+ }
506
+
454
507
  function fixUrlStr(url) {
455
508
  // remove double slash
456
509
  return url.replace(/([^:]\/)\/+/g, '$1');
457
510
  }
458
511
 
512
+ /**
513
+ * Represents the base class for a geocoder.
514
+ * This abstract class provides the basic methods that every geocoder should implement.
515
+ *
516
+ * @template O - The type of options that can be passed to the geocoder.
517
+ */
459
518
  class BaseGeocoder {
460
519
  constructor(options) {
461
520
  if (options) {
@@ -501,6 +560,11 @@ class Geocoder extends BaseGeocoder {
501
560
  }
502
561
  }
503
562
 
563
+ /**
564
+ * Abstract base class representing a search provider.
565
+ * It builds upon the BaseGeocoder functionalities with provider-specific properties.
566
+ * @template O - Options type which extends BaseProviderOptions.
567
+ */
504
568
  class BaseProvider extends BaseGeocoder {
505
569
  }
506
570
 
@@ -2138,59 +2202,395 @@ NgwConnector$1.errors = {
2138
2202
  */
2139
2203
  var NgwConnector = NgwConnector$1;
2140
2204
 
2205
+ function reEscape(s) {
2206
+ return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
2207
+ }
2208
+ function like(b, a, iLike) {
2209
+ a = String(a);
2210
+ b = String(b);
2211
+ if (a === b)
2212
+ return true;
2213
+ if (iLike && a.toUpperCase() === b.toUpperCase())
2214
+ return true;
2215
+ const re = `^${reEscape(a)}$`.replace(/%/g, '.*').replace('_', '.');
2216
+ return new RegExp(re, iLike ? 'i' : '').exec(b) !== null;
2217
+ }
2218
+ const operationsAliases = {
2219
+ // greater(>)
2220
+ gt: (a, b) => a > b,
2221
+ // lower(<)
2222
+ lt: (a, b) => a < b,
2223
+ // greater or equal(>=)
2224
+ ge: (a, b) => a >= b,
2225
+ // lower or equal(<=)
2226
+ le: (a, b) => a <= b,
2227
+ // equal(=)
2228
+ eq: (a, b) => a === b,
2229
+ // not equal(!=)
2230
+ ne: (a, b) => a !== b,
2231
+ in: (a, b) => b.indexOf(a) !== -1,
2232
+ notin: (a, b) => b.indexOf(a) === -1,
2233
+ // LIKE SQL statement(for strings compare)
2234
+ like: (a, b) => {
2235
+ return like(a, b);
2236
+ },
2237
+ // ILIKE SQL statement(for strings compare)
2238
+ ilike: (a, b) => {
2239
+ return like(a, b, true);
2240
+ },
2241
+ };
2242
+ function isPropertyFilter(filter) {
2243
+ const pf = filter;
2244
+ if (pf.length === 3 &&
2245
+ typeof pf[0] === 'string' &&
2246
+ typeof pf[1] === 'string') {
2247
+ return true;
2248
+ }
2249
+ return false;
2250
+ }
2251
+ function propertiesFilter(properties, filters) {
2252
+ const logic = typeof filters[0] === 'string' ? filters[0] : 'all';
2253
+ const filterFunction = (p) => {
2254
+ if (isPropertyFilter(p)) {
2255
+ const [field, operation, value] = p;
2256
+ const operationExec = operationsAliases[operation];
2257
+ if (operationExec) {
2258
+ if (operation === 'like' || operation === 'ilike') {
2259
+ if (typeof field === 'string') {
2260
+ let prop = '';
2261
+ const value_ = field.replace(/^%?(\w+)%?$/, (match, cleanField) => {
2262
+ prop = properties[cleanField];
2263
+ return field.replace(cleanField, value);
2264
+ });
2265
+ return operationExec(prop, value_);
2266
+ }
2267
+ }
2268
+ return operationExec(properties[field], value);
2269
+ }
2270
+ return false;
2271
+ }
2272
+ else {
2273
+ return propertiesFilter(properties, p);
2274
+ }
2275
+ };
2276
+ const filters_ = filters.filter((x) => Array.isArray(x));
2277
+ return logic === 'any'
2278
+ ? filters_.some(filterFunction)
2279
+ : filters_.every(filterFunction);
2280
+ }
2281
+
2282
+ const FEATURE_REQUEST_PARAMS = {
2283
+ srs: 4326,
2284
+ geom_format: 'geojson',
2285
+ };
2286
+ function updateItemRequestParam(params, options) {
2287
+ const { extensions, geom, fields, srs, ilike, like } = options;
2288
+ if (extensions) {
2289
+ params.extensions = extensions.join(',');
2290
+ }
2291
+ if (fields !== undefined) {
2292
+ params.fields = Array.isArray(fields) ? fields.join(',') : '';
2293
+ }
2294
+ if (geom !== undefined) {
2295
+ params.geom = geom ? 'yes' : 'no';
2296
+ if (!geom) {
2297
+ delete params.srs;
2298
+ delete params.geom_format;
2299
+ }
2300
+ }
2301
+ if (defined(ilike)) {
2302
+ params.ilike = ilike;
2303
+ }
2304
+ else if (defined(like)) {
2305
+ params.like = like;
2306
+ }
2307
+ if (defined(srs)) {
2308
+ params.srs = srs;
2309
+ }
2310
+ }
2311
+ // NGW REST API is not able to filtering by combined queries
2312
+ // therefore the filter is divided into several requests
2313
+ function createFeatureFieldFilterQueries(opt) {
2314
+ const queries = getQueries(opt);
2315
+ return CancelablePromise.all(queries).then((itemsParts) => {
2316
+ // this list of ids used for optimization
2317
+ const ids = [];
2318
+ const items = [];
2319
+ for (const part of itemsParts) {
2320
+ for (const item of part) {
2321
+ if (!ids.includes(item.id)) {
2322
+ items.push(item);
2323
+ ids.push(item.id);
2324
+ }
2325
+ }
2326
+ }
2327
+ const offset = opt.offset !== undefined ? opt.offset : 0;
2328
+ const limit = opt.limit !== undefined ? opt.limit : items.length;
2329
+ if (opt.offset || opt.limit) {
2330
+ return items.splice(offset, limit);
2331
+ }
2332
+ return items;
2333
+ });
2334
+ }
2335
+ function createParam(pf) {
2336
+ const [field, operation, value] = pf;
2337
+ const isFldStr = field !== 'id' ? 'fld_' : '';
2338
+ let vStart = '';
2339
+ let vEnd = '';
2340
+ const field_ = String(field)
2341
+ .trim()
2342
+ .replace(/^(%?)(.+?)(%?)$/, (m, a, b, c) => {
2343
+ vStart = a;
2344
+ vEnd = c;
2345
+ return b;
2346
+ });
2347
+ const v = vStart + value + vEnd;
2348
+ return [`${isFldStr}${field_}__${operation}`, v];
2349
+ }
2350
+ function getQueries(opt, _queries = [], _parentAllParams = []) {
2351
+ const { filters } = opt;
2352
+ const logic = typeof filters[0] === 'string' ? filters[0] : 'all';
2353
+ const filters_ = filters.filter((x) => Array.isArray(x));
2354
+ if (logic === 'any') {
2355
+ for (const f of filters_) {
2356
+ if (isPropertyFilter(f)) {
2357
+ _queries.push(fetchNgwLayerItemsRequest({
2358
+ ...opt,
2359
+ paramList: [..._parentAllParams, createParam(f)],
2360
+ }));
2361
+ }
2362
+ else {
2363
+ getQueries({
2364
+ ...opt,
2365
+ filters: f,
2366
+ }, _queries, [..._parentAllParams]);
2367
+ }
2368
+ }
2369
+ }
2370
+ else if (logic === 'all') {
2371
+ const filters = [];
2372
+ const propertiesFilterList = [];
2373
+ for (const f of filters_) {
2374
+ if (isPropertyFilter(f)) {
2375
+ filters.push(createParam(f));
2376
+ }
2377
+ else {
2378
+ propertiesFilterList.push(f);
2379
+ }
2380
+ }
2381
+ if (propertiesFilterList.length) {
2382
+ for (const x of propertiesFilterList) {
2383
+ getQueries({
2384
+ ...opt,
2385
+ filters: x,
2386
+ }, _queries, [..._parentAllParams, ...filters]);
2387
+ }
2388
+ }
2389
+ else {
2390
+ _queries.push(fetchNgwLayerItemsRequest({
2391
+ ...opt,
2392
+ paramList: [..._parentAllParams, ...filters],
2393
+ }));
2394
+ }
2395
+ }
2396
+ return _queries;
2397
+ }
2398
+ function createWktFromCoordArray(coord) {
2399
+ const polygon = coord.map(([lng, lat]) => {
2400
+ const [x, y] = degrees2meters(lng, lat).map((c) => round(c));
2401
+ return x + ' ' + y;
2402
+ });
2403
+ return `POLYGON((${polygon.join(', ')}))`;
2404
+ }
2405
+ function fetchNgwLayerItemsRequest(options) {
2406
+ const params = {
2407
+ ...FEATURE_REQUEST_PARAMS,
2408
+ };
2409
+ const { limit, cache, signal, offset, orderBy, paramList, connector, geomFormat, intersects, resourceId, } = options;
2410
+ if (limit) {
2411
+ if (limit !== Number.POSITIVE_INFINITY) {
2412
+ params.limit = limit;
2413
+ }
2414
+ }
2415
+ else {
2416
+ // Strict restriction on loading data from large layers
2417
+ params.limit = 7000;
2418
+ }
2419
+ if (offset) {
2420
+ params.offset = offset;
2421
+ }
2422
+ if (geomFormat) {
2423
+ params.geom_format = geomFormat;
2424
+ }
2425
+ updateItemRequestParam(params, options);
2426
+ if (orderBy) {
2427
+ params.order_by = orderBy.join(',');
2428
+ }
2429
+ if (Array.isArray(intersects)) {
2430
+ const coordinates = isLngLatBoundsArray(intersects)
2431
+ ? getBoundsCoordinates(intersects)
2432
+ : intersects;
2433
+ params.intersects = createWktFromCoordArray(coordinates);
2434
+ }
2435
+ else if (typeof intersects === 'string') {
2436
+ params.intersects = intersects;
2437
+ }
2438
+ if (paramList) {
2439
+ params.paramList = paramList;
2440
+ }
2441
+ const reqParams = {
2442
+ id: resourceId,
2443
+ ...params,
2444
+ };
2445
+ return connector.get('feature_layer.feature.collection', { cache, signal }, reqParams);
2446
+ }
2447
+
2448
+ function prepareNgwFieldsToPropertiesFilter(fields) {
2449
+ let f;
2450
+ for (f in fields) {
2451
+ const field = fields[f];
2452
+ if (isObject$1(field)) {
2453
+ const date = field;
2454
+ if (defined(date.year) && defined(date.month) && defined(date.day)) {
2455
+ const dt = [
2456
+ date.year,
2457
+ date.month - 1,
2458
+ date.day,
2459
+ ];
2460
+ if ('hour' in date) {
2461
+ [date.hour, date.minute, date.second].forEach((x) => {
2462
+ dt.push(x);
2463
+ });
2464
+ }
2465
+ fields[f] = new Date(...dt).toISOString();
2466
+ }
2467
+ }
2468
+ else if (f === 'id' && typeof field === 'string') {
2469
+ fields[f] = field.split(',').map(Number);
2470
+ }
2471
+ }
2472
+ return fields;
2473
+ }
2474
+
2475
+ function fetchNgwLayerItems(options) {
2476
+ const filters = options.filters;
2477
+ if (filters) {
2478
+ return createFeatureFieldFilterQueries({
2479
+ ...options,
2480
+ filters,
2481
+ }).then((data) => {
2482
+ var _a;
2483
+ const clientFilterValidate = (_a = options.clientFilterValidate) !== null && _a !== void 0 ? _a : isFilterWithAnyCase(filters);
2484
+ // Additional client-side filter check
2485
+ if (clientFilterValidate) {
2486
+ data = data.filter((y) => {
2487
+ const fields = prepareNgwFieldsToPropertiesFilter({
2488
+ ...y.fields,
2489
+ id: y.id,
2490
+ });
2491
+ const result = propertiesFilter(fields, filters);
2492
+ return result;
2493
+ });
2494
+ }
2495
+ return data;
2496
+ });
2497
+ }
2498
+ else {
2499
+ return fetchNgwLayerItemsRequest(options);
2500
+ }
2501
+ }
2502
+ function isFilterWithAnyCase(filters) {
2503
+ if (filters[0] === 'any') {
2504
+ return true;
2505
+ }
2506
+ for (let i = 1; i < filters.length; i++) {
2507
+ const p = filters[i];
2508
+ if (isArray(p)) {
2509
+ const deep = isFilterWithAnyCase(p);
2510
+ if (deep) {
2511
+ return true;
2512
+ }
2513
+ }
2514
+ }
2515
+ return false;
2516
+ }
2517
+
2141
2518
  class NgwProvider extends BaseProvider {
2142
2519
  constructor(options) {
2520
+ var _a;
2143
2521
  super(options);
2144
2522
  this.caseInsensitive = true;
2145
- this.searchUrl = 'https://maxim.nextgis.com';
2146
2523
  this.searchResources = [];
2147
- this.requests = [];
2524
+ this._abortControllers = [];
2525
+ if (!options.searchUrl && !((_a = options.connectorOptions) === null || _a === void 0 ? void 0 : _a.baseUrl)) {
2526
+ throw new Error('The `searchUrl` of `connectorOptions.baseUrl` are required');
2527
+ }
2148
2528
  this.connector =
2149
2529
  options.connector ||
2150
2530
  new NgwConnector({
2151
2531
  baseUrl: this.searchUrl,
2532
+ ...options.connectorOptions,
2152
2533
  });
2153
2534
  this.searchResources = options.searchResources;
2154
2535
  }
2155
2536
  abort() {
2156
- this.requests.forEach((x) => {
2157
- x.cancel();
2537
+ this._abortControllers.forEach((a) => {
2538
+ a.abort();
2158
2539
  });
2159
- this.requests = [];
2160
- }
2161
- async *search(like) {
2162
- for (const resource of this.searchResources) {
2163
- const limit = resource.limit || 300;
2164
- const request = this.connector.get('feature_layer.store', {
2165
- headers: { RANGE: `items=0-${limit}` },
2166
- }, {
2167
- id: resource.id,
2168
- like,
2169
- });
2170
- this.requests.push(request);
2171
- request.catch((er) => {
2172
- console.warn(er);
2173
- });
2174
- const resp = await request;
2175
- const entries = [];
2176
- if (resp) {
2177
- resp.forEach((x) => {
2178
- entries.push({
2179
- query: like,
2180
- resourceId: resource.id,
2181
- ...x,
2182
- });
2540
+ this._abortControllers.length = 0;
2541
+ }
2542
+ async *search(query) {
2543
+ var _a, _b;
2544
+ const signal = this._makeSignal();
2545
+ for (const config of this.searchResources) {
2546
+ if (signal.aborted) {
2547
+ break;
2548
+ }
2549
+ const { searchMethod } = config;
2550
+ const limit = (_a = config.limit) !== null && _a !== void 0 ? _a : NgwProvider.limit;
2551
+ //
2552
+ const resourceId = (_b = config.id) !== null && _b !== void 0 ? _b : config.resourceId;
2553
+ try {
2554
+ const resource = await this.connector.getResourceOrFail(resourceId, {
2555
+ cache: true,
2183
2556
  });
2557
+ const options = {
2558
+ connector: this.connector,
2559
+ resourceId,
2560
+ signal,
2561
+ limit,
2562
+ };
2563
+ if (searchMethod === 'like') {
2564
+ options.like = query;
2565
+ }
2566
+ else {
2567
+ options.ilike = query;
2568
+ }
2569
+ const request = fetchNgwLayerItems(options);
2570
+ const entries = await request;
2571
+ for (const item of this._items({
2572
+ entries,
2573
+ query,
2574
+ config,
2575
+ resourceItem: resource,
2576
+ })) {
2577
+ item.result = () => this.result(item);
2578
+ yield item;
2579
+ }
2184
2580
  }
2185
- for (const item of this._items(entries)) {
2186
- item.result = () => this.result(item);
2187
- yield item;
2581
+ catch (er) {
2582
+ if (er &&
2583
+ 'name' in er &&
2584
+ er.name !== 'CancelError') {
2585
+ console.warn(er);
2586
+ }
2188
2587
  }
2189
2588
  }
2190
2589
  }
2191
2590
  result(item) {
2591
+ const signal = this._makeSignal();
2192
2592
  const req = this.connector
2193
- .get('feature_layer.feature.item_extent', null, {
2593
+ .get('feature_layer.feature.item_extent', { signal }, {
2194
2594
  id: item.resourceId,
2195
2595
  fid: item.featureId,
2196
2596
  })
@@ -2201,25 +2601,47 @@ class NgwProvider extends BaseProvider {
2201
2601
  extent: [e.minLon, e.minLat, e.maxLon, e.maxLat],
2202
2602
  };
2203
2603
  });
2204
- this.requests.push(req);
2205
2604
  return req;
2206
2605
  }
2207
- _items(entries) {
2606
+ _makeSignal() {
2607
+ const abortController = new AbortController();
2608
+ this._abortControllers.push(abortController);
2609
+ return abortController.signal;
2610
+ }
2611
+ _items({ entries, query, config, resourceItem, }) {
2612
+ var _a;
2208
2613
  const items = [];
2209
- entries.forEach((entry) => {
2210
- const meta = this.searchResources.find((x) => x.id === entry.resourceId);
2211
- if (meta) {
2614
+ let labelField = undefined;
2615
+ if (resourceItem.feature_layer) {
2616
+ labelField = (_a = resourceItem.feature_layer.fields.find((f) => f.label_field)) === null || _a === void 0 ? void 0 : _a.keyname;
2617
+ }
2618
+ const meta = this.searchResources.find((x) => { var _a; return (_a = x.id) !== null && _a !== void 0 ? _a : x.resourceId === resourceItem.resource.id; });
2619
+ if (meta) {
2620
+ entries.forEach((entry) => {
2621
+ let text = labelField ? entry.fields[labelField] : entry.id;
2622
+ if (config.renderSearch) {
2623
+ text = config.renderSearch({
2624
+ item: entry,
2625
+ resourceItem,
2626
+ });
2627
+ }
2628
+ else if (labelField) {
2629
+ text = entry.fields[labelField];
2630
+ }
2212
2631
  items.push({
2213
- query: entry.query,
2214
- text: entry.label,
2632
+ query: query,
2633
+ text,
2215
2634
  featureId: entry.id,
2216
- resourceId: entry.resourceId,
2635
+ resourceId: resourceItem.resource.id,
2636
+ item: entry,
2637
+ resourceItem,
2217
2638
  });
2218
- }
2219
- });
2639
+ });
2640
+ }
2220
2641
  return items;
2221
2642
  }
2222
2643
  }
2644
+ NgwProvider.limit = 300;
2223
2645
 
2224
2646
  class NominatimProvider extends BaseProvider {
2225
2647
  constructor(options) {
@@ -2285,6 +2707,12 @@ class NominatimProvider extends BaseProvider {
2285
2707
  }
2286
2708
  }
2287
2709
 
2710
+ /**
2711
+ * Modern geocoder on async generators. May use different data providers.
2712
+ *
2713
+ * @packageDocumentation
2714
+ * @module geocoder
2715
+ */
2288
2716
  function create(options) {
2289
2717
  return new Geocoder(options);
2290
2718
  }