@geosdi/ngx-geocoding 1.2.0 → 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 +200 -34
- package/package.json +1 -1
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
|
-
|
|
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": ">=
|
|
16
|
-
"@angular/core": ">=
|
|
17
|
-
"typescript": ">=
|
|
18
|
-
"rxjs": ">=7.
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
| ------------- | ------------- |
|
|
50
|
-
| geocode(address: string): Observable<GeocodingLocation[]> | list of GeocodingLocation |
|
|
211
|
+
## Troubleshooting
|
|
51
212
|
|
|
52
|
-
|
|
213
|
+
Every fallback is logged with `console.warn`, prefixed `[ngx-geocoding]`.
|
|
53
214
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
|
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.
|