@panoramax/web-viewer 4.0.1 → 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.
Files changed (55) hide show
  1. package/CHANGELOG.md +31 -3
  2. package/build/index.css +1 -1
  3. package/build/index.css.map +1 -1
  4. package/build/index.js +150 -51
  5. package/build/index.js.map +1 -1
  6. package/config/jest/mocks.js +2 -1
  7. package/docs/03_URL_settings.md +1 -1
  8. package/docs/09_Develop.md +5 -1
  9. package/docs/reference/components/core/Viewer.md +1 -1
  10. package/docs/reference/components/ui/CopyButton.md +1 -0
  11. package/docs/reference/components/ui/Map.md +13 -0
  12. package/docs/reference/components/ui/MapMore.md +13 -0
  13. package/docs/reference/components/ui/Photo.md +1 -1
  14. package/docs/reference/components/ui/widgets/CopyCoordinates.md +32 -0
  15. package/docs/reference/components/ui/widgets/GeoSearch.md +5 -1
  16. package/docs/reference/utils/API.md +1 -1
  17. package/docs/reference.md +1 -0
  18. package/docs/tutorials/migrate_v4.md +1 -1
  19. package/docs/tutorials/synced_coverage.md +1 -1
  20. package/mkdocs.yml +1 -0
  21. package/package.json +1 -1
  22. package/src/components/core/CoverageMap.js +2 -2
  23. package/src/components/core/PhotoViewer.js +5 -1
  24. package/src/components/core/Viewer.js +10 -5
  25. package/src/components/menus/PictureLegend.js +7 -4
  26. package/src/components/menus/PictureMetadata.js +23 -2
  27. package/src/components/styles.js +61 -0
  28. package/src/components/ui/ButtonGroup.css +2 -0
  29. package/src/components/ui/CopyButton.js +3 -1
  30. package/src/components/ui/Map.js +35 -4
  31. package/src/components/ui/Photo.js +4 -2
  32. package/src/components/ui/TogglableGroup.js +1 -1
  33. package/src/components/ui/widgets/CopyCoordinates.js +75 -0
  34. package/src/components/ui/widgets/GeoSearch.js +13 -5
  35. package/src/components/ui/widgets/Legend.js +1 -1
  36. package/src/components/ui/widgets/OSMEditors.js +2 -2
  37. package/src/components/ui/widgets/PictureLegendActions.js +1 -1
  38. package/src/components/ui/widgets/Player.js +1 -0
  39. package/src/components/ui/widgets/index.js +1 -0
  40. package/src/translations/en.json +6 -2
  41. package/src/translations/fr.json +6 -2
  42. package/src/translations/it.json +3 -1
  43. package/src/translations/ti.json +9 -0
  44. package/src/utils/API.js +1 -1
  45. package/src/utils/InitParameters.js +2 -2
  46. package/src/utils/geocoder.js +155 -67
  47. package/src/utils/index.js +2 -1
  48. package/src/utils/picture.js +6 -1
  49. package/src/utils/services.js +57 -0
  50. package/src/utils/utils.js +18 -5
  51. package/tests/components/ui/Map.test.js +7 -3
  52. package/tests/utils/InitParameters.test.js +15 -15
  53. package/tests/utils/__snapshots__/geocoder.test.js.snap +0 -6
  54. package/tests/utils/geocoder.test.js +1 -1
  55. package/tests/utils/utils.test.js +136 -109
