@panoramax/web-viewer 4.0.1-develop-16812d39 → 4.0.2-develop-9d664bb8

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.
@@ -114,7 +114,7 @@ Component properties. All of [Basic properties](Basic.md/#Panoramax.components.c
114
114
  | [psv] | <code>object</code> | | [Any option to pass to Photo component](../ui/Photo.md/#Panoramax.components.ui.Photo) as an object.<br />Example: `psv="{'transitionDuration': 500, 'picturesNavigation': 'pic'}"` |
115
115
  | [url-parameters] | <code>string</code> | <code>true</code> | Should the component add and update URL query parameters to save viewer state ? |
116
116
  | [focus] | <code>string</code> | <code>&quot;pic&quot;</code> | The component showing up as main component (pic, map) |
117
- | [geocoder] | <code>string</code> | <code>&quot;nominatim&quot;</code> | The geocoder engine to use (nominatim, ban) |
117
+ | [geocoder] | <code>string</code> | <code>&quot;nominatim&quot;</code> | The geocoder engine to use (nominatim, ban, or URL to a standard [GeocodeJSON-compliant](https://github.com/geocoders/geocodejson-spec/blob/master/draft/README.md) API) |
118
118
  | [widgets] | <code>string</code> | <code>true</code> | Use default set of widgets ? Set to false to avoid any widget to show up, and use slots to populate as you like. |
119
119
  | [picture] | <code>string</code> | | The picture ID to display |
120
120
  | [sequence] | <code>string</code> | | The sequence ID of the picture displayed |
@@ -16,7 +16,11 @@ Ready-to-use geocoder search bar.
16
16
 
17
17
  **Example**
18
18
  ```html
19
+ <!-- Default geocoder -->
19
20
  <pnx-widget-geosearch _parent=${viewer} />
21
+
22
+ <!-- Custom-URL geocoder -->
23
+ <pnx-widget-geosearch geocoder="https://photon.komoot.io/api" _parent=${viewer} />
20
24
  ```
21
25
  <a name="Panoramax.components.ui.widgets.GeoSearch+properties"></a>
22
26
 
@@ -28,5 +32,5 @@ Component properties.
28
32
 
29
33
  | Name | Type | Default | Description |
30
34
  | --- | --- | --- | --- |
31
- | [geocoder] | <code>string</code> | <code>&quot;nominatim&quot;</code> | The geocoder engine to use (nominatim, ban) |
35
+ | [geocoder] | <code>string</code> | <code>&quot;nominatim&quot;</code> | The geocoder engine to use (nominatim, ban, or URL to a standard [GeocodeJSON-compliant](https://github.com/geocoders/geocodejson-spec/blob/master/draft/README.md) API) |
32
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panoramax/web-viewer",
3
- "version": "4.0.1-develop-16812d39",
3
+ "version": "4.0.2-develop-9d664bb8",
4
4
  "description": "Panoramax web viewer for geolocated pictures",
5
5
  "main": "build/index.js",
6
6
  "author": "Panoramax team",
@@ -95,7 +95,7 @@ export default class Viewer extends PhotoViewer {
95
95
  * @property {object} [psv] [Any option to pass to Photo component](#Panoramax.components.ui.Photo) as an object.<br />Example: `psv="{'transitionDuration': 500, 'picturesNavigation': 'pic'}"`
96
96
  * @property {string} [url-parameters=true] Should the component add and update URL query parameters to save viewer state ?
97
97
  * @property {string} [focus=pic] The component showing up as main component (pic, map)
98
- * @property {string} [geocoder=nominatim] The geocoder engine to use (nominatim, ban)
98
+ * @property {string} [geocoder=nominatim] The geocoder engine to use (nominatim, ban, or URL to a standard [GeocodeJSON-compliant](https://github.com/geocoders/geocodejson-spec/blob/master/draft/README.md) API)
99
99
  * @property {string} [widgets=true] Use default set of widgets ? Set to false to avoid any widget to show up, and use slots to populate as you like.
100
100
  * @property {string} [picture] The picture ID to display
101
101
  * @property {string} [sequence] The sequence ID of the picture displayed
@@ -2,10 +2,14 @@
2
2
  import maplibregl from "!maplibre-gl";
3
3
 
4
4
  import { LitElement, html } from "lit";
5
- import { forwardGeocodingBAN, forwardGeocodingNominatim } from "../../../utils/geocoder";
5
+ import { forwardGeocodingBAN, forwardGeocodingStandard, forwardGeocodingNominatim } from "../../../utils/geocoder";
6
6
  import "./GeoSearch.css";
7
7
 
8
- const GEOCODER_ENGINES = { "ban": forwardGeocodingBAN, "nominatim": forwardGeocodingNominatim };
8
+ const GEOCODER_ENGINES = {
9
+ "ban": forwardGeocodingBAN,
10
+ "standard": forwardGeocodingStandard,
11
+ "nominatim": forwardGeocodingNominatim
12
+ };
9
13
 
10
14
  /**
11
15
  * Ready-to-use geocoder search bar.
@@ -14,7 +18,11 @@ const GEOCODER_ENGINES = { "ban": forwardGeocodingBAN, "nominatim": forwardGeoco
14
18
  * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
15
19
  * @example
16
20
  * ```html
21
+ * <!-- Default geocoder -->
17
22
  * <pnx-widget-geosearch _parent=${viewer} />
23
+ *
24
+ * <!-- Custom-URL geocoder -->
25
+ * <pnx-widget-geosearch geocoder="https://photon.komoot.io/api" _parent=${viewer} />
18
26
  * ```
19
27
  */
20
28
  export default class GeoSearch extends LitElement {
@@ -22,7 +30,7 @@ export default class GeoSearch extends LitElement {
22
30
  * Component properties.
23
31
  * @memberof Panoramax.components.ui.widgets.GeoSearch#
24
32
  * @type {Object}
25
- * @property {string} [geocoder=nominatim] The geocoder engine to use (nominatim, ban)
33
+ * @property {string} [geocoder=nominatim] The geocoder engine to use (nominatim, ban, or URL to a standard [GeocodeJSON-compliant](https://github.com/geocoders/geocodejson-spec/blob/master/draft/README.md) API)
26
34
  */
27
35
  static properties = {
28
36
  geocoder: {type: String},
@@ -54,7 +62,7 @@ export default class GeoSearch extends LitElement {
54
62
  connectedCallback() {
55
63
  super.connectedCallback();
56
64
 
57
- this._geocoderEngine = GEOCODER_ENGINES[this.geocoder];
65
+ this._geocoderEngine = GEOCODER_ENGINES[this.geocoder] || (config => GEOCODER_ENGINES.standard(config, this.geocoder));
58
66
  this._parent?.onceMapReady?.().then(() => {
59
67
  this._geolocate = this._geolocateCtrl.onAdd(this._parent.map);
60
68
  this._geolocate.setAttribute("slot", "pre");
@@ -84,7 +92,7 @@ export default class GeoSearch extends LitElement {
84
92
  else {
85
93
  return this._geocoderEngine({
86
94
  query,
87
- limit: 3,
95
+ limit: 5,
88
96
  //bbox: this._parent.map.getBounds().toArray().map(d => d.join(",")).join(","),
89
97
  proximity: this._parent.map.getCenter().lat+","+this._parent.map.getCenter().lng,
90
98
  }).then(data => {
@@ -22,65 +22,38 @@ function geocoderParamsToURLString(params) {
22
22
 
23
23
  /**
24
24
  * Transforms Nominatim search result into a nice-to-display address.
25
- * @param {object} addr The Nominatim API "address" property
25
+ * @param {object} props The Nominatim API feature properties
26
26
  * @returns {string} The clean-up string for display
27
27
  * @private
28
28
  */
29
- function nominatimAddressToPlaceName(addr) {
29
+ function nominatimAddressToPlaceName(props) {
30
30
  // API format @ https://nominatim.org/release-docs/develop/api/Output/#addressdetails
31
- if(!addr || typeof addr != "object") { return ""; }
32
-
33
- let res = "";
34
-
35
- // House n°-like
36
- if(addr.house_number) { res = addr.house_number; }
37
- else if(addr.house_name) { res = addr.house_name; }
38
- else {
39
- const potentialNames = [
40
- "emergency", "historic", "military", "natural", "landuse", "place", "railway", "man_made",
41
- "aerialway", "boundary", "amenity", "aeroway", "club", "craft", "leisure", "office",
42
- "mountain_pass", "shop", "tourism", "bridge", "tunnel", "waterway", "park"
43
- ];
44
- for(let pn of potentialNames) {
45
- if(addr[pn]) {
46
- res = addr[pn];
47
- break;
48
- }
49
- }
50
- }
51
31
 
52
- // Street-like
53
- let street;
54
- if(addr.road && addr.road.length > 6) { street = addr.road; }
55
- else {
56
- const potentialNames = [
57
- // Hamlet-like
58
- "hamlet", "croft", "isolated_dwelling",
59
- // Zone Indus-like
60
- "farm", "farmyard", "industrial", "commercial", "retail", "city_block", "residential",
61
- // Quarter-like
62
- "neighbourhood", "allotments", "quarter",
63
- // Fallback to road if nothing else found
64
- "road"
65
- ];
66
- for(let pn of potentialNames) {
67
- if(addr[pn]) {
68
- street = addr[pn];
69
- break;
70
- }
71
- }
72
- }
32
+ const nameKind = [
33
+ "house_name", "emergency", "historic", "military", "natural", "landuse", "place", "railway", "man_made",
34
+ "aerialway", "boundary", "amenity", "aeroway", "club", "craft", "leisure", "office",
35
+ "mountain_pass", "shop", "tourism", "bridge", "tunnel", "waterway", "park"
36
+ ].find(pn => props?.address?.[pn]);
73
37
 
74
- if(street && res.length > 0) { res += (addr.house_number ? " " : ", ")+street; }
75
- else if(street) { res = street; }
38
+ const localityKind = [
39
+ "hamlet", "croft", "isolated_dwelling",
40
+ "farm", "farmyard", "industrial", "commercial", "retail", "city_block", "residential",
41
+ "neighbourhood", "allotments", "quarter",
42
+ ].find(pn => props?.address?.[pn]);
76
43
 
77
- // City
78
- if(addr.village || addr.town || addr.city || addr.municipality) {
79
- if(res.length > 0) { res += ", "; }
80
- res += addr.village || addr.town || addr.city || addr.municipality;
81
- }
44
+ const stprops = {
45
+ type: props?.addresstype || props?.type,
46
+ name: (props?.name?.length > 0 ? props.name : null) || (nameKind ? props.address[nameKind] : undefined),
47
+ housenumber: props?.address?.house_number,
48
+ street: props?.address?.road,
49
+ locality: localityKind ? props.address[localityKind] : undefined,
50
+ city: props?.address?.village || props?.address?.town || props?.address?.city || props?.address?.municipality,
51
+ county: props?.address?.county,
52
+ state: props?.address?.state,
53
+ country: props?.address?.country,
54
+ };
82
55
 
83
- return res;
56
+ return geocodeJsonToPlaceName(stprops);
84
57
  }
85
58
 
86
59
  /**
@@ -102,7 +75,7 @@ export function forwardGeocodingNominatim(config) {
102
75
  const finalRes = { features: [] };
103
76
  const listedNames = [];
104
77
  res.features.forEach(f => {
105
- const plname = nominatimAddressToPlaceName(f.properties.address) || f.properties.display_name;
78
+ const plname = nominatimAddressToPlaceName(f.properties) || f.properties.display_name;
106
79
  if(!listedNames.includes(plname)) {
107
80
  finalRes.features.push({
108
81
  place_type: ["place"],
@@ -124,11 +97,105 @@ export function reverseGeocodingNominatim(lat, lon) {
124
97
 
125
98
  /**
126
99
  * Base adresse nationale (FR) geocoder, ready to use for our Map
127
- * @param {object} config Configuration sent by MapLibre GL Geocoder, following the geocoderApi format ( https://github.com/maplibre/maplibre-gl-geocoder/blob/main/API.md#setgeocoderapi )
128
- * @returns {object} GeoJSON Feature collection in Carmen GeoJSON format
100
+ * @param {object} config Configuration sent by MapLibre GL Geocoder, following the geocoderApi format ( https://maplibre.org/maplibre-gl-geocoder/types/MaplibreGeocoderApiConfig.html )
101
+ * @returns {object} GeoJSON Feature collection in Carmen GeoJSON format ( https://maplibre.org/maplibre-gl-geocoder/types/CarmenGeojsonFeature.html )
129
102
  * @private
130
103
  */
131
104
  export function forwardGeocodingBAN(config) {
105
+ return forwardGeocodingStandard(config, AdresseDataGouvBaseURL());
106
+ }
107
+
108
+ /**
109
+ * Transforms GeocodeJSON search result into a nice-to-display address.
110
+ * @param {object} props The GecodeJSON API feature properties
111
+ * @returns {string} The clean-up string for display
112
+ * @private
113
+ */
114
+ function geocodeJsonToPlaceName(props) {
115
+ // API format @ https://github.com/geocoders/geocodejson-spec/blob/master/draft/README.md
116
+ if(!props || typeof props != "object") { return ""; }
117
+
118
+ // P1 = main name, P2=locality-like, P3=country+high-level admin
119
+ let p1 = props.name;
120
+ let p2 = [], p3 = [];
121
+
122
+ switch(props.type) {
123
+ case "hamlet":
124
+ case "croft":
125
+ case "isolated_dwelling":
126
+ case "neighbourhood":
127
+ case "allotments":
128
+ case "quarter":
129
+ case "farm":
130
+ case "farmyard":
131
+ case "industrial":
132
+ case "commercial":
133
+ case "retail":
134
+ case "city_block":
135
+ case "residential":
136
+ case "locality":
137
+ case "district":
138
+ p3.push(props.city);
139
+ p3.push(props.county);
140
+ p3.push(props.state);
141
+ p3.push(props.country);
142
+ break;
143
+ case "city":
144
+ p3.push(props.county);
145
+ p3.push(props.state);
146
+ p3.push(props.country);
147
+ break;
148
+ case "region":
149
+ p3.push(props.county);
150
+ p3.push(props.state);
151
+ p3.push(props.country);
152
+ break;
153
+ case "country":
154
+ break;
155
+ case "house":
156
+ case "housenumber":
157
+ p2.push(props.housenumber);
158
+ p2.push(props.street);
159
+ p2.push(props.locality);
160
+ p2.push(props.district);
161
+ p3.push(props.city);
162
+ p3.push(props.county);
163
+ p3.push(props.state);
164
+ p3.push(props.country);
165
+ break;
166
+ case "street":
167
+ default:
168
+ p2.push(props.street);
169
+ p2.push(props.locality);
170
+ p2.push(props.district);
171
+ p3.push(props.city);
172
+ p3.push(props.county);
173
+ p3.push(props.state);
174
+ p3.push(props.country);
175
+ break;
176
+ }
177
+
178
+ p2 = p2.filter(v => v);
179
+ p2 = p2.filter((v,i) => v != p1 && (i === 0 || p2[i-1] !== v));
180
+ p2 = p2.length > 0 ? (props.housenumber ? p2.slice(0,2).join(" ") : p2.shift()) : null;
181
+ if(p2 === p1) { p2 = null; }
182
+
183
+ p3 = p3.filter(v => v);
184
+ p3 = p3.filter((v,i) => v != p1 && (!p2 || !p2.includes(v)) && (i === 0 || p3[i-1] !== v));
185
+
186
+ let res = [p1, p2, p3.shift()].filter(v => v);
187
+
188
+ return res.join(", ");
189
+ }
190
+
191
+ /**
192
+ * Standard forward geocoder
193
+ * @param {object} config Configuration sent by MapLibre GL Geocoder, following the geocoderApi format ( https://maplibre.org/maplibre-gl-geocoder/types/MaplibreGeocoderApiConfig.html )
194
+ * @param {string} endpoint The URL endpoint (everything before the /?q=...)
195
+ * @returns {object} GeoJSON Feature collection in Carmen GeoJSON format ( https://maplibre.org/maplibre-gl-geocoder/types/CarmenGeojsonFeature.html )
196
+ * @private
197
+ */
198
+ export function forwardGeocodingStandard(config, endpoint) {
132
199
  // Transform parameters into BAN format
133
200
  const params = { q: config.query, limit: config.limit };
134
201
  if(typeof config.proximity === "string") {
@@ -137,18 +204,37 @@ export function forwardGeocodingBAN(config) {
137
204
  params.lon = lon;
138
205
  }
139
206
 
140
- const toPlaceName = p => [p.name, p.district, p.city].filter(v => v).join(", ");
141
- const placeTypeToZoom = { "housenumber": 20, "street": 18, "locality": 15, "municipality": 12 };
207
+ const placeTypeToZoom = {
208
+ "house": 20,
209
+ "housenumber": 20,
210
+ "street": 18,
211
+ "locality": 15,
212
+ "district": 13,
213
+ "municipality": 12,
214
+ "city": 12,
215
+ "county": 8,
216
+ "region": 7,
217
+ "state": 7,
218
+ "country": 5
219
+ };
142
220
 
143
- return fetch(`${AdresseDataGouvBaseURL()}/search/?${geocoderParamsToURLString(params)}`)
221
+ return fetch(`${endpoint}/?${geocoderParamsToURLString(params)}`)
144
222
  .then(res => res.json())
145
223
  .then(res => {
146
- res.features = res.features.map(f => ({
147
- place_type: ["place"],
148
- place_name: toPlaceName(f.properties),
149
- center: new maplibregl.LngLat(...f.geometry.coordinates),
150
- zoom: placeTypeToZoom[f.properties.type],
151
- }));
152
- return res;
224
+ const finalRes = { features: [] };
225
+ const listedNames = [];
226
+ (res.features || []).forEach(f => {
227
+ const plname = geocodeJsonToPlaceName(f.properties);
228
+ if(!listedNames.includes(plname) && f.properties.type != "other") {
229
+ finalRes.features.push({
230
+ place_type: ["place"],
231
+ place_name: plname,
232
+ center: new maplibregl.LngLat(...f.geometry.coordinates),
233
+ zoom: placeTypeToZoom[f.properties.type],
234
+ });
235
+ listedNames.push(plname);
236
+ }
237
+ });
238
+ return finalRes;
153
239
  });
154
- }
240
+ }
@@ -45,7 +45,7 @@ export function InternetFastTestFile() {
45
45
  * @returns {string} The Base Adresse Nationale URL (must support /search calls).
46
46
  */
47
47
  export function AdresseDataGouvBaseURL() {
48
- return "https://data.geopf.fr/geocodage";
48
+ return "https://data.geopf.fr/geocodage/search";
49
49
  }
50
50
 
51
51
  /**
@@ -2,7 +2,6 @@
2
2
 
3
3
  exports[`forwardGeocodingBAN works 1`] = `
4
4
  Object {
5
- "attribution": "BAN",
6
5
  "features": Array [
7
6
  Object {
8
7
  "center": Object {
@@ -16,11 +15,6 @@ Object {
16
15
  "zoom": 20,
17
16
  },
18
17
  ],
19
- "licence": "ODbL 1.0",
20
- "limit": 1,
21
- "query": "8 bd du port",
22
- "type": "FeatureCollection",
23
- "version": "draft",
24
18
  }
25
19
  `;
26
20