@panoramax/web-viewer 4.0.1 → 4.0.2-develop-9b499e28

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 (56) 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 +137 -83
  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/data/Map_geocoder_nominatim.json +25 -40
  53. package/tests/utils/InitParameters.test.js +15 -15
  54. package/tests/utils/__snapshots__/geocoder.test.js.snap +5 -16
  55. package/tests/utils/geocoder.test.js +2 -2
  56. 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,22 @@
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
+
6
+ const PLACETYPE_ZOOM = {
7
+ "house": 20,
8
+ "housenumber": 20,
9
+ "street": 18,
10
+ "locality": 15,
11
+ "district": 13,
12
+ "municipality": 12,
13
+ "city": 12,
14
+ "county": 8,
15
+ "region": 7,
16
+ "state": 7,
17
+ "country": 5
18
+ };
19
+
4
20
  /**
5
21
  * Transforms a set of parameters into an URL-ready string
6
22
  * It also removes null/undefined values
@@ -18,69 +34,6 @@ function geocoderParamsToURLString(params) {
18
34
  return new URLSearchParams(p).toString();
19
35
  }
20
36
 
21
- /**
22
- * Transforms Nominatim search result into a nice-to-display address.
23
- * @param {object} addr The Nominatim API "address" property
24
- * @returns {string} The clean-up string for display
25
- * @private
26
- */
27
- function nominatimAddressToPlaceName(addr) {
28
- // 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
-
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
- }
71
-
72
- if(street && res.length > 0) { res += (addr.house_number ? " " : ", ")+street; }
73
- else if(street) { res = street; }
74
-
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
- }
80
-
81
- return res;
82
- }
83
-
84
37
  /**
85
38
  * Nominatim (OSM) geocoder, ready to use for our Map
86
39
  * @private
@@ -94,18 +47,19 @@ export function forwardGeocodingNominatim(config) {
94
47
  viewbox: config.bbox,
95
48
  };
96
49
 
97
- return fetch(`https://nominatim.openstreetmap.org/search?${geocoderParamsToURLString(params)}&format=geojson&polygon_geojson=1&addressdetails=1`)
50
+ return fetch(`${NominatimBaseUrl()}/search?${geocoderParamsToURLString(params)}&format=geocodejson&addressdetails=1`)
98
51
  .then(res => res.json())
99
52
  .then(res => {
100
53
  const finalRes = { features: [] };
101
54
  const listedNames = [];
102
- res.features.forEach(f => {
103
- const plname = nominatimAddressToPlaceName(f.properties.address) || f.properties.display_name;
55
+ (res.features || []).forEach(f => {
56
+ const plname = geocodeJsonToPlaceName(f.properties?.geocoding) || f.properties?.geocoding?.label;
104
57
  if(!listedNames.includes(plname)) {
105
58
  finalRes.features.push({
106
59
  place_type: ["place"],
107
60
  place_name: plname,
108
- bounds: new maplibregl.LngLatBounds(f.bbox),
61
+ center: new maplibregl.LngLat(...f.geometry.coordinates),
62
+ zoom: PLACETYPE_ZOOM[f.properties?.geocoding?.type],
109
63
  });
110
64
  listedNames.push(plname);
111
65
  }
@@ -115,18 +69,113 @@ export function forwardGeocodingNominatim(config) {
115
69
  }
116
70
 
117
71
  export function reverseGeocodingNominatim(lat, lon) {
118
- return fetch(`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&zoom=18&format=jsonv2`)
72
+ return fetch(`${NominatimBaseUrl()}/reverse?lat=${lat}&lon=${lon}&zoom=18&format=geocodejson`)
119
73
  .then(res => res.json())
120
- .then(res => nominatimAddressToPlaceName(res?.address));
74
+ .then(res => geocodeJsonToPlaceName(res?.features?.shift()?.properties?.geocoding));
121
75
  }
122
76
 
123
77
  /**
124
78
  * 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
79
+ * @param {object} config Configuration sent by MapLibre GL Geocoder, following the geocoderApi format ( https://maplibre.org/maplibre-gl-geocoder/types/MaplibreGeocoderApiConfig.html )
80
+ * @returns {object} GeoJSON Feature collection in Carmen GeoJSON format ( https://maplibre.org/maplibre-gl-geocoder/types/CarmenGeojsonFeature.html )
127
81
  * @private
128
82
  */