@@ -0,0 +1,75 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { degToDms } from "../../../utils/utils";
3
+ import { fa } from "../../../utils/widgets";
4
+ import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
5
+ import { btngroup } from "../../styles";
6
+
7
+ /**
8
+ * Copy Coordinates button allows easy copy of several format of map coordinates.
9
+ * @class Panoramax.components.ui.widgets.CopyCoordinates
10
+ * @element pnx-copy-coordinates
11
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
12
+ * @example
13
+ * ```html
14
+ * <pnx-copy-coordinates gps=${[-1.7, 48.6]} _parent=${viewer} />
15
+ * ```
16
+ */
17
+ export default class CopyCoordinates extends LitElement {
18
+ /** @private */
19
+ static styles = [btngroup, css`
20
+ pnx-togglable-group::part(menu) {
21
+ border-radius: 5px;
22
+ min-width: unset;
23
+ }
24
+ `];
25
+
26
+ /**
27
+ * Component properties.
28
+ * @memberof Panoramax.components.ui.widgets.CopyCoordinates#
29
+ * @type {Object}
30
+ * @property {number[]} gps GPS/map coordinates, as [lon, lat]
31
+ */
32
+ static properties = {
33
+ gps: {type: Array},
34
+ };
35
+
36
+ /** @private */
37
+ render() {
38
+ const dmslonval = degToDms(this.gps[0]);
39
+ const dmslon = dmslonval.d < 0 ? `${Math.abs(dmslonval.d)}°${dmslonval.m}'${dmslonval.s}"W` : `${dmslonval.d}°${dmslonval.m}'${dmslonval.s}"E`;
40
+ const dmslatval = degToDms(this.gps[1]);
41
+ const dmslat = dmslatval.d < 0 ? `${Math.abs(dmslatval.d)}°${dmslatval.m}'${dmslatval.s}"S` : `${dmslatval.d}°${dmslatval.m}'${dmslatval.s}"N`;
42
+
43
+ const values = {
44
+ ddeclatlon: `${this.gps[1].toFixed(7)}, ${this.gps[0].toFixed(7)}`,
45
+ ddeclonlat: `${this.gps[0].toFixed(7)}, ${this.gps[1].toFixed(7)}`,
46
+ ddmslonlat: `${dmslon}, ${dmslat}`,
47
+ ddmslatlon: `${dmslat}, ${dmslon}`,
48
+ };
49
+
50
+ return html`<pnx-button-group style="display: inline-block; vertical-align: baseline" dir="row">
51
+ <pnx-copy-button
52
+ size="sm"
53
+ text=${values.ddeclonlat}
54
+ title=${this._parent?._t.pnx.metadata_location_copy.replace("{v}", values.ddeclonlat)}
55
+ ></pnx-copy-button>
56
+
57
+ <pnx-togglable-group padded="false" ._parent=${this._parent}>
58
+ <pnx-button
59
+ size="sm"
60
+ slot="button"
61
+ title=${this._parent?._t.pnx.metadata_location_copy_more}
62
+ >${fa(faChevronDown)}</pnx-button>
63
+ <pnx-list-group>
64
+ ${Object.values(values).map(text => html`
65
+ <pnx-copy-button unstyled text=${text} ._t=${this._parent._t}>
66
+ ${this._parent?._t.pnx.metadata_location_copy.replace("{v}", text)}
67
+ </pnx-copy-button>
68
+ `)}
69
+ </pnx-list-group>
70
+ </pnx-togglable-group>
71
+ </pnx-button-group>`;
72
+ }
73
+ }
74
+
75
+ customElements.define("pnx-copy-coordinates", CopyCoordinates);
@@ -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 => {
@@ -81,7 +81,7 @@ export default class Legend extends LitElement {
81
81
  >
82
82
  <img class="logo" src=${PanoramaxImg} alt="" />
83
83
  <div>
84
- Panoramax est le géocommun des photos de rues.
84
+ ${this._parent?._t.pnx.whats_panoramax}
85
85
  <pnx-link-button
86
86
  title=${this._parent?._t.map.more_panoramax}
87
87
  kind="superinline"
@@ -1,12 +1,12 @@
1
1
  import { LitElement, html, nothing, css } from "lit";
2
2
  import { fa } from "../../../utils/widgets";
3
3
  import { josmBboxParameters } from "../../../utils/utils";
4
+ import { IdEditorURL } from "../../../utils/services";
4
5
  import { faLocationDot } from "@fortawesome/free-solid-svg-icons/faLocationDot";
5
6
  import { faSatelliteDish } from "@fortawesome/free-solid-svg-icons/faSatelliteDish";
6
7
  import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalLinkAlt";
7
8
 
8
9
  const JOSM_REMOTE_URL = "http://127.0.0.1:8111";
9
- const ID_URL = "https://www.openstreetmap.org/edit?editor=id";
10
10
 
11
11
  /**
12
12
  * OSM Editors component offers direct links to OpenStreetMap's iD and JOSM editors.
@@ -126,7 +126,7 @@ export default class OSMEditors extends LitElement {
126
126
  "photo_overlay": "panoramax",
127
127
  "photo": `panoramax/${this._pic.id}`,
128
128
  };
129
- const idUrl = idOpts && `${ID_URL}#${new URLSearchParams(idOpts).toString()}`;
129
+ const idUrl = idOpts && `${IdEditorURL()}#${new URLSearchParams(idOpts).toString()}`;
130
130
 
131
131
  return html`
132
132
  <pnx-link-button
@@ -58,7 +58,7 @@ export default class PictureLegendActions extends LitElement {
58
58
 
59
59
  /** @private */
60
60
  _closeGroup() {
61
- this.renderRoot.querySelector("pnx-togglable-group").close();
61
+ this.renderRoot.querySelector("#pic-legend-headline-menu").close();
62
62
  }
63
63
 
64
64
  /** @private */
@@ -105,6 +105,7 @@ export default class Player extends LitElement {
105
105
  dir="row"
106
106
  size=${this.size}
107
107
  class="pnx-print-hidden"
108
+ style=${!this._activePrev && !this._activePlay && !this._activeNext ? "display: none": ""}
108
109
  >
109
110
  <pnx-button
110
111
  kind="superflat"
@@ -3,6 +3,7 @@
3
3
  * @module Panoramax:components:ui:widgets
4
4
  */
5
5
 
6
+ export {default as CopyCoordinates} from "./CopyCoordinates";
6
7
  export {default as GeoSearch} from "./GeoSearch";
7
8
  export {default as Legend} from "./Legend";
8
9
  export {default as MapFiltersButton} from "./MapFiltersButton";
@@ -39,6 +39,7 @@
39
39
  "share_embed": "Embed on your website",
40
40
  "share_embed_docs": "Read more about embed configuration",
41
41
  "share_print": "Print",
42
+ "whats_panoramax": "Panoramax is the geo-commons for territories' pictures.",
42
43
  "copy": "Copy",
43
44
  "copied": "Copied",
44
45
  "error": "We have a problem…",
@@ -156,10 +157,11 @@
156
157
  "metadata_camera_resolution": "Resolution",
157
158
  "metadata_camera_focal_length": "Focal length",
158
159
  "metadata_location": "Position",
159
- "metadata_location_longitude": "Longitude",
160
- "metadata_location_latitude": "Latitude",
160
+ "metadata_location_coordinates": "Coordinates (longitude, latitude)",
161
161
  "metadata_location_orientation": "Direction",
162
162
  "metadata_location_precision": "Positioning precision",
163
+ "metadata_location_copy": "Copy {v}",
164
+ "metadata_location_copy_more": "More coordinates copy options",
163
165
  "metadata_quality": "Quality",
164
166
  "metadata_quality_help": "Know more about Quality Score",
165
167
  "metadata_quality_score": "Global score",
@@ -169,6 +171,8 @@
169
171
  "metadata_exif": "EXIF",
170
172
  "metadata_exif_name": "Tag",
171
173
  "metadata_exif_value": "Value",
174
+ "metadata_exif_doc": "Docs for EXIF tags",
175
+ "metadata_exif_doc_title": "Go to Exiv2 documentation to have full details on EXIF and XMP tags definitions",
172
176
  "report": "Report",
173
177
  "report_auth": "This report will be sent using your account \"{a}\"",
174
178
  "report_nature_label": "Nature of the issue",
@@ -39,6 +39,7 @@
39
39
  "share_embed": "Intégration sur votre site",
40
40
  "share_embed_docs": "Découvrir plus de possibilités pour l'intégration sur votre site",
41
41
  "share_print": "Imprimer",
42
+ "whats_panoramax": "Panoramax est le géocommun des photos des territoires.",
42
43
  "copy": "Copier",
43
44
  "copied": "Copié",
44
45
  "error": "On a un problème…",
@@ -156,10 +157,11 @@
156
157
  "metadata_camera_resolution": "Résolution",
157
158
  "metadata_camera_focal_length": "Longueur focale",
158
159
  "metadata_location": "Position",
159
- "metadata_location_longitude": "Longitude",
160
- "metadata_location_latitude": "Latitude",
160
+ "metadata_location_coordinates": "Coordonnées (longitude, latitude)",
161
161
  "metadata_location_orientation": "Direction",
162
162
  "metadata_location_precision": "Précision du positionnement",
163
+ "metadata_location_copy": "Copier {v}",
164
+ "metadata_location_copy_more": "Copier d'autres formats de coordonnées",
163
165
  "metadata_quality": "Qualité",
164
166
  "metadata_quality_help": "En savoir plus sur le score de qualité",
165
167
  "metadata_quality_score": "Note globale",
@@ -169,6 +171,8 @@
169
171
  "metadata_exif": "EXIF",
170
172
  "metadata_exif_name": "Balise",
171
173
  "metadata_exif_value": "Valeur",
174
+ "metadata_exif_doc": "Doc des attributs EXIF",
175
+ "metadata_exif_doc_title": "Acééder à la doc Exiv2 pour en savoir plus sur les attributs EXIF et XMP",
172
176
  "report": "Signaler",
173
177
  "report_auth": "Ce signalement sera envoyé en utilisant votre compte \"{a}\"",
174
178
  "report_nature_label": "Nature du problème",
@@ -183,7 +183,9 @@
183
183
  "contribute_id": "Contribuisci a OpenStreetMap con l’editor iD",
184
184
  "geo_uri": "App esterna",
185
185
  "filter_date_6months": "6 mesi",
186
- "picture_all": "Tutte"
186
+ "picture_all": "Tutte",
187
+ "metadata_exif_doc": "Doc per gli attributi EXIF",
188
+ "metadata_exif_doc_title": "Vai alla documentazione di Exiv2 per i dettagli completi sulle definizioni degli attributi EXIF e XMP"
187
189
  },
188
190
  "psv": {
189
191
  "loadError": "Impossibile caricare l’immagine panoramica",
@@ -0,0 +1,9 @@
1
+ {
2
+ "pnx": {
3
+ "share": "ናይ ሓባር",
4
+ "error_click": "ቀፃሊ",
5
+ "zoom": "ዙም",
6
+ "options": "ኣማራጺታት",
7
+ "copy": "ቅዳሕ"
8
+ }
9
+ }
package/src/utils/API.js CHANGED
@@ -8,7 +8,7 @@ import { isNullId } from "./utils";
8
8
  * @typicalname api
9
9
  * @fires Panoramax.utils.API#ready
10
10
  * @fires Panoramax.utils.API#broken
11
- * @param {string} endpoint The endpoint. It corresponds to the <a href="https://github.com/radiantearth/stac-api-spec/blob/main/overview.md#example-landing-page">STAC landing page</a>, with all links describing the API capabilites.
11
+ * @param {string} endpoint The endpoint. It corresponds to the <a href="https://github.com/radiantearth/stac-api-spec/blob/main/overview.md#example-landing-page">STAC landing page</a>, with all links describing the API capabilities.
12
12
  * @param {object} [options] Options
13
13
  * @param {string|object} [options.style] General map style
14
14
  * @param {string} [options.tiles] API route serving pictures & sequences vector tiles
@@ -323,7 +323,7 @@ export function alterPSVState(psv, params) {
323
323
  * @param {Map} map The MapLibre component to change.
324
324
  * @param {object} params The parameters to apply.
325
325
  */
326
- export function alterMapState(map, params) {
326
+ export async function alterMapState(map, params) {
327
327
  // Map position
328
328
  const mapOpts = getMapPositionFromString(params.map, map);
329
329
  if(mapOpts) {
@@ -333,7 +333,7 @@ export function alterMapState(map, params) {
333
333
  // Visible users
334
334
  let vu = Array.isArray(params.users) ? params.users : (params.users || "").split(",");
335
335
  if(vu.length === 0 || (vu.length === 1 && vu[0].trim() === "")) { vu = ["geovisio"]; }
336
- map.setVisibleUsers(vu);
336
+ await map.setVisibleUsers(vu);
337
337
 
338
338
  // Change map filters
339
339
  map.setFilters?.(paramsToMapFilters(params));
@@ -1,6 +1,8 @@
1
1
  // DO NOT REMOVE THE "!": bundled builds breaks otherwise !!!
2
2
  import maplibregl from "!maplibre-gl";
3
3
 
4
+ import { NominatimBaseUrl, AdresseDataGouvBaseURL } from "./services";
5
+
4
6
  /**
5
7
  * Transforms a set of parameters into an URL-ready string
6
8
  * It also removes null/undefined values
@@ -20,65 +22,38 @@ function geocoderParamsToURLString(params) {
20
22
 
21
23
  /**
22
24
  * Transforms Nominatim search result into a nice-to-display address.
23
- * @param {object} addr The Nominatim API "address" property
25
+ * @param {object} props The Nominatim API feature properties
24
26
  * @returns {string} The clean-up string for display
25
27
  * @private
26
28
  */
27
- function nominatimAddressToPlaceName(addr) {
29
+ function nominatimAddressToPlaceName(props) {
28
30
  // API format @ https://nominatim.org/release-docs/develop/api/Output/#addressdetails
29
- if(!addr || typeof addr != "object") { return ""; }
30
-
31
- let res = "";
32
-
33
- // House n°-like
34
- if(addr.house_number) { res = addr.house_number; }
35
- else if(addr.house_name) { res = addr.house_name; }
36
- else {
37
- const potentialNames = [
38
- "emergency", "historic", "military", "natural", "landuse", "place", "railway", "man_made",
39
- "aerialway", "boundary", "amenity", "aeroway", "club", "craft", "leisure", "office",
40
- "mountain_pass", "shop", "tourism", "bridge", "tunnel", "waterway", "park"
41
- ];
42
- for(let pn of potentialNames) {
43
- if(addr[pn]) {
44
- res = addr[pn];
45
- break;
46
- }
47
- }
48
- }
49
31
 
50
- // Street-like
51
- let street;
52
- if(addr.road && addr.road.length > 6) { street = addr.road; }
53
- else {
54
- const potentialNames = [
55
- // Hamlet-like
56
- "hamlet", "croft", "isolated_dwelling",
57
- // Zone Indus-like
58
- "farm", "farmyard", "industrial", "commercial", "retail", "city_block", "residential",
59
- // Quarter-like
60
- "neighbourhood", "allotments", "quarter",
61
- // Fallback to road if nothing else found
62
- "road"
63
- ];
64
- for(let pn of potentialNames) {
65
- if(addr[pn]) {
66
- street = addr[pn];
67
- break;
68
- }
69
- }
70
- }
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]);
71
37
 
72
- if(street && res.length > 0) { res += (addr.house_number ? " " : ", ")+street; }
73
- 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]);
74
43
 
75
- // City
76
- if(addr.village || addr.town || addr.city || addr.municipality) {
77
- if(res.length > 0) { res += ", "; }
78
- res += addr.village || addr.town || addr.city || addr.municipality;
79
- }
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
+ };
80
55
 
81
- return res;
56
+ return geocodeJsonToPlaceName(stprops);
82
57
  }
83
58
 
84
59
  /**
@@ -94,13 +69,13 @@ export function forwardGeocodingNominatim(config) {
94
69
  viewbox: config.bbox,
95
70
  };
96
71
 
97
- return fetch(`https://nominatim.openstreetmap.org/search?${geocoderParamsToURLString(params)}&format=geojson&polygon_geojson=1&addressdetails=1`)
72
+ return fetch(`${NominatimBaseUrl()}/search?${geocoderParamsToURLString(params)}&format=geojson&polygon_geojson=1&addressdetails=1`)
98
73
  .then(res => res.json())
99
74
  .then(res => {
100
75
  const finalRes = { features: [] };
101
76
  const listedNames = [];
102
77
  res.features.forEach(f => {
103
- const plname = nominatimAddressToPlaceName(f.properties.address) || f.properties.display_name;
78
+ const plname = nominatimAddressToPlaceName(f.properties) || f.properties.display_name;
104
79
  if(!listedNames.includes(plname)) {
105
80
  finalRes.features.push({
106
81
  place_type: ["place"],
@@ -115,18 +90,112 @@ export function forwardGeocodingNominatim(config) {
115
90
  }
116
91
 
117
92
  export function reverseGeocodingNominatim(lat, lon) {
118
- return fetch(`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&zoom=18&format=jsonv2`)
93
+ return fetch(`${NominatimBaseUrl()}/reverse?lat=${lat}&lon=${lon}&zoom=18&format=jsonv2`)
119
94
  .then(res => res.json())
120
95
  .then(res => nominatimAddressToPlaceName(res?.address));
121
96
  }
122
97
 
123
98
  /**
124
99
  * Base adresse nationale (FR) geocoder, ready to use for our Map
125
- * @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 )
126
- * @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 )
127
102
  * @private
128
103
  */
129
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) {
130
199
  // Transform parameters into BAN format
131
200
  const params = { q: config.query, limit: config.limit };
132
201
  if(typeof config.proximity === "string") {
@@ -135,18 +204,37 @@ export function forwardGeocodingBAN(config) {
135
204
  params.lon = lon;
136
205
  }
137
206
 
138
- const toPlaceName = p => [p.name, p.district, p.city].filter(v => v).join(", ");
139
- 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
+ };
140
220
 
141
- return fetch(`https://api-adresse.data.gouv.fr/search/?${geocoderParamsToURLString(params)}`)
221
+ return fetch(`${endpoint}/?${geocoderParamsToURLString(params)}`)
142
222
  .then(res => res.json())
143
223
  .then(res => {
144
- res.features = res.features.map(f => ({
145
- place_type: ["place"],
146
- place_name: toPlaceName(f.properties),
147
- center: new maplibregl.LngLat(...f.geometry.coordinates),
148
- zoom: placeTypeToZoom[f.properties.type],
149
- }));
150
- 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;
151
239
  });
152
- }
240
+ }
@@ -2,10 +2,11 @@ import * as geocoder from "./geocoder";
2
2
  import * as i18n from "./i18n";
3
3
  import * as map from "./map";
4
4
  import * as picture from "./picture";
5
+ import * as services from "./services";
5
6
  import * as utils from "./utils";
6
7
  import * as widgets from "./widgets";
7
8
 
8
- export { geocoder, i18n, map, picture, utils, widgets };
9
+ export { geocoder, i18n, map, picture, services, utils, widgets };
9
10
  export {default as API} from "./API";
10
11
  export {default as PhotoAdapter} from "./PhotoAdapter";
11
12
  export {default as URLHandler} from "./URLHandler";
@@ -191,7 +191,12 @@ export function getCroppedPanoData(picture) {
191
191
  // Check if crop is really necessary
192
192
  if(res) {
193
193
  res = Object.fromEntries(Object.entries(res || {}).filter(e => !isNaN(e[1])));
194
- if(res.fullWidth == res.croppedWidth && res.fullHeight == res.croppedHeight) {
194
+ if(
195
+ (!res.fullWidth && !res.croppedWidth && res.fullHeight && !res.croppedHeight)
196
+ || (res.fullWidth && !res.croppedWidth && !res.fullHeight && !res.croppedHeight)
197
+ || (res.fullWidth && !res.croppedWidth && res.fullHeight && !res.croppedHeight)
198
+ || (res.fullWidth == res.croppedWidth && res.fullHeight == res.croppedHeight)
199
+ ) {
195
200
  res = {};
196
201
  }
197
202
  }