@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.
package/README.md CHANGED
@@ -1,10 +1,17 @@
1
1
  # NgxGeocoding
2
2
 
3
- This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 13.0.0.
4
-
5
3
  # General information
6
4
 
7
- Component to get a list of place with a geocoding service using nominatim of openstreet map
5
+ Address search for Angular, from one or more sources tried **in order**.
6
+
7
+ Out of the box it queries **Nominatim** (OpenStreetMap), which needs no API key
8
+ and is what this library has always done. **Google** can be added on top: the
9
+ library never sees your key, it uses the **Maps JS SDK** the host application
10
+ already loads in the page.
11
+
12
+ A source that is not usable is **skipped, and that is not an error**: the same
13
+ configuration therefore works both on a deploy that has a Google key and on one
14
+ that does not.
8
15
 
9
16
  ## Installation
10
17
 
@@ -12,48 +19,207 @@ Component to get a list of place with a geocoding service using nominatim of ope
12
19
 
13
20
  It depends on :
14
21
 
15
- "@angular/common": ">=13.0.0",
16
- "@angular/core": ">=13.0.0",
17
- "typescript": ">=4.4.4",
18
- "rxjs": ">=7.0.0"
22
+ "@angular/common": ">=20.0.0",
23
+ "@angular/core": ">=20.0.0",
24
+ "typescript": ">=5.0.0",
25
+ "rxjs": ">=7.8.0"
26
+
27
+ Nominatim is called through `HttpClient`, so the application must provide it —
28
+ `provideHttpClient()` in the bootstrap providers, or `HttpClientModule` on a
29
+ module-based application.
30
+
31
+ ## Configuration
32
+
33
+ Everything is declared in the application `providers`, with
34
+ `provideNgxGeocoding`:
35
+
36
+ ```typescript
37
+ import { provideNgxGeocoding } from "@geosdi/ngx-geocoding";
38
+
39
+ bootstrapApplication(AppComponent, {
40
+ providers: [
41
+ provideHttpClient(),
42
+ provideNgxGeocoding({
43
+ sources: ["google", "nominatim"],
44
+ country: "it",
45
+ bias: { lat: 40.83, lon: 14.11, radiusM: 25_000 },
46
+ }),
47
+ ],
48
+ });
49
+ ```
50
+
51
+ | option | type | default | used by |
52
+ | -------------- | ------------------------------ | ----------------------- | ------------------------------ |
53
+ | `sources` | `("google" \| "nominatim")[]` | `["nominatim"]` | the order sources are tried in |
54
+ | `country` | `string` — two letters, `"it"` | none | `google` only |
55
+ | `bias` | `{ lat, lon, radiusM }` | none | `google` only |
56
+ | `nominatimUrl` | `string` | public OSM `search.php` | `nominatim` only |
57
+
58
+ `bias` is a **bias, not a filter**: an address outside the area is still
59
+ reachable, but "via Roma" proposes the nearby one first. Without it Google
60
+ orders as it likes, and on a common street name the first suggestion is almost
61
+ always somewhere else.
62
+
63
+ `provideNgxGeocoding` is optional: **not calling it leaves everything as it
64
+ was** — one source, Nominatim.
65
+
66
+ ### Nominatim
67
+
68
+ Nothing to configure, no key, no Cloud project.
69
+
70
+ ```typescript
71
+ provideNgxGeocoding({
72
+ sources: ["nominatim"],
73
+ nominatimUrl: "https://nominatim.example.org/search.php",
74
+ });
75
+ ```
76
+
77
+ Two things to know:
78
+
79
+ - `country` and `bias` are **not** applied to Nominatim. The request is left
80
+ exactly as this library has always sent it (`?q=…&format=jsonv2`), because
81
+ that is the behaviour applications relied on before Google existed here;
82
+ - the public OpenStreetMap endpoint is a courtesy service with a usage policy
83
+ (roughly: one request per second, no bulk traffic). For anything more than
84
+ occasional searches point `nominatimUrl` at your own instance.
85
+
86
+ Nominatim results **already carry `lat` and `lon`**.
87
+
88
+ ### Google
89
+
90
+ Two things live outside this library.
91
+
92
+ **1. The SDK tag**, in `index.html`, with the application's own key:
93
+
94
+ ```html
95
+ <script
96
+ defer
97
+ src="https://maps.googleapis.com/maps/api/js?key=YOUR_KEY&libraries=places&language=it"
98
+ ></script>
99
+ ```
100
+
101
+ - **the key belongs to the application** and must not be passed to this
102
+ library: there is no option for it. Loading the SDK here too would also mean
103
+ two copies of it in the same page;
104
+ - **`libraries=places`** — without it only the third step of the cascade
105
+ (`Geocoder`) can answer;
106
+ - **`language`** decides the language of the suggestions, and this is the only
107
+ place to set it: the SDK request has no parameter for it;
108
+ - **`defer` is fine**: the library waits for the SDK up to 10 s, polling every
109
+ 100 ms, so a search typed right after startup does not get lost.
110
+
111
+ **2. The Google Cloud project**, where the key must have:
112
+
113
+ - **Maps JavaScript API** enabled — this is the SDK itself;
114
+ - **Places API (New)** and/or **Places API** (legacy) enabled, for the
115
+ suggestions. Either one is enough, see the cascade below;
116
+ - **Geocoding API** enabled, for the last step;
117
+ - if the key has **HTTP referrer restrictions**, every origin the application
118
+ is deployed on must be listed. A missing one answers `REQUEST_DENIED`, which
119
+ looks exactly like a product that was never enabled.
120
+
121
+ #### Which Google API actually answers
122
+
123
+ `google` is **a cascade**, and it is worth knowing before "simplifying" that
124
+ code: from a browser only **Places (New)** exposes CORS, so a plain `fetch` to
125
+ the other Google APIs cannot work at all. Going through the SDK, search works
126
+ with whatever the Cloud project has already enabled:
127
+
128
+ 1. **`AutocompleteSuggestion`** (Places **New**) — the way Google recommends
129
+ since 1 March 2025, and the only one offered to new projects. The product
130
+ behind it must be enabled separately: where it is not, it answers `403` and
131
+ the cascade steps down;
132
+ 2. **`AutocompleteService`** (Places **legacy**) — same incremental
133
+ suggestions. Google calls it deprecated and "not available to new
134
+ customers", but it is what answers on projects where the New one was never
135
+ enabled;
136
+ 3. **`Geocoder`** — no incremental suggestions, usually a single result, but it
137
+ **already carries the coordinates** and lives in the base SDK, so it does
138
+ not need `libraries=places`.
139
+
140
+ Steps go down on their own and **are not retried for the rest of the session**;
141
+ reloading the page resets that. Each fallback is written to the console with the
142
+ reason (`REQUEST_DENIED`, `OVER_QUERY_LIMIT`, …).
19
143
 
