diff --git a/library/api/panoramax/README.md b/library/api/panoramax/README.md index 6083b80..2d31eaf 100644 --- a/library/api/panoramax/README.md +++ b/library/api/panoramax/README.md @@ -2,79 +2,140 @@ _English below_ -Ce script permet d'afficher dans votre projet les photos provenant du projet Panoramax -(https://panoramax.openstreetmap.fr/ et/ou https://panoramax.ign.fr/) dans Lizmap Web Client. À noter qu'il n'est pas -nécessaire que votre projet soit dans une projection spécifique pour que ce script fonctionne. C'est QGIS qui se -chargera de la reprojection. +Ce script affiche dans Lizmap Web les photos du projet Panoramax (https://panoramax.fr). Les instances panoramax utilisées sont les instances IGN (https://panoramax.ign.fr) et OpenStreetMap France (https://panoramax.openstreetmap.fr). - + + +Deux fichiers : + +- **`panoramax4_3.9.js`** : **VERSION RECOMMANDEE**. Utilise le web-viewer Panoramax V4 (Web Components) et crée lui-même les couches de points Panoramax dans la carte. +- **`panoramax3_3.9.legacy.js`** : ancienne version basée sur l'API V3 et sur une couche Panoramax ajoutée dans le projet QGIS. Conservée pour référence, elle n'est plus maintenue et ne propose pas les modes d'affichage décrits plus bas. ## Pré-requis -- Lizmap Web Client ≥ 3.8 +- Lizmap Web Client 3.9. +- Le navigateur doit pouvoir joindre le CDN jsDelivr (web-viewer Panoramax 4.4.0), l'API `api.panoramax.xyz` et les serveurs de tuiles `panoramax.ign.fr` et `panoramax.openstreetmap.fr`. +- Aucune couche Panoramax n'est nécessaire dans le projet QGIS : le script ajoute lui-même deux couches de tuiles vectorielles (IGN et OSM) à la carte quand le panneau est ouvert. +- Aucune contrainte de projection : OpenLayers reprojette les tuiles à la volée. -- Pour des raisons de performance et du fait qu'OpenLayers ne permette pas la reprojection de tuiles vectorielles, - **la ou les couches de données Panoramax doivent être ajoutées depuis votre projet QGIS**. - **Un groupe ou une couche nommée Panoramax doit absolument exister dans votre projet QGIS. Sinon le script générera - une erreur**. Vous pouvez changer le nom de votre couche/groupe mais dans ce cas il sera nécessaire de modifier - la variable `PANORAMAX_QGIS_LAYER_NAME` du script pour qu'elle corresponde au nom de votre couche. Vous pouvez - également modifier depuis QGIS le style de votre couche de tuiles vectorielles si vous le souhaitez. +## Installation - - +1. Copier `panoramax4_3.9.js` dans le dossier `media/js/default` du répertoire Lizmap (ou `media/js/nom_du_projet` pour un seul projet). +2. Recharger la carte : un bouton avec une icône appareil photo apparaît dans la barre de gauche. ## Utilisation -Pour utiliser le script Panoramax dans votre projet : -1. assurez-vous d'avoir une couche Panoramax présente dans votre projet QGIS -2. copier `panoramax_3.8.js` dans le dossier `media/js` de votre projet -3. le bouton Panoramax s'affichera dans Lizmap Web Client -4. En cliquant sur ce bouton, les photos associées aux points de la couche Panoramax seront affichées. - Un clic sur un point permet d'afficher la photo correspondante. +1. Cliquer sur le bouton Panoramax : le minidock s'ouvre avec le visualiseur, et les points Panoramax s'affichent sur la carte. +2. Cliquer sur un point : la photo correspondante s'affiche. Une flèche indique l'azimut de la vue et la carte se centre sur la photo. +3. Naviguer dans les photos avec les flèches du visualiseur ou le lecteur sous l'image : la carte et la flèche suivent chaque photo. +4. Fermer le minidock : les couches Panoramax sont masquées et la disposition revient à la normale. + +## Modes d'affichage (position minidock) + +Deux boutons en haut à droite du visualiseur Panoramax permettent de changer la disposition de la page : + +- **Moitié d'écran.** La zone carte est partagée entre la carte à gauche et les photos à droite. Il est possible de déplacer le séparateur entre les deux panneaux en faisant glisser celui-ci vers la droite ou vers la gauche (part de la carte entre 20 % et 80 %, 50 % par défaut). La carte continue de suivre la navigation dans les photos. + +  + +- **Pleine carte.** Les photos de Panoramax prennent toute la zone carte et la carte devient une vignette en bas à gauche, toujours centrée sur la photo courante. Un clic sur la vignette de la carte rétablit la disposition. + +  + +Un second clic sur le bouton actif, ou la fermeture du minidock, rétablit la disposition normale. On peut passer directement d'un mode à l'autre. Si un panneau Lizmap est ouvert (couches, impression, etc.), la carte et les photos se placent à sa droite et suivent son ouverture ou sa fermeture. + +Les deux modes sont masqués en affichage mobile et n'existent pas quand `DOCK_POSITION` vaut `dock`. ## Personnalisation -Si vous modifiez le nom du groupe ou de la couche Panoramax dans QGIS, vous devrez adapter la variable -`PANORAMAX_QGIS_LAYER_NAME` dans le script pour qu'elle corresponde au nom de la couche dans votre projet. +Les constantes en tête du script, au-dessus de la ligne "DO NOT MODIFY BELOW THIS LINE" : + +| Constante | Rôle | Valeur par défaut | +|---|---|---| +| `DOCK_POSITION` | `minidock` ou `dock`. Les modes d'affichage n'existent qu'en `minidock` | `minidock` | +| `DOCK_ICON`, `DOCK_TITLE` | Icône et titre du bouton | `icon-camera`, `Panoramax` | +| `SPLIT_DEFAULT_PERCENT` | Part de la carte (à gauche) en moitié d'écran | `50` | +| `SPLIT_MIN_PERCENT`, `SPLIT_MAX_PERCENT` | Bornes du séparateur | `20`, `80` | +| `MAP_THUMBNAIL_WIDTH`, `MAP_THUMBNAIL_HEIGHT` | Taille de la vignette carte en pleine carte (px) | `240`, `160` | +| `MAP_THUMBNAIL_MARGIN` | Distance de la vignette aux bords bas et gauche (px) | `20` | +| `ARROW_ICON_SIZE`, `ARROW_ICON_COLOR` | Taille et couleur de la flèche d'azimut | `0.3`, `#e4e8e6` | +| `PANORAMAX_INSTANCE` | API utilisée par le visualiseur | `https://api.panoramax.xyz/api` | +| `CONTENT_TEXT` | Textes du panneau et des boutons, par langue (fr, en, it, es, de, pt, nl, pl). La langue du navigateur est utilisée, `en` par défaut | - | +| `DEBUG_MODE` | Messages de diagnostic dans la console | `false` | + +Plus bas dans le script, `PANORAMAX_SOURCES` liste les serveurs de tuiles (URL et zoom maximum) et`PANORAMAX_LAYER_CONFIG` le nom, la visibilité initiale et le z-index des couches. Le style des points est défini dans la méthode `#setPanoramaxLayerStyle()`. + +## Limites connues -Vous pouvez également personnaliser l'apparence du bouton ou le comportement d'affichage selon vos besoins via des -ajustements dans le fichier JavaScript. +- En moitié d'écran, les boutons de zoom Lizmap (en haut à droite de la zone carte) sont sous le visualiseur. La carte reste manipulable à la souris. +- Le mode et la répartition ne sont pas conservés d'une session à l'autre. # (EN) Panoramax -This script allows you to display photos from the Panoramax project (https://panoramax.openstreetmap.fr/ and/or -https://panoramax.ign.fr/) in your Lizmap Web Client project. It is not necessary for your project to have a specific -projection for this script to work. QGIS will handle the reprojection. +This script displays photos from the Panoramax project (https://panoramax.fr) in Lizmap Web Client, using the IGN (https://panoramax.ign.fr) and OpenStreetMap France (https://panoramax.openstreetmap.fr) instances. - + -## Requirements +Two files are provided: + +- **`panoramax4_3.9.js`**: **RECOMMENDED VERSION**. Uses the Panoramax V4 web-viewer (Web Components) and creates the Panoramax point layers in the map by itself. +- **`panoramax3_3.9.legacy.js`**: former version based on the V3 API and on a Panoramax layer added to the QGIS project. Kept for reference, no longer maintained, should not be used, without the display modes described below. -- Lizmap Web Client ≥ 3.8 +## Requirements -- For performance reasons and because OpenLayers does not allow the reprojection of vector tiles, **the Panoramax data - layer(s) must be added from your QGIS project**. **A layer or group named Panoramax must exist in your QGIS project. - Otherwise, the script will generate an error**. You can change the name of your layer/group, but in that case, you - will need to modify the PANORAMAX_QGIS_LAYER_NAME variable in the script to match the name of your layer. You can - also modify the vector tiles style in QGIS. +- Lizmap Web Client 3.9. +- The browser must reach the jsDelivr CDN (Panoramax web-viewer 4.4.0), the `api.panoramax.xyz` API and the `panoramax.ign.fr` and `panoramax.openstreetmap.fr` tile servers. +- No Panoramax layer is needed in the QGIS project: the script adds two vector tile layers (IGN and OSM) to the map when the panel is open. +- No projection constraint: OpenLayers reprojects the tiles on the fly (tested on an EPSG:2975 project). - - +## Installation +1. Copy `panoramax4_3.9.js` to the `media/js/default` folder of the Lizmap repository (or `media/js/project_name` for a single project). +2. Reload the map: a button with a camera icon appears in the left bar. ## Usage -1. copy the panoramax_3.8.js file to the media/js folder of your QGIS project. -2. verify that the Panoramax layer (or group) exists in your QGIS project. -3. open Lizmap Web Client and ensure the version installed is 3.8 or higher. -4. the Panoramax button should now appear in the Lizmap interface, allowing you to view the photos related to the points - in the Panoramax layer. +1. Click the Panoramax button: the minidock opens with the viewer, and the Panoramax points are displayed on the map. +2. Click a point: the matching photo is displayed. An arrow shows the view azimuth and the map is centred on the photo. +3. Browse the photos with the viewer arrows or the player under the picture: the map and the arrow follow each photo. +4. Close the minidock: the Panoramax layers are hidden and the layout goes back to normal. + +## Display modes (minidock position) + +Two buttons at the top right of the viewer change the page layout: + +- **Half screen.** The map area is shared between the map on the left and the photos on the right. A splitter between them can be dragged (map share between 20% and 80%, 50% by default). The map keeps following the photo navigation. + +  +- **Full map.** The photos take the whole map area and the map becomes a thumbnail at the bottom left, still centred on the current photo. Clicking the thumbnail restores the layout. + +  + +A second click on the active button, or closing the minidock, restores the normal layout. Switching directly from one mode to the other works. If a Lizmap panel is open (layers, print, etc.), the map and the photos are placed to its right and follow it when it opens or closes. + +Both modes are hidden on the mobile layout and do not exist when `DOCK_POSITION` is `dock`. ## Customization -If you change the name of the Panoramax group or layer in QGIS, you will need to adjust the `PANORAMAX_QGIS_LAYER_NAME` -variable in the script to match the name of your layer. +Constants at the top of the script, above the "DO NOT MODIFY BELOW THIS LINE" marker: + +| Constant | Purpose | Default | +|---|---|---| +| `DOCK_POSITION` | `minidock` or `dock`. Display modes only exist in `minidock` | `minidock` | +| `DOCK_ICON`, `DOCK_TITLE` | Button icon and title | `icon-camera`, `Panoramax` | +| `SPLIT_DEFAULT_PERCENT` | Map share (left side) in half-screen mode | `50` | +| `SPLIT_MIN_PERCENT`, `SPLIT_MAX_PERCENT` | Splitter bounds | `20`, `80` | +| `MAP_THUMBNAIL_WIDTH`, `MAP_THUMBNAIL_HEIGHT` | Map thumbnail size in full-map mode (px) | `240`, `160` | +| `MAP_THUMBNAIL_MARGIN` | Thumbnail distance from the bottom and left edges (px) | `20` | +| `ARROW_ICON_SIZE`, `ARROW_ICON_COLOR` | Azimuth arrow size and colour | `0.3`, `#e4e8e6` | +| `PANORAMAX_INSTANCE` | API used by the viewer | `https://api.panoramax.xyz/api` | +| `CONTENT_TEXT` | Panel and button texts per language (fr, en, it, es, de, pt, nl, pl). The browser language is used, `en` as fallback | - | +| `DEBUG_MODE` | Diagnostic messages in the console | `false` | + +Further down, `PANORAMAX_SOURCES` lists the tile servers (URL and maximum zoom) and `PANORAMAX_LAYER_CONFIG` the name, initial visibility and z-index of the layers. The point style is defined in the `#setPanoramaxLayerStyle()` method. + +## Known limits -You can also customize the appearance of the button or the display behavior according to your needs by making -adjustments to the JavaScript file. +- In half-screen mode the Lizmap zoom buttons (top right of the map area) sit under the viewer. The map can still be panned and zoomed with the mouse. +- The mode and the split ratio are not kept between sessions. diff --git a/library/api/panoramax/full-panoramax.jpg b/library/api/panoramax/full-panoramax.jpg new file mode 100644 index 0000000..db65455 Binary files /dev/null and b/library/api/panoramax/full-panoramax.jpg differ diff --git a/library/api/panoramax/half-panoramax.jpg b/library/api/panoramax/half-panoramax.jpg new file mode 100644 index 0000000..b5e6f13 Binary files /dev/null and b/library/api/panoramax/half-panoramax.jpg differ diff --git a/library/api/panoramax/panoramax_3.8.js b/library/api/panoramax/panoramax3_3.9.legacy.js similarity index 67% rename from library/api/panoramax/panoramax_3.8.js rename to library/api/panoramax/panoramax3_3.9.legacy.js index a8661d7..ce5098a 100644 --- a/library/api/panoramax/panoramax_3.8.js +++ b/library/api/panoramax/panoramax3_3.9.legacy.js @@ -15,6 +15,9 @@ const lizmapPanoramax = function() { // Dock position: can be dock, minidock const DOCK_POSITION = 'dock'; + + // BUFFER RADIUS FOR PANORAMAX SEARCH (in map units) + const BUFFER_RADIUS = 3; // Title of the dock const DOCK_TITLE = 'Panoramax'; @@ -54,7 +57,10 @@ const lizmapPanoramax = function() { ################################### ******************************** */ - // HTML Content + const PANORAMAX_JS_URL = 'https://cdn.jsdelivr.net/npm/@panoramax/web-viewer@3.2.3/build/index.min.js'; + const PANORAMAX_CSS_URL = 'https://cdn.jsdelivr.net/npm/@panoramax/web-viewer@3.2.3/build/index.min.css' + + // HTML Content const DOM_ID_PANORAMAX = "LizPanoramax-viewer"; const HTML_TEMPLATE = `
${POPUP_TEXT}
@@ -67,7 +73,14 @@ const lizmapPanoramax = function() { constructor(){ //Check if Panoramax Layer exists. If not, display an error message and exit directly this.panoramaxLayer = this.#getPanoramaxLayer(); - + this.panoramaxDockOpen = false; + + this.mapClickHandler = null; + this.panoViewerListeners = { + 'psv:view-rotated': null, + 'psv:picture-loaded': null + }; + if(!this.panoramaxLayer) { // check if there is a panoramax layer in the project const error = `No Panoramax layer available. @@ -88,10 +101,7 @@ const lizmapPanoramax = function() { } // everything is good we can display the "normal" dock content - this.#addMapEvent(); - this.#addLizmapDock(HTML_TEMPLATE); - //this.#setPanoramaxLayerVisibility(false); - this.#addPanoramaxHeadingLayer(); + this.#addLizmapDock(HTML_TEMPLATE); }); } @@ -101,37 +111,50 @@ const lizmapPanoramax = function() { */ async #loadScripts() { return new Promise(resolve => { - - const panoramax_js = 'https://cdn.jsdelivr.net/npm/@panoramax/web-viewer@3.2.3/build/index.min.js'; - const panoramax_css = 'https://cdn.jsdelivr.net/npm/@panoramax/web-viewer@3.2.3/build/index.min.css' - - if (!document.querySelector(`script[src="${panoramax_js}"]`)) { - // Chargement du script JS - const script = document.createElement('script'); - script.src = panoramax_js; - script.setAttribute('type', 'text/javascript'); - - script.onload = function () { - resolve(true); - }; - - script.onerror = function () { - if (DEBUG_MODE) console.error("Échec du chargement du script."); - resolve(false); - }; - - document.head.appendChild(script); - + + let scriptLoaded = !!document.querySelector(`script[src="${PANORAMAX_JS_URL}"]`); + let cssLoaded = !!document.querySelector(`link[href="${PANORAMAX_CSS_URL}"]`); + + + let jsPromise, cssPromise; + + if (!scriptLoaded) { + jsPromise = new Promise(jsResolve => { + const script = document.createElement('script'); + script.src = PANORAMAX_JS_URL; + script.type = 'text/javascript'; + script.onload = () => jsResolve(true); + script.onerror = () => { + if (DEBUG_MODE) console.error("Échec du chargement du script."); + jsResolve(false); + }; + document.head.appendChild(script); + }); + } else { + jsPromise = Promise.resolve(true); } - - if (!document.querySelector(`link[href="${panoramax_css}"]`)) { - // Chargement du CSS - const style = document.createElement("link"); - style.href = panoramax_css; - style.rel = 'stylesheet'; - style.type = 'text/css'; - document.head.appendChild(style); - } + + if (!cssLoaded) { + cssPromise = new Promise(cssResolve => { + const style = document.createElement("link"); + style.href = PANORAMAX_CSS_URL; + style.rel = 'stylesheet'; + style.type = 'text/css'; + style.onload = () => cssResolve(true); + style.onerror = () => { + if (DEBUG_MODE) console.error("Échec du chargement du CSS."); + cssResolve(false); + }; + document.head.appendChild(style); + }); + } else { + cssPromise = Promise.resolve(true); + } + + Promise.all([jsPromise, cssPromise]).then(results => { + // Si l'un des deux a échoué, on retourne false + resolve(results.every(Boolean)); + }); }); } @@ -189,6 +212,18 @@ const lizmapPanoramax = function() { * @param {Panoramax Picture} picture */ #setPanoramaxHeadingLayerHeading(picture){ + // Vérifie l'existence de la couche et de sa source avant de tenter de les utiliser + if(!this.layerArrowHeadingSource || !this.layerArrowHeading) { + if (DEBUG_MODE) console.warn("Heading layer not initialized"); + return; + } + + // Valider les données + if(!picture.geometry?.coordinates || !picture.properties) { + if (DEBUG_MODE) console.warn("Invalid picture data", picture); + return; + } + const oldFeature = this.layerArrowHeadingSource.getFeatures()?.[0]; if (oldFeature) { this.layerArrowHeadingSource.removeFeature(oldFeature); @@ -200,7 +235,8 @@ const lizmapPanoramax = function() { picture.geometry.coordinates[1] ]).transform('EPSG:4326', lizMap.map.projection.projCode) })); - const r = picture.properties["view:azimuth"] * (Math.PI/180); + const azimuth = picture.properties["view:azimuth"] ?? 0; + const r = azimuth * (Math.PI/180); this.layerArrowHeading.getStyle().getImage().setRotation(r); this.layerArrowHeading.changed(); } @@ -219,11 +255,16 @@ const lizmapPanoramax = function() { * Init all the method */ initPanoramaxDock(){ - if(this.panoramaxLayer){ + if(this.panoramaxLayer && !this.panoramaxDockOpen){ lizMap.mainLizmap.popup.active = false; - this.panoramaxDockOpen = true; + this.panoramaxDockOpen = true; + this.#addPanoramaxHeadingLayer(); this.#setPanoramaxLayerVisibility(true); - this.#addPanoramaxViewer(); + //Attendre que le DOM soit prêt + setTimeout(() => { + this.#addPanoramaxViewer(); + this.#addMapEvent(); + }, 100); } } @@ -231,51 +272,103 @@ const lizmapPanoramax = function() { * Add Panoramax Viewer */ #addPanoramaxViewer(){ - this.panoViewer = new Panoramax.Viewer( - DOM_ID_PANORAMAX, - PANORAMAX_INSTANCE, - { - hash:false, // !!! do not change => change Lizmap URL - map: false - } - ); - this.#addPanoramaxViewerEvent(); + if (!window.Panoramax) { + if (DEBUG_MODE) console.error("Panoramax global not available"); + return; + } + const viewerContainer = document.getElementById(DOM_ID_PANORAMAX); + if (!viewerContainer) { + if (DEBUG_MODE) console.error("Viewer container not found in DOM"); + return; + } + if(this.panoViewer) { + if (DEBUG_MODE) console.warn("Panoramax Viewer already initialized"); + return; + } + try { + this.panoViewer = new Panoramax.Viewer( + DOM_ID_PANORAMAX, + PANORAMAX_INSTANCE, + { + hash:false, // !!! do not change => change Lizmap URL + map: false + } + ); + this.#addPanoramaxViewerEvent(); + } catch (error) { + if (DEBUG_MODE) console.error("Error initializing Panoramax Viewer", error); + throw new Error("Error initializing Panoramax Viewer"); + } } /** * Add all Panoramax Viewer Events */ #addPanoramaxViewerEvent(){ - this.panoViewer.addEventListener('psv:view-rotated', (e) => { + // Stocker les listeners AVEC les bonnes fonctions + this.panoViewerListeners['psv:view-rotated'] = (e) => { if(e.explicitOriginalTarget._selectedPicId){ - let r = e.detail.x * (Math.PI/180); + const azimuth = e.detail.x ?? 0; // Valeur par défaut + let r = azimuth * (Math.PI/180); this.layerArrowHeading.getStyle().getImage().setRotation(r); this.layerArrowHeading.changed(); } - }); + }; - this.panoViewer.addEventListener('psv:picture-loaded', (e) => { - let r = e.detail.x * (Math.PI/180); - if(this.layerArrowHeadingSource.getFeatures()[0] && e.detail.lon && e.detail.lat){ + this.panoViewerListeners['psv:picture-loaded'] = (e) => { + const azimuth = e.detail.x ?? 0; // Valeur par défaut + let r = azimuth * (Math.PI/180); + if(this.layerArrowHeadingSource.getFeatures()[0] + && typeof e.detail?.lon === 'number' + && typeof e.detail?.lat === 'number' ){ const coords = lizMap.ol.proj.transform([e.detail.lon, e.detail.lat], 'EPSG:4326', lizMap.mainLizmap.projection); lizMap.mainLizmap.map.getView().setCenter(coords); this.layerArrowHeadingSource.getFeatures()[0].getGeometry().setCoordinates(coords); } this.layerArrowHeading.getStyle().getImage().setRotation(r); this.layerArrowHeading.changed(); - }); + }; + + this.panoViewer.addEventListener('psv:view-rotated', this.panoViewerListeners['psv:view-rotated']); + this.panoViewer.addEventListener('psv:picture-loaded', this.panoViewerListeners['psv:picture-loaded']); } /** * Fetch picture on single map cick */ #addMapEvent(){ - lizMap.mainLizmap.map.on('singleclick', e => { + // Créer une méthode nommée pour pouvoir la désabonner + this.mapClickHandler = (e) => { //Fire event only if panoramax dock is opened if(this.panoramaxDockOpen){ - const extent =this.#getBufferedExtent(e.coordinate); + const extent = this.#getBufferedExtent(e.coordinate); this.#getPanoramaxPicture(extent); } + }; + lizMap.mainLizmap.map.on('singleclick', this.mapClickHandler); + } + + /** + * Remove map click event listener + */ + #removeMapEvent(){ + if(this.mapClickHandler){ + lizMap.mainLizmap.map.un('singleclick', this.mapClickHandler); + this.mapClickHandler = null; + } + } + + /** + * Remove all Panoramax Viewer Events + */ + #removePanoramaxViewerEvent(){ + if(!this.panoViewer) return; + + Object.keys(this.panoViewerListeners).forEach(eventName => { + if(this.panoViewerListeners[eventName]){ + this.panoViewer.removeEventListener(eventName, this.panoViewerListeners[eventName]); + this.panoViewerListeners[eventName] = null; + } }); } @@ -287,13 +380,12 @@ const lizmapPanoramax = function() { #getBufferedExtent(p){ const point = new lizMap.ol.geom.Point(p); const extent = point.getExtent(); - const radius = 3; - const bufferedExtent = new lizMap.ol.extent.buffer(extent,radius); + const bufferedExtent = new lizMap.ol.extent.buffer(extent,BUFFER_RADIUS); const pbl = new lizMap.ol.geom.Point([bufferedExtent[0], bufferedExtent[1]]); //bottom left const pur = new lizMap.ol.geom.Point([bufferedExtent[2], bufferedExtent[3]]); //upper right - if(lizMap.map.projection.projCode != "EPSG:4326"){ + if(lizMap.map.projection.projCode !== "EPSG:4326"){ // reproject extent to 4326 pbl.transform(lizMap.map.projection.projCode, 'EPSG:4326'); pur.transform(lizMap.map.projection.projCode, 'EPSG:4326'); @@ -323,7 +415,7 @@ const lizmapPanoramax = function() { throw new Error(error); } const picture = await response.json(); - if(picture.features.length){ + if(picture?.features?.length > 0 && this.panoViewer){ this.panoViewer.select(null, picture.features[0].id, true); this.#setPanoramaxHeadingLayerHeading(picture.features[0]); } @@ -340,10 +432,18 @@ const lizmapPanoramax = function() { if(this.panoramaxLayer) { lizMap.mainLizmap.popup.active = true; this.panoramaxDockOpen = false; + + // Remove map click listener + this.#removeMapEvent(); + + // Remove Panoramax viewer listeners + this.#removePanoramaxViewerEvent(); + + // Clear layer used for heading arrow this.layerArrowHeadingSource.clear(); //Hide layer - lizPanoramax.#setPanoramaxLayerVisibility(false); + this.#setPanoramaxLayerVisibility(false); // Remove all viewer references if (this.panoViewer) { @@ -363,6 +463,8 @@ const lizmapPanoramax = function() { /** * Lizmap event */ + let lizPanoramax; + lizMap.events.on({ 'uicreated': function(e) { lizPanoramax = new LizPanoramax(); @@ -370,24 +472,24 @@ const lizmapPanoramax = function() { //MINI DOCK 'minidockopened': e => { - if (e.id === DOCK_ID) { + if (e.id === DOCK_ID && lizPanoramax) { lizPanoramax.initPanoramaxDock(); } }, 'minidockclosed': e => { - if (e.id === DOCK_ID) { + if (e.id === DOCK_ID && lizPanoramax) { lizPanoramax.removePanoramaxDock(); } }, //DOCK 'dockopened': e => { - if (e.id === DOCK_ID) { + if (e.id === DOCK_ID && lizPanoramax) { lizPanoramax.initPanoramaxDock(); } }, 'dockclosed': e => { - if (e.id === DOCK_ID) { + if (e.id === DOCK_ID && lizPanoramax) { lizPanoramax.removePanoramaxDock(); } }, diff --git a/library/api/panoramax/panoramax4_3.9.js b/library/api/panoramax/panoramax4_3.9.js new file mode 100644 index 0000000..3a35802 --- /dev/null +++ b/library/api/panoramax/panoramax4_3.9.js @@ -0,0 +1,887 @@ +/** + * @license Mozilla Public License Version 2.0 + * This script has been developed by the "community" + * There isn't any guarantee that this script will work on another version of Lizmap Web Client. + */ + +const lizmapPanoramax = function() { + + // ID of the dock (do not change) + const DOCK_ID = 'panoramax'; + + // Icon of the dock menu and used before each link + // See https://getbootstrap.com/2.3.2/base-css.html#icons + const DOCK_ICON = 'icon-camera'; + + // Dock position: can be dock, minidock + const DOCK_POSITION = 'minidock'; + + // Title of the dock + const DOCK_TITLE = 'Panoramax'; + + // LAYOUT MODES (only when DOCK_POSITION is 'minidock') + // "half screen": map on the left, photos on the right, draggable splitter + // "full map" : photos take the map area, the map becomes a thumbnail + const SPLIT_DEFAULT_PERCENT = 50; // map share (left side) in half-screen mode + const SPLIT_MIN_PERCENT = 20; // separator bounds + const SPLIT_MAX_PERCENT = 80; + const MAP_THUMBNAIL_WIDTH = 240; // thumbnail width in full-panoramax mode + const MAP_THUMBNAIL_HEIGHT = 160; + const MAP_THUMBNAIL_MARGIN = 20; // thumbnail distance from the bottom and left edges (px) + + // ARROW ICON PROPERTIES + const ARROW_ICON_SIZE = 0.3; + const ARROW_ICON_COLOR = "#e4e8e6"; + + const PANORAMAX_INSTANCE = 'https://api.panoramax.xyz/api'; + + const CONTENT_TEXT = { + "fr": { + click: "Veuillez cliquer sur un point de la couche Panoramax pour afficher les photos.", + half: "Afficher la carte et les photos côte à côte", + halfExit: "Revenir à la disposition normale", + fullMap: "Afficher les photos sur toute la carte", + fullMapExit: "Revenir à la carte" + }, + "en": { + click: "Please click on a point in the Panoramax layer to display the photos.", + half: "Show map and photos side by side", + halfExit: "Back to normal layout", + fullMap: "Show photos over the whole map", + fullMapExit: "Back to the map" + }, + "it": { + click: "Per favore, fai clic su un punto del livello Panoramax per visualizzare le foto.", + half: "Mostra mappa e foto affiancate", + halfExit: "Torna alla disposizione normale", + fullMap: "Mostra le foto su tutta la mappa", + fullMapExit: "Torna alla mappa" + }, + "es": { + click: "Por favor, haz clic en un punto de la capa Panoramax para mostrar las fotos.", + half: "Mostrar mapa y fotos lado a lado", + halfExit: "Volver a la disposición normal", + fullMap: "Mostrar las fotos en todo el mapa", + fullMapExit: "Volver al mapa" + }, + "de": { + click: "Bitte klicken Sie auf einen Punkt in der Panoramax-Schicht, um die Fotos anzuzeigen.", + half: "Karte und Fotos nebeneinander anzeigen", + halfExit: "Zurück zur normalen Ansicht", + fullMap: "Fotos über die ganze Karte anzeigen", + fullMapExit: "Zurück zur Karte" + }, + "pt": { + click: "Por favor, clique em um ponto da camada Panoramax para exibir as fotos.", + half: "Mostrar mapa e fotos lado a lado", + halfExit: "Voltar à disposição normal", + fullMap: "Mostrar as fotos em todo o mapa", + fullMapExit: "Voltar ao mapa" + }, + "nl": { + click: "Klik alstublieft op een punt in de Panoramax-laag om de foto's te bekijken.", + half: "Kaart en foto's naast elkaar tonen", + halfExit: "Terug naar de normale weergave", + fullMap: "Foto's over de hele kaart tonen", + fullMapExit: "Terug naar de kaart" + }, + "pl": { + click: "Kliknij punkt na warstwie Panoramax, aby wyświetlić zdjęcia.", + half: "Pokaż mapę i zdjęcia obok siebie", + halfExit: "Wróć do normalnego układu", + fullMap: "Pokaż zdjęcia na całej mapie", + fullMapExit: "Wróć do mapy" + } + }; + + //Change text depending on navigator language + const DEFAULT_LANGUAGE = "en" + const NAVIGATOR_LANGUAGE = navigator.language ? navigator.language.slice(0, 2) : DEFAULT_LANGUAGE; + //IF the navigator.language is not listed in CONTENT_TEXT => switch to the DEFAULT_LANGUAGE + const TEXT = CONTENT_TEXT[NAVIGATOR_LANGUAGE] ? CONTENT_TEXT[NAVIGATOR_LANGUAGE] : CONTENT_TEXT[DEFAULT_LANGUAGE]; + + //Layout mode only with minidock + const LAYOUT_ENABLED = (DOCK_POSITION === 'minidock'); + + const DEBUG_MODE = false; + + /** ******************************** + ################################### + DO NOT MODIFY BELOW THIS LINE + ################################### + ******************************** */ + + const PANORAMAX_JS_URL = 'https://cdn.jsdelivr.net/npm/@panoramax/web-viewer@4.4.0/build/index.min.js'; + const PANORAMAX_CSS_URL = 'https://cdn.jsdelivr.net/npm/@panoramax/web-viewer@4.4.0/build/index.min.css' + + // Layout button icons (inline SVG, 20 px, inherited color) + const ICON_HALF = ``; + const ICON_FULLMAP = ``; + const ICON_EXIT = ``; + + // Buttons slotted in the "top-right" corner of the Panoramax viewer (minidock only) + const LAYOUT_BUTTONS = LAYOUT_ENABLED ? ` +${TEXT.click}
+ ${PHOTO_VIEWER} +${error}
`); + return; + } + + // Scripts loaded successfully, display dock content + this.#addLizmapDock(HTML_TEMPLATE); + + // Create Vector Tile layers (no longer dependent on QGIS) + if (!this.panoramaxVectorLayers) { + this.panoramaxVectorLayers = this.#addPanoramaxVectorLayers(); + + if (DEBUG_MODE) { + console.log('Panoramax Vector Tile layers created and registered'); + } + } + }); + } + + /** + * LOAD PANORAMAX EXTERNAL JS AND CSS + * @returns {Promise