@geosdi/ngx-geocoding 1.1.4 → 1.2.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.
@@ -1,19 +1,19 @@
1
1
  import * as i0 from '@angular/core';
2
- import { NgModule, inject, Injectable } from '@angular/core';
2
+ import { NgModule, InjectionToken, inject, Injectable } from '@angular/core';
3
3
  import { HttpClient } from '@angular/common/http';
4
- import { Subject } from 'rxjs';
5
- import { tap, map } from 'rxjs/operators';
4
+ import { Subject, of, from, throwError, Observable } from 'rxjs';
5
+ import { catchError, switchMap, map, tap } from 'rxjs/operators';
6
6
 
7
7
  /**
8
8
  * @author Vito Salvia - CNR IMAA geoSDI Group
9
9
  * @email vito.salvia@gmail.com
10
10
  */
11
11
  class NgxGeocodingModule {
12
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.6", ngImport: i0, type: NgxGeocodingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
13
- /** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.6", ngImport: i0, type: NgxGeocodingModule }); }
14
- /** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.6", ngImport: i0, type: NgxGeocodingModule }); }
12
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NgxGeocodingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
13
+ /** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: NgxGeocodingModule }); }
14
+ /** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NgxGeocodingModule }); }
15
15
  }
16
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.6", ngImport: i0, type: NgxGeocodingModule, decorators: [{
16
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NgxGeocodingModule, decorators: [{
17
17
  type: NgModule,
18
18
  args: [{
19
19
  declarations: [],
@@ -26,27 +26,329 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.6", ngImpor
26
26
  * @author Vito Salvia - CNR IMAA geoSDI Group
27
27
  * @email vito.salvia@gmail.com
28
28
  */
29
+ /**
30
+ * La configurazione della ricerca, che **può non esserci** : in quel caso vale
31
+ * il comportamento storico, cioè Nominatim e basta.
32
+ */
33
+ const NGX_GEOCODING_CONFIG = new InjectionToken("NGX_GEOCODING_CONFIG");
34
+ /**
35
+ * Dichiara da dove arrivano gli indirizzi, nei `providers` dell'applicazione :
36
+ *
37
+ * ```ts
38
+ * provideNgxGeocoding({
39
+ * sources: ["google", "nominatim"],
40
+ * country: "it",
41
+ * bias: { lat: 40.83, lon: 14.11, radiusM: 25_000 },
42
+ * })
43
+ * ```
44
+ *
45
+ * L'ordine è quello in cui le sorgenti si provano, e una che non è utilizzabile
46
+ * si salta : la stessa riga vale quindi per il deploy che ha la chiave Google e
47
+ * per quello che non ce l'ha.
48
+ *
49
+ * Chi non la chiama non cambia niente.
50
+ */
51
+ function provideNgxGeocoding(config) {
52
+ return { provide: NGX_GEOCODING_CONFIG, useValue: config };
53
+ }
54
+
55
+ /**
56
+ * L'SDK Maps JS dell'applicazione ospite, e l'attesa perché sia pronto.
57
+ *
58
+ * **Questa libreria non lo carica** : il tag lo mette in pagina chi la usa, con
59
+ * la propria chiave —
60
+ *
61
+ * ```html
62
+ * <script defer src="https://maps.googleapis.com/maps/api/js?key=…&libraries=places&language=it"></script>
63
+ * ```
64
+ *
65
+ * — e le ragioni perché resti così sono due : la chiave è dell'applicazione e
66
+ * non deve passare di qui, e caricarlo una seconda volta vorrebbe dire due
67
+ * copie dell'SDK nella stessa pagina.
68
+ *
69
+ * Serve però **aspettarlo** : quel tag è di norma `defer`, quindi chi cerca un
70
+ * indirizzo subito dopo l'avvio può arrivare prima di lui.
71
+ *
72
+ * Della query contano due parametri : `libraries=places`, senza cui non c'è
73
+ * niente da aspettare, e `language`, che decide la lingua delle proposte —
74
+ * `getPlacePredictions` non ha un parametro per chiederla.
75
+ *
76
+ * @author Vito Salvia - CNR IMAA geoSDI Group
77
+ * @email vito.salvia@gmail.com
78
+ */
79
+ /** Quanto si aspetta l'SDK prima di rinunciare, e ogni quanto lo si guarda. */
80
+ const ATTESA = {
81
+ tetto_ms: 10_000,
82
+ passo_ms: 100,
83
+ };
84
+ /** Gli stati dell'SDK su cui si decide qualcosa. */
85
+ const STATO_PLACES = {
86
+ ok: "OK",
87
+ nessuno: "ZERO_RESULTS",
88
+ negato: "REQUEST_DENIED",
89
+ quota: "OVER_QUERY_LIMIT",
90
+ };
91
+ /** La libreria `places` se è già in pagina, `undefined` altrimenti. */
92
+ function places() {
93
+ return window.google?.maps?.places;
94
+ }
95
+ /** Il costruttore del `Geocoder`, che sta nell'SDK di base. */
96
+ function geocoder() {
97
+ return window.google?.maps?.Geocoder;
98
+ }
99
+ /**
100
+ * La libreria `places`, aspettando che l'SDK finisca di caricarsi.
101
+ *
102
+ * Rifiuta dopo {@link ATTESA} : il chiamante ripiega sulla strada successiva,
103
+ * perché una ricerca che non risponde mai è peggio di una che risponde meno
104
+ * bene. Il motivo tipico di un'attesa che scade è che al tag dello script manchi
105
+ * `libraries=places` — l'SDK c'è, ma quella libreria no.
106
+ */
107
+ function attendiPlaces() {
108
+ return attendi(places, "la libreria 'places' (manca `libraries=places` ?)");
109
+ }
110
+ /**
111
+ * Il `Geocoder`, aspettando l'SDK.
112
+ *
113
+ * **Non vuole `libraries=places`** : sta nell'SDK di base, ed è la ragione per
114
+ * cui regge da ultima spiaggia anche dove Places non è disponibile.
115
+ */
116
+ function attendiGeocoder() {
117
+ return attendi(geocoder, "il Geocoder").then((costruttore) => new costruttore());
118
+ }
119
+ /** L'attesa vera, uguale per le due parti dell'SDK. */
120
+ function attendi(cerca, cosa) {
121
+ const gia = cerca();
122
+ if (gia)
123
+ return Promise.resolve(gia);
124
+ return new Promise((risolvi, rifiuta) => {
125
+ const scadenza = Date.now() + ATTESA.tetto_ms;
126
+ const attesa = setInterval(() => {
127
+ const pronto = cerca();
128
+ if (pronto) {
129
+ clearInterval(attesa);
130
+ risolvi(pronto);
131
+ return;
132
+ }
133
+ if (Date.now() > scadenza) {
134
+ clearInterval(attesa);
135
+ rifiuta(new Error(`SDK Maps senza ${cosa}`));
136
+ }
137
+ }, ATTESA.passo_ms);
138
+ });
139
+ }
140
+
141
+ /**
142
+ * @author Vito Salvia - CNR IMAA geoSDI Group
143
+ * @email vito.salvia@gmail.com
144
+ */
145
+ /** Il servizio pubblico di OpenStreetMap, quando non se ne dichiara un altro. */
146
+ const NOMINATIM = "https://nominatim.openstreetmap.org/search.php";
147
+ /** Le sorgenti da provare quando l'applicazione non dice niente. */
148
+ const SOLO_NOMINATIM = ["nominatim"];
149
+ /** Quanti metri fa un grado di latitudine, per il riquadro del Geocoder. */
150
+ const METRI_PER_GRADO = 111_320;
151
+ /**
152
+ * La ricerca di indirizzi, da una o più sorgenti.
153
+ *
154
+ * **Senza configurazione fa quello che ha sempre fatto** : interroga Nominatim,
155
+ * che non vuole nessuna chiave. Google si accende con
156
+ * `provideNgxGeocoding({ sources: ["google", "nominatim"] })`, e vuole che
157
+ * l'applicazione abbia in pagina l'**SDK Maps** con `libraries=places`.
158
+ *
159
+ * **Perché l'SDK e non una chiamata a Google**, che è la cosa da sapere prima
160
+ * di «semplificare» questo codice : delle API di Google dal browser si può
161
+ * chiamare la sola **Places (New)**, l'unica che esponga CORS, e quel prodotto
162
+ * va abilitato a parte sul progetto Cloud — dove non lo è, una `fetch` prende
163
+ * `403 SERVICE_DISABLED`. **Places legacy** e **Geocoding** CORS non ce l'hanno
164
+ * affatto, ma l'SDK le raggiunge da sé : passando da lui la ricerca funziona
165
+ * con le abilitazioni che il progetto ha già.
166
+ *
167
+ * Dentro `google` c'è quindi una **cascata**, ed è la stessa che si scriverebbe
168
+ * lato server :
169
+ *
170
+ * 1. **`AutocompleteSuggestion`** (Places **New**), che è la strada
171
+ * raccomandata da Google dal 1° marzo 2025 e l'unica offerta ai progetti
172
+ * nuovi. Sotto sta un prodotto che va **abilitato a parte** sulla console
173
+ * Cloud : dove non lo è risponde `403`, e si scende;
174
+ * 2. **`AutocompleteService`** (Places legacy), che dà le stesse proposte
175
+ * incrementali. Google la dà per deprecata — «non disponibile per i nuovi
176
+ * clienti», con dodici mesi di preavviso prima di spegnerla — ma è quella
177
+ * che risponde sui progetti dove la New non è stata abilitata;
178
+ * 3. **`Geocoder`**, quando nemmeno Places risponde : nessuna proposta
179
+ * incrementale, di norma un risultato solo, ma **porta già le coordinate** e
180
+ * sta nell'SDK di base, quindi non dipende da `libraries=places`.
181
+ *
182
+ * I gradini si scendono da sé e non si risalgono nella stessa sessione. Il
183
+ * primo è in cima **anche se oggi, su un progetto senza la New, fallisce
184
+ * sempre** : costa una richiesta negata per sessione, e il giorno in cui quel
185
+ * prodotto viene abilitato la ricerca si sposta sulla strada giusta senza che
186
+ * nessuno tocchi il codice.
187
+ *
188
+ * **Le proposte di Places non portano le coordinate**, che si chiedono per il
189
+ * solo luogo scelto : vedi {@link resolve}, ed è il motivo per cui esiste.
190
+ */
29
191
  class NgxGeocodingService {
30
192
  constructor() {
31
193
  this.subjectLocation = new Subject();
32
194
  this.subjectNoResults = new Subject();
33
195
  this.subjectRemove = new Subject();
34
196
  this.http = inject(HttpClient);
197
+ this.config = inject(NGX_GEOCODING_CONFIG, {
198
+ optional: true,
199
+ });
200
+ /**
201
+ * Una sorgente che ha detto di no non si richiama per il resto della
202
+ * sessione : chiave senza il prodotto abilitato, referrer fuori dalle
203
+ * restrizioni, quota. Senza questo sarebbe una richiesta negata a ogni
204
+ * tasto premuto.
205
+ */
206
+ this.spente = new Set();
207
+ /**
208
+ * Dentro `google`, i gradini già scesi : `AutocompleteSuggestion` (Places
209
+ * New) e `AutocompleteService` (Places legacy). Da lì in poi si parte
210
+ * direttamente da dove si era arrivati.
211
+ */
212
+ this.suggestionSpenta = false;
213
+ this.placesSpenta = false;
35
214
  }
36
215
  /**
216
+ * Le proposte per quello che si sta digitando, dalla prima sorgente che
217
+ * risponde.
37
218
  *
38
- * @param address
219
+ * @param address il testo digitato
39
220
  */
40
221
  geocode(address) {
41
- const encoded = encodeURIComponent(address);
42
- return this.http
43
- .get(`https://nominatim.openstreetmap.org/search.php?q=${encoded}&format=jsonv2`)
44
- .pipe(tap((res) => {
45
- if (res.length <= 0) {
222
+ return this.daSorgenti(address, this.sorgenti());
223
+ }
224
+ /**
225
+ * Le coordinate del luogo **scelto**, chieste solo adesso.
226
+ *
227
+ * Le proposte di Places arrivano senza, e risolverle tutte a ogni tasto
228
+ * premuto vorrebbe dire una richiesta di dettaglio per proposta. Chi le ha
229
+ * già — Nominatim le manda sempre, e il `Geocoder` pure — non paga niente e
230
+ * si riprende il luogo così com'è, quindi **chiamarla è sempre corretto** :
231
+ * è la riga da mettere dove si sceglie una proposta, prima di usarne `lat`
232
+ * e `lon`.
233
+ *
234
+ * Fallendo restituisce il luogo com'è, senza coordinate : chi ha chiamato
235
+ * lo sa già gestire, ed è meglio che muovere una mappa a caso.
236
+ *
237
+ * @param place la proposta scelta fra quelle di {@link geocode}
238
+ */
239
+ resolve(place) {
240
+ if (!place?.place_id || (place.lat && place.lon))
241
+ return of(place);
242
+ if (this.spente.has("google"))
243
+ return of(place);
244
+ if (!this.sorgenti().includes("google"))
245
+ return of(place);
246
+ // Lo stesso ordine delle proposte : chi le ha date sa dare anche il
247
+ // dettaglio, e chiedere il dettaglio a Places (New) quando le proposte
248
+ // sono arrivate dalla legacy sarebbe una richiesta negata in più.
249
+ if (!this.suggestionSpenta) {
250
+ return this.luogoDaSuggestion(place).pipe(catchError((err) => {
251
+ this.suggestionSpenta = true;
252
+ console.warn("[ngx-geocoding] dettaglio da Places (New) non " +
253
+ `riuscito, si prova la legacy — ${this.perche(err)}`);
254
+ return this.luogoDaLegacy(place);
255
+ }));
256
+ }
257
+ if (this.placesSpenta)
258
+ return of(place);
259
+ return this.luogoDaLegacy(place);
260
+ }
261
+ /**
262
+ * Le coordinate con **Places (New)** : `Place` nasce con l'id e
263
+ * `fetchFields` lo **riempie**, quindi i campi si leggono dall'oggetto
264
+ * dopo l'attesa e non dal valore restituito.
265
+ */
266
+ luogoDaSuggestion(place) {
267
+ return from(attendiPlaces()).pipe(switchMap((places) => {
268
+ const Luogo = places.Place;
269
+ if (!Luogo) {
270
+ return throwError(() => new Error("Place non c'è in questo SDK"));
271
+ }
272
+ const luogo = new Luogo({
273
+ id: place.place_id,
274
+ });
275
+ return from(luogo.fetchFields({
276
+ fields: ["location", "formattedAddress"],
277
+ })).pipe(map(() => ({
278
+ ...place,
279
+ display_name: place.display_name ?? luogo.formattedAddress ?? "",
280
+ lat: this.testo(luogo.location?.lat()),
281
+ lon: this.testo(luogo.location?.lng()),
282
+ })));
283
+ }));
284
+ }
285
+ /** Le coordinate con **Places legacy**, cioè `PlacesService.getDetails`. */
286
+ luogoDaLegacy(place) {
287
+ return from(attendiPlaces()).pipe(switchMap((places) => new Observable((chi) => {
288
+ new places.PlacesService(document.createElement("div")).getDetails({
289
+ placeId: place.place_id,
290
+ fields: ["geometry", "formatted_address"],
291
+ }, (luogo, stato) => {
292
+ if (stato !== STATO_PLACES.ok || !luogo) {
293
+ chi.error(new Error(stato));
294
+ return;
295
+ }
296
+ const punto = luogo.geometry?.location;
297
+ chi.next({
298
+ ...place,
299
+ display_name: place.display_name ??
300
+ luogo.formatted_address ??
301
+ "",
302
+ lat: this.testo(punto?.lat()),
303
+ lon: this.testo(punto?.lng()),
304
+ });
305
+ chi.complete();
306
+ });
307
+ })), catchError((err) => {
308
+ console.warn("[ngx-geocoding] coordinate del luogo non risolte — " +
309
+ this.perche(err));
310
+ return of(place);
311
+ }));
312
+ }
313
+ /**
314
+ * Prova le sorgenti in ordine, passando alla successiva quando una rifiuta.
315
+ *
316
+ * Finite le sorgenti si rilancia un errore invece di restituire un elenco
317
+ * vuoto : «non ho trovato niente» e «non ho potuto cercare» sono due cose
318
+ * diverse, e chi mostra la barra deve poterle distinguere.
319
+ */
320
+ daSorgenti(address, restanti) {
321
+ const sorgente = restanti[0];
322
+ if (!sorgente) {
323
+ return throwError(() => new Error("Nessuna sorgente di geocoding disponibile"));
324
+ }
325
+ const dopo = restanti.slice(1);
326
+ if (this.spente.has(sorgente))
327
+ return this.daSorgenti(address, dopo);
328
+ const richiesta = sorgente === "google"
329
+ ? this.daGoogle(address)
330
+ : this.daNominatim(address);
331
+ return richiesta.pipe(tap((luoghi) => {
332
+ if (luoghi.length <= 0) {
46
333
  this.subjectNoResults.next(null);
47
334
  }
48
- // return res;
49
- }))
335
+ }), catchError((err) => {
336
+ this.spente.add(sorgente);
337
+ console.warn(`[ngx-geocoding] sorgente "${sorgente}" non utilizzabile, ` +
338
+ `non la si richiede più — ${this.perche(err)}`);
339
+ return this.daSorgenti(address, dopo);
340
+ }));
341
+ }
342
+ /**
343
+ * Nominatim, **identico a come questa libreria l'ha sempre chiamato** : è
344
+ * il comportamento su cui contano le applicazioni che la usavano prima che
345
+ * Google esistesse qui dentro.
346
+ */
347
+ daNominatim(address) {
348
+ const encoded = encodeURIComponent(address);
349
+ const base = this.config?.nominatimUrl ?? NOMINATIM;
350
+ return this.http
351
+ .get(`${base}?q=${encoded}&format=jsonv2`)
50
352
  .pipe(map((res) => res.map((item) => ({
51
353
  display_name: item.display_name,
52
354
  lat: item.lat,
@@ -54,16 +356,222 @@ class NgxGeocodingService {
54
356
  place_id: item.place_id,
55
357
  }))));
56
358
  }
57
- /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.6", ngImport: i0, type: NgxGeocodingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
58
- /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.6", ngImport: i0, type: NgxGeocodingService, providedIn: "root" }); }
359
+ /**
360
+ * Google : le proposte di Places, e il `Geocoder` quando Places dice di no.
361
+ *
362
+ * `ZERO_RESULTS` **non** è un rifiuto : è una ricerca senza esiti, e
363
+ * confonderlo con un errore farebbe scivolare al `Geocoder` — e poi fuori
364
+ * da Google — alla prima ricerca sfortunata.
365
+ */
366
+ daGoogle(address) {
367
+ if (this.suggestionSpenta)
368
+ return this.dallaLegacy(address);
369
+ return this.daSuggestion(address).pipe(catchError((err) => {
370
+ this.suggestionSpenta = true;
371
+ console.warn("[ngx-geocoding] Places (New) non utilizzabile, si prova " +
372
+ `quella legacy — ${this.perche(err)}`);
373
+ return this.dallaLegacy(address);
374
+ }));
375
+ }
376
+ /** Il secondo e il terzo gradino : Places legacy, poi il `Geocoder`. */
377
+ dallaLegacy(address) {
378
+ if (this.placesSpenta)
379
+ return this.daGeocoder(address);
380
+ return this.daPlaces(address).pipe(catchError((err) => {
381
+ this.placesSpenta = true;
382
+ console.warn("[ngx-geocoding] Places legacy non utilizzabile, si prova " +
383
+ `il Geocoder — ${this.perche(err)}`);
384
+ return this.daGeocoder(address);
385
+ }));
386
+ }
387
+ /**
388
+ * Le proposte di **Places (New)**, con `AutocompleteSuggestion`.
389
+ *
390
+ * Due cose che non si vedono dalla firma : `text` di una proposta non è una
391
+ * stringa ma un oggetto formattabile, e va letto con `toString()` — messo
392
+ * in un template darebbe `[object Object]` ; e un elenco **vuoto non è un
393
+ * errore**, è una ricerca senza esiti, quindi non fa scendere di gradino.
394
+ *
395
+ * Se la classe non c'è nell'SDK — versione più vecchia — si solleva subito,
396
+ * che è il modo di scendere senza aspettare una risposta che non arriverà.
397
+ */
398
+ daSuggestion(address) {
399
+ const paese = this.config?.country;
400
+ const cerchio = this.cerchio();
401
+ return from(attendiPlaces()).pipe(switchMap((places) => {
402
+ const nuova = places.AutocompleteSuggestion;
403
+ if (!nuova) {
404
+ return throwError(() => new Error("AutocompleteSuggestion non c'è in questo SDK"));
405
+ }
406
+ return from(nuova.fetchAutocompleteSuggestions({
407
+ input: address,
408
+ ...(paese
409
+ ? { includedRegionCodes: [paese], region: paese }
410
+ : {}),
411
+ ...(cerchio ? { locationBias: cerchio } : {}),
412
+ })).pipe(map((risposta) => (risposta?.suggestions ?? [])
413
+ .map((s) => s.placePrediction)
414
+ .filter((p) => !!p?.placeId)
415
+ .map((p) => ({
416
+ place_id: p.placeId,
417
+ display_name: p.text?.toString() ?? "",
418
+ }))));
419
+ }));
420
+ }
421
+ /** Le proposte incrementali di `AutocompleteService`. */
422
+ daPlaces(address) {
423
+ const paese = this.config?.country;
424
+ const cerchio = this.cerchio();
425
+ return from(attendiPlaces()).pipe(switchMap((places) => new Observable((chi) => {
426
+ new places.AutocompleteService().getPlacePredictions({
427
+ input: address,
428
+ ...(paese
429
+ ? {
430
+ componentRestrictions: {
431
+ country: paese,
432
+ },
433
+ }
434
+ : {}),
435
+ ...(cerchio ? { locationBias: cerchio } : {}),
436
+ }, (proposte, stato) => {
437
+ if (stato === STATO_PLACES.nessuno) {
438
+ chi.next([]);
439
+ chi.complete();
440
+ return;
441
+ }
442
+ if (stato !== STATO_PLACES.ok) {
443
+ chi.error(new Error(stato));
444
+ return;
445
+ }
446
+ chi.next((proposte ?? [])
447
+ .filter((p) => !!p.place_id)
448
+ .map((p) => ({
449
+ place_id: p.place_id,
450
+ display_name: p.description ?? "",
451
+ })));
452
+ chi.complete();
453
+ });
454
+ })));
455
+ }
456
+ /**
457
+ * Il `Geocoder`, l'ultima strada di Google.
458
+ *
459
+ * I suoi risultati **portano già le coordinate**, quindi su questi
460
+ * {@link resolve} non fa nessuna richiesta in più.
461
+ */
462
+ daGeocoder(address) {
463
+ const paese = this.config?.country;
464
+ const riquadro = this.riquadro();
465
+ return from(attendiGeocoder()).pipe(switchMap((geocoder) => new Observable((chi) => {
466
+ geocoder.geocode({
467
+ address,
468
+ ...(paese
469
+ ? {
470
+ componentRestrictions: {
471
+ country: paese,
472
+ },
473
+ region: paese,
474
+ }
475
+ : {}),
476
+ ...(riquadro ? { bounds: riquadro } : {}),
477
+ }, (risultati, stato) => {
478
+ if (stato === STATO_PLACES.nessuno) {
479
+ chi.next([]);
480
+ chi.complete();
481
+ return;
482
+ }
483
+ if (stato !== STATO_PLACES.ok) {
484
+ chi.error(new Error(stato));
485
+ return;
486
+ }
487
+ chi.next((risultati ?? []).map((r) => ({
488
+ place_id: r.place_id,
489
+ display_name: r.formatted_address ?? "",
490
+ lat: this.testo(r.geometry?.location?.lat()),
491
+ lon: this.testo(r.geometry?.location?.lng()),
492
+ })));
493
+ chi.complete();
494
+ });
495
+ })));
496
+ }
497
+ /** Il bias come lo vuole `AutocompleteService`. */
498
+ cerchio() {
499
+ const bias = this.config?.bias;
500
+ return bias
501
+ ? {
502
+ center: { lat: bias.lat, lng: bias.lon },
503
+ radius: bias.radiusM,
504
+ }
505
+ : undefined;
506
+ }
507
+ /**
508
+ * Il bias come lo vuole il `Geocoder`, che prende un **riquadro** e non un
509
+ * cerchio : lo si iscrive attorno al punto. La longitudine si stringe con
510
+ * la latitudine (`cos`), o alle nostre latitudini il riquadro verrebbe
511
+ * largo un terzo in più del voluto.
512
+ */
513
+ riquadro() {
514
+ const bias = this.config?.bias;
515
+ if (!bias)
516
+ return undefined;
517
+ const dLat = bias.radiusM / METRI_PER_GRADO;
518
+ const dLon = dLat / Math.max(Math.cos((bias.lat * Math.PI) / 180), 0.01);
519
+ return {
520
+ north: bias.lat + dLat,
521
+ south: bias.lat - dLat,
522
+ east: bias.lon + dLon,
523
+ west: bias.lon - dLon,
524
+ };
525
+ }
526
+ /** Le sorgenti dichiarate, o quella storica. */
527
+ sorgenti() {
528
+ const dichiarate = this.config?.sources ?? [];
529
+ return dichiarate.length ? dichiarate : SOLO_NOMINATIM;
530
+ }
531
+ /**
532
+ * Perché una strada ha detto di no, in una riga : dall'SDK arriva uno stato
533
+ * (`REQUEST_DENIED` per la chiave senza il prodotto abilitato o per il
534
+ * referrer, `OVER_QUERY_LIMIT` per la quota), da una http il corpo
535
+ * d'errore. Senza questo il motivo resta dentro un oggetto che in console
536
+ * va aperto, e si legge solo «si ripiega».
537
+ */
538
+ perche(err) {
539
+ if (err instanceof Error && err.message)
540
+ return err.message;
541
+ const dettaglio = err?.error?.error;
542
+ if (!dettaglio)
543
+ return "motivo non dichiarato";
544
+ return [dettaglio.status, dettaglio.message]
545
+ .filter((pezzo) => !!pezzo)
546
+ .join(" : ");
547
+ }
548
+ /**
549
+ * Una coordinata come stringa, o `undefined` se non c'è.
550
+ *
551
+ * `String(undefined)` darebbe `"undefined"`, che passerebbe indenne per
552
+ * ogni controllo di presenza e morirebbe in `Number()` — cioè una mappa
553
+ * portata su `NaN, NaN` senza che niente lo dica.
554
+ */
555
+ testo(valore) {
556
+ return valore === null || valore === undefined
557
+ ? undefined
558
+ : String(valore);
559
+ }
560
+ /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NgxGeocodingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
561
+ /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NgxGeocodingService, providedIn: "root" }); }
59
562
  }
60
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.6", ngImport: i0, type: NgxGeocodingService, decorators: [{
563
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NgxGeocodingService, decorators: [{
61
564
  type: Injectable,
62
565
  args: [{
63
566
  providedIn: "root",
64
567
  }]
65
568
  }] });
66
569
 
570
+ /**
571
+ * @author Vito Salvia - CNR IMAA geoSDI Group
572
+ * @email vito.salvia@gmail.com
573
+ */
574
+
67
575
  /*
68
576
  * Public API Surface of ngx-geocoding
69
577
  */
@@ -72,5 +580,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.6", ngImpor
72
580
  * Generated bundle index. Do not edit.
73
581
  */
74
582
 
75
- export { NgxGeocodingModule, NgxGeocodingService };
583
+ export { NGX_GEOCODING_CONFIG, NgxGeocodingModule, NgxGeocodingService, provideNgxGeocoding };
76
584
  //# sourceMappingURL=geosdi-ngx-geocoding.mjs.map