20
144
  ## Usage
21
145
 
22
- ```typescript
23
-
24
- import { Injectable } from '@angular/core';
25
- import {NgxGeocodingService} from "./ngx-geocoding.service"; import {GeocodingLocation} from "./geocoding-location.model";
146
+ ```typescript
147
+ import { Component, inject } from "@angular/core";
148
+ import { GeocodingLocation, NgxGeocodingService } from "@geosdi/ngx-geocoding";
149
+
150
+ @Component({ selector: "app-search", standalone: true, template: "…" })
151
+ export class SearchComponent {
152
+ private geocodingService: NgxGeocodingService = inject(NgxGeocodingService);
153
+
154
+ places: GeocodingLocation[] = [];
155
+ chosen?: GeocodingLocation;
156
+
157
+ search(address: string): void {
158
+ this.geocodingService.geocode(address).subscribe({
159
+ // An empty list means "nothing found", and also fires subjectNoResults.
160
+ next: (res: GeocodingLocation[]) => (this.places = res),
161
+ // The error means "I could not search at all": no source was usable.
162
+ error: () => (this.places = []),
163
+ });
164
+ }
165
+
166
+ // Google suggestions arrive WITHOUT coordinates: resolve() asks for them,
167
+ // for the chosen place only. Places that already have them (Nominatim, the
168
+ // Geocoder) are returned as they are, so calling it is always correct.
169
+ choose(place: GeocodingLocation): void {
170
+ this.geocodingService
171
+ .resolve(place)
172
+ .subscribe((full: GeocodingLocation) => {
173
+ this.chosen = full;
174
+ if (full.lat && full.lon) {
175
+ // Number(undefined) would be NaN: coordinates may be missing if the
176
+ // detail request failed, and the place is returned as it was.
177
+ this.moveMap(Number(full.lat), Number(full.lon));
178
+ }
179
+ });
180
+ }
181
+ }
182
+ ```
26
183
 
27
- @Injectable({
28
- providedIn: 'root'
29
- })
30
- export class Service {
184
+ `geocode` **errors** when no source could be used, and **emits an empty list**
185
+ when the search simply found nothing: "I found nothing" and "I could not
186
+ search" are two different things, and a search bar has to tell them apart.
31
187
 
188
+ ## Methods
32
189
 
33
- constructor(private geocodingService: NgxGeocodingService,) {
34
- }
190
+ | Method | action |
191
+ | ------------------------------------------------------------------ | -------------------------------------------------------------------- |
192
+ | `geocode(address: string): Observable<GeocodingLocation[]>` | suggestions for the typed text, from the first source that answers |
193
+ | `resolve(place: GeocodingLocation): Observable<GeocodingLocation>` | coordinates of the **chosen** place; returns it unchanged on failure |
35
194
 
36
- public addPlugin() {
37
- this.geocodingService.geocode(addressToSearch).subscribe((res: GeocodingLocation[]) => {
38
- alert(JSON.stringify(res));
39
- });
40
- }
195
+ ## Model
41
196
 
42
- }
197
+ | `GeocodingLocation` | content |
198
+ | ------------------- | ---------------------------------------------------------- |
199
+ | `display_name` | the address to show |
200
+ | `lat`, `lon` | coordinates, as strings — **absent on Google suggestions** |
201
+ | `place_id` | id of the place, what `resolve` needs |
43
202
 
44
- ```
203
+ ## Events
45
204
 