129
83
  export function forwardGeocodingBAN(config) {
84
+ return forwardGeocodingStandard(config, AdresseDataGouvBaseURL());
85
+ }
86
+
87
+ /**
88
+ * Transforms GeocodeJSON search result into a nice-to-display address.
89
+ * @param {object} props The GecodeJSON API feature properties
90
+ * @returns {string} The clean-up string for display
91
+ * @private
92
+ */
93
+ function geocodeJsonToPlaceName(props) {
94
+ // API format @ https://github.com/geocoders/geocodejson-spec/blob/master/draft/README.md
95
+ if(!props || typeof props != "object") { return ""; }
96
+
97
+ // P1 = main name, P2=locality-like, P3=country+high-level admin
98
+ let p1 = props.name;
99
+ let p2 = [], p3 = [];
100
+
101
+ switch(props.type) {
102
+ case "hamlet":
103
+ case "croft":
104
+ case "isolated_dwelling":
105
+ case "neighbourhood":
106
+ case "allotments":
107
+ case "quarter":
108
+ case "farm":
109
+ case "farmyard":
110
+ case "industrial":
111
+ case "commercial":
112
+ case "retail":
113
+ case "city_block":
114
+ case "residential":
115
+ case "locality":
116
+ case "district":
117
+ p3.push(props.city);
118
+ p3.push(props.county);
119
+ p3.push(props.state);
120
+ p3.push(props.country);
121
+ break;
122
+ case "city":
123
+ p3.push(props.county);
124
+ p3.push(props.state);
125
+ p3.push(props.country);
126
+ break;
127
+ case "region":
128
+ p3.push(props.county);
129
+ p3.push(props.state);
130
+ p3.push(props.country);
131
+ break;
132
+ case "country":
133
+ break;
134
+ case "house":
135
+ case "housenumber":
136
+ p2.push(props.housenumber);
137
+ p2.push(props.street);
138
+ p2.push(props.locality);
139
+ p2.push(props.district);
140
+ p3.push(props.city);
141
+ p3.push(props.county);
142
+ p3.push(props.state);
143
+ p3.push(props.country);
144
+ break;
145
+ case "street":
146
+ case "road":
147
+ default:
148
+ p2.push(props.street);
149
+ p2.push(props.locality);
150
+ p2.push(props.district);
151
+ p3.push(props.city);
152
+ p3.push(props.county);
153
+ p3.push(props.state);
154
+ p3.push(props.country);
155
+ break;
156
+ }
157
+
158
+ p2 = p2.filter(v => v);
159
+ p2 = p2.filter((v,i) => v != p1 && (i === 0 || p2[i-1] !== v));
160
+ p2 = p2.length > 0 ? (props.housenumber ? p2.slice(0,2).join(" ") : p2.shift()) : null;
161
+ if(p2 === p1) { p2 = null; }
162
+
163
+ p3 = p3.filter(v => v);
164
+ p3 = p3.filter((v,i) => v != p1 && (!p2 || !p2.includes(v)) && (i === 0 || p3[i-1] !== v));
165
+
166
+ let res = [p1, p2, p3.shift()].filter(v => v);
167
+
168
+ return res.join(", ");
169
+ }
170
+
171
+ /**
172
+ * Standard forward geocoder
173
+ * @param {object} config Configuration sent by MapLibre GL Geocoder, following the geocoderApi format ( https://maplibre.org/maplibre-gl-geocoder/types/MaplibreGeocoderApiConfig.html )
174
+ * @param {string} endpoint The URL endpoint (everything before the /?q=...)
175
+ * @returns {object} GeoJSON Feature collection in Carmen GeoJSON format ( https://maplibre.org/maplibre-gl-geocoder/types/CarmenGeojsonFeature.html )
176
+ * @private
177
+ */
178
+ export function forwardGeocodingStandard(config, endpoint) {
130
179
  // Transform parameters into BAN format
131
180
  const params = { q: config.query, limit: config.limit };
132
181
  if(typeof config.proximity === "string") {
@@ -135,18 +184,23 @@ export function forwardGeocodingBAN(config) {
135
184
  params.lon = lon;
136
185
  }
137
186
 
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 };
140
-
141
- return fetch(`https://api-adresse.data.gouv.fr/search/?${geocoderParamsToURLString(params)}`)
187
+ return fetch(`${endpoint}/?${geocoderParamsToURLString(params)}`)
142
188
  .then(res => res.json())
143
189
  .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;
190
+ const finalRes = { features: [] };
191
+ const listedNames = [];
192
+ (res.features || []).forEach(f => {
193
+ const plname = geocodeJsonToPlaceName(f.properties);
194
+ if(!listedNames.includes(plname) && f.properties.type != "other") {
195
+ finalRes.features.push({
196
+ place_type: ["place"],
197
+ place_name: plname,
198
+ center: new maplibregl.LngLat(...f.geometry.coordinates),
199
+ zoom: PLACETYPE_ZOOM[f.properties.type],
200
+ });
201
+ listedNames.push(plname);
202
+ }
203
+ });
204
+ return finalRes;
151
205
  });
152
- }
206
+ }
@@ -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
  }
@@ -0,0 +1,57 @@
1
+ /*
2
+ * Settings that may be useful to change for special cases (e.g. offline implementation).
3
+ * Most users will be fine with the defaults.
4
+ */
5
+
6
+
7
+ /**
8
+ * OpenStreetMap iD editor URL
9
+ * @returns {string} The editor URL
10
+ */
11
+ export function IdEditorURL() {
12
+ return "https://www.openstreetmap.org/edit?editor=id";
13
+ }
14
+
15
+
16
+ /* -----------------------------------------------------
17
+ * Internet speed tests
18
+ */
19
+
20
+ /**
21
+ * Get the threshold fast internet speed value.
22
+ *
23
+ * @returns {number} the minimum speed in MBps for internet to be considered "fast"
24
+ */
25
+ export function InternetFastThreshold() {
26
+ return 10;
27
+ }
28
+
29
+ /**
30
+ * Get the fast internet speed test file.
31
+ *
32
+ * @returns {string} URL for the file to use for internet speed testing.
33
+ */
34
+ export function InternetFastTestFile() {
35
+ return "https://panoramax.openstreetmap.fr/images/05/ca/2c/98/0111-4baf-b6f3-587bb8847d2e.jpg";
36
+ }
37
+
38
+
39
+ /* -----------------------------------------------------
40
+ * Geocoding-related settings
41
+ */
42
+
43
+ /**
44
+ * Get the Base Adresse Nationale URL for geocoding API.
45
+ * @returns {string} The Base Adresse Nationale URL (must support /search calls).
46
+ */
47
+ export function AdresseDataGouvBaseURL() {
48
+ return "https://data.geopf.fr/geocodage/search";
49
+ }
50
+
51
+ /**
52
+ * Get the Nominatim base URL for geocoding API.
53
+ * @returns {string} The Nominatim URL (must support /search & /reverse calls).
54
+ */
55
+ export function NominatimBaseUrl() {
56
+ return "https://nominatim.openstreetmap.org";
57
+ }