46
- ## Methods
205
+ | Method | action |
206
+ | ------------------ | ---------------------------------------- |
207
+ | `subjectNoResults` | subscribe to notify if research is empty |
208
+ | `subjectLocation` | subject to notify location |
209
+ | `subjectRemove` | subject to notify remove location |
47
210
 
48
- | Method | action
49
- | ------------- | ------------- |
50
- | geocode(address: string): Observable<GeocodingLocation[]> | list of GeocodingLocation |
211
+ ## Troubleshooting
51
212
 
52
- ## Events
213
+ Every fallback is logged with `console.warn`, prefixed `[ngx-geocoding]`.
53
214
 
54
- | Method | action
55
- | ------------- | ------------- |
56
- | subjectNoResults | subscribe to notify if research is empty |
57
- | subjectLocation | subject to notify location |
58
- | subjectRemove | subject to notify remove location |
215
+ | what the console says | what it means |
216
+ | ------------------------------------------- | ---------------------------------------------------------------------------------- |
217
+ | `SDK Maps senza la libreria 'places'` | `libraries=places` missing from the script tag, or no SDK in the page |
218
+ | `SDK Maps senza il Geocoder` | no SDK in the page at all |
219
+ | `REQUEST_DENIED` | product not enabled on the Cloud project, or referrer outside the key restrictions |
220
+ | `OVER_QUERY_LIMIT` | quota or billing on the Google project |
221
+ | `sorgente "google" non utilizzabile` | every Google step refused; the cascade fell through to the next source |
222
+ | `Nessuna sorgente di geocoding disponibile` | all declared sources refused — this is the error `geocode` throws |
59
223
 
224
+ A source that refused is not asked again until the page is reloaded, so a fix on
225
+ the Cloud console needs a reload to be seen.