diff --git a/.eslintrc.json b/.eslintrc.json index d9b08572f..d59417be1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -62,7 +62,10 @@ ], "quotes": [ "error", - "double" + "double", + { + "allowTemplateLiterals": true + } ], "semi": [ "error", diff --git a/DRAFT_CHANGELOG.md b/DRAFT_CHANGELOG.md index c392283b2..7b09760eb 100644 --- a/DRAFT_CHANGELOG.md +++ b/DRAFT_CHANGELOG.md @@ -1,5 +1,6 @@ + # Extension Geoportail OpenLayers, version __VERSION__ **__DATE__** @@ -7,36 +8,36 @@ ## Summary -Ajout d'écouteurs sur les controles de Géocodage direct en inverse +Amélioration du traitement pour les couches vecteur tuilé (documentation, légendes, ajout simplifié) ## Changelog * [Added] - - Ajout d'evenements sur le contrôle SearchEngine lors de la selection d'un résultat (#348) - - Ajout d'evenements sur le contrôle ReverseGeocode lors de la selection d'un résultat (#356) - - Ajout de méthodes publiques sur les contrôles Iso et Route (#343) : - - getGeoJSON() : fournit le tracé au format GeoJSON - - getData() : fournit la configuration du calcul + - Ajout simplifié d'une couche vecteur tuilé IGN : + ```js + var LayerMapBox = new ol.layer.GeoportalMapBox({ + layer : "PLAN.IGN", + style : "gris" + }); + ``` * [Changed] + - GFI : ignore la propriété "icon" lors de la construction de la pop-up (05bbfa0ab8ccd09b32954aabad421b00f6faec35) + - Vecteur tuilé : évolution sur la construction et l'affichage des légendes (#362) + * [Deprecated] * [Removed] * [Fixed] - - Faute d'ortographe description couche Isocurve - - Ajout des modules dans la JSDoc (#349) - - Mise à jour des clefs des services (#352) - * [Security] --- - # Extension Geoportail Leaflet, version __VERSION__ **__DATE__** @@ -44,15 +45,10 @@ Ajout d'écouteurs sur les controles de Géocodage direct en inverse ## Summary -Ajout d'écouteurs sur les contrôles de Géocodage direct et inverse - ## Changelog * [Added] - - Ajout d'evenements sur le contrôle ReverseGeocoding lors de la selection d'un résultat (#351) - - Ajout d'evenements sur le contrôle SearchEngine lors de la selection d'un résultat (#354) - * [Changed] * [Deprecated] @@ -66,7 +62,6 @@ Ajout d'écouteurs sur les contrôles de Géocodage direct et inverse --- - # Extension Geoportail Itowns, version __VERSION__ **__DATE__** @@ -80,7 +75,7 @@ Ajout d'écouteurs sur les contrôles de Géocodage direct et inverse * [Changed] - - mise à jour du readme + - mise à jour du readme pour ajout couche VT (d61ebdd223d1f1516e1877209190b298f18f71d0) * [Deprecated] diff --git a/build/scripts/release/geoportal-extensions-openlayers-3.2.20.tgz b/build/scripts/release/geoportal-extensions-openlayers-3.2.20.tgz new file mode 100644 index 000000000..bb2b35737 Binary files /dev/null and b/build/scripts/release/geoportal-extensions-openlayers-3.2.20.tgz differ diff --git a/build/scripts/release/package-openlayers.json b/build/scripts/release/package-openlayers.json index dc9e794c0..a9b541c04 100644 --- a/build/scripts/release/package-openlayers.json +++ b/build/scripts/release/package-openlayers.json @@ -1,35 +1,21 @@ { - "module" : "src/OpenLayers/index.js", - "name" : "geoportal-extensions-openlayers", - "devDependencies" : {}, - "types" : "src/OpenLayers/index.d.ts", - "license" : "CECILL-B", - "repository" : { - "type" : "git", - "url" : "https://github.com/IGNF/geoportal-extensions.git" - }, - "peerDependencies" : {}, - "bugs" : {}, - "keywords" : [ - "geoportail", - "plugin", - "javascript", - "OpenLayers" - ], + "directories" : {}, "dependencies" : { - "loglevel" : "1.6.6", - "ol" : "6.9.0", "@mapbox/mapbox-gl-style-spec" : "13.20.1", - "node-fetch" : "^2.6.1", - "geoportal-access-lib" : "3.2.1", "proj4" : "2.7.5", + "ol" : "6.9.0", + "sortablejs" : "1.14.0", "eventbusjs" : "0.2.0", + "loglevel" : "1.6.6", + "node-fetch" : "^2.6.1", "xmldom" : "^0.1.27", - "sortablejs" : "1.14.0" + "geoportal-access-lib" : "3.2.1" }, - "directories" : {}, - "version" : "3.2.20", - "scripts" : {}, + "peerDependencies" : {}, + "license" : "CECILL-B", + "types" : "src/OpenLayers/index.d.ts", + "devDependencies" : {}, + "name" : "geoportal-extensions-openlayers", "files" : [ "dist/", "src/", @@ -37,5 +23,19 @@ "README.md", "package.json" ], - "date" : "21/02/2023" + "bugs" : {}, + "repository" : { + "type" : "git", + "url" : "https://github.com/IGNF/geoportal-extensions.git" + }, + "version" : "3.2.20", + "keywords" : [ + "geoportail", + "plugin", + "javascript", + "OpenLayers" + ], + "date" : "21/02/2023", + "scripts" : {}, + "module" : "src/OpenLayers/index.js" } diff --git a/doc/CHANGELOG-leaflet.md b/doc/CHANGELOG-leaflet.md index 0e8291366..f0873a825 100644 --- a/doc/CHANGELOG-leaflet.md +++ b/doc/CHANGELOG-leaflet.md @@ -84,6 +84,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Extension Geoportail Leaflet, version 2.2.8](#extension-geoportail-leaflet-version-228) * [Summary](#summary-24) * [Changelog](#changelog-21) +- [Extension Geoportail Leaflet, version 2.2.9](#extension-geoportail-leaflet-version-229) + * [Summary](#summary-25) + * [Changelog](#changelog-22) @@ -757,3 +760,30 @@ Ajout des déclarations typescript, améliorations et corrections des interfaces * [Security] --- +# Extension Geoportail Leaflet, version 2.2.9 + +**21/02/2023** +> Release Extension Geoportail leaflet + +## Summary + +Ajout d'écouteurs sur les contrôles de Géocodage direct et inverse + +## Changelog + +* [Added] + + - Ajout d'evenements sur le contrôle ReverseGeocoding lors de la selection d'un résultat (#351) + - Ajout d'evenements sur le contrôle SearchEngine lors de la selection d'un résultat (#354) + +* [Changed] + +* [Deprecated] + +* [Removed] + +* [Fixed] + +* [Security] + +--- diff --git a/doc/CHANGELOG-openlayers.md b/doc/CHANGELOG-openlayers.md index 7484a454a..6a0f776d3 100644 --- a/doc/CHANGELOG-openlayers.md +++ b/doc/CHANGELOG-openlayers.md @@ -142,6 +142,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Extension Geoportail OpenLayers, version 3.2.19](#extension-geoportail-openlayers-version-3219) * [Summary](#summary-42) * [Changelog](#changelog-39) +- [Extension Geoportail OpenLayers, version 3.2.20](#extension-geoportail-openlayers-version-3220) + * [Summary](#summary-43) + * [Changelog](#changelog-40) @@ -1533,3 +1536,37 @@ Mise à jour de l'access-lib en version 3.2.1 * [Security] --- +# Extension Geoportail OpenLayers, version 3.2.20 + +**21/02/2023** +> Release Extension Geoportail openlayers + +## Summary + +Ajout d'écouteurs sur les controles de Géocodage direct en inverse + +## Changelog + +* [Added] + + - Ajout d'evenements sur le contrôle SearchEngine lors de la selection d'un résultat (#348) + - Ajout d'evenements sur le contrôle ReverseGeocode lors de la selection d'un résultat (#356) + - Ajout de méthodes publiques sur les contrôles Iso et Route (#343) : + - getGeoJSON() : fournit le tracé au format GeoJSON + - getData() : fournit la configuration du calcul + +* [Changed] + +* [Deprecated] + +* [Removed] + +* [Fixed] + + - Faute d'ortographe description couche Isocurve + - Ajout des modules dans la JSDoc (#349) + - Mise à jour des clefs des services (#352) + +* [Security] + +--- diff --git a/doc/README-itowns.md b/doc/README-itowns.md index 726e207b1..309db0ad7 100644 --- a/doc/README-itowns.md +++ b/doc/README-itowns.md @@ -1,6 +1,6 @@ # Extension Géoportail pour iTowns -[![release](https://img.shields.io/badge/release%20-itowns%202.3.6-brightgreen.svg?style=flat)](https://github.com/IGNF/geoportal-extensions/releases/tag/itowns-2.3.6) +![GitHub package.json version](https://img.shields.io/github/package-json/v/IGNF/geoportal-extensions?display_name=release&filename=build%2Fscripts%2Frelease%2Fpackage-itowns.json) @@ -30,11 +30,16 @@ - [Exemple d'utilisation](#exemple-dutilisation-2) - [Utilisation directe de la librairie iTowns](#utilisation-directe-de-la-librairie-itowns-1) - [Exemple d'utilisation](#exemple-dutilisation-3) + - [Affichage des couches Vecteur Tuilé Géoportail](#affichage-des-couches-vecteur-tuilé-géoportail) + - [Utilisation de l'accès privilégié aux couches Vecteur Tuilé Géoportail](#Utilisation-de-laccès-privilégié-aux-couches-vecteur-tuilé-géoportail) + - [Exemple d'utilisation](#exemple-dutilisation-4) + - [Utilisation directe de la librairie iTowns](#utilisation-directe-de-la-librairie-itowns-1) + - [Exemple d'utilisation](#exemple-dutilisation-5) - [Affichage des couches MNT WMTS Géoportail pour affichage du relief](#affichage-des-couches-mnt-wmts-géoportail-pour-affichage-du-relief) - [Utilisation de l'accès privilégié aux couches WMTS Géoportail pour afficher un MNT](#utilisation-de-laccès-privilégié-aux-couches-wmts-géoportail-pour-afficher-un-mnt) - - [Exemple d'utilisation](#exemple-dutilisation-4) + - [Exemple d'utilisation](#exemple-dutilisation-6) - [Utilisation directe de la librairie iTowns](#utilisation-directe-de-la-librairie-itowns-2) - - [Exemple d'utilisation](#exemple-dutilisation-5) + - [Exemple d'utilisation](#exemple-dutilisation-7) - [Widget de gestion d'empilement des couches](#widget-de-gestion-dempilement-des-couches) - [Exemples d'utilisation](#exemples-dutilisation) - [Utilisation simple](#utilisation-simple) @@ -54,12 +59,14 @@ - [Exemples d'utilisation](#exemples-dutilisation-5) - [Utilisation simple](#utilisation-simple-5) - [Utilisation avancée](#utilisation-avancée) - - [Widget d'affichage des bâtiments en 3D](#widget-daffichage-des-bâtiments) + - [Widget d'affichage des bâtiments](#widget-daffichage-des-bâtiments) - [Exemples d'utilisation](#exemples-dutilisation-6) - [Utilisation simple](#utilisation-simple-6) - [Utilisation avancée](#utilisation-avancée-1) + + L'extension Géoportail pour iTowns étend la librairie 3D iTowns afin de proposer l'ajout de widgets au globe. Les fonctionnalités suivantes sont proposées en complément de la bibliothèque [iTowns](http://www.itowns-project.org/) : * [affichage des couches WMTS Géoportail](#WMTS) @@ -90,7 +97,7 @@ L'utilisation de l'extension Géoportail pour iTowns se fait via les étapes sui * [Configuration de l'accès à la plateforme Géoportail](#config) - +Une documentation technique (**jsdoc**), une **demo** et un **generateur de carte** sont disponibles [ici](https://ignf.github.io/geoportal-extensions/). @@ -115,6 +122,8 @@ L'extension Géoportail pour iTowns comprend l'arborescence de fichiers suivante Les scripts d'iTowns s'obtiennent sur [la page de téléchargement d'iTowns](https://github.com/iTowns/itowns/releases). +

(back to top)

+ #### Téléchargement direct @@ -123,6 +132,7 @@ Vous pouvez télécharger la dernière version de l'extension Géoportail pour i L'archive téléchargée (GpItowns.zip) comprend l'arborescence décrite ci-dessus. +

(back to top)

@@ -149,6 +159,8 @@ http://ignf.github.io/geoportal-extensions/itowns-latest/dist/GpPluginItowns-src http://ignf.github.io/geoportal-extensions/itowns-latest/dist/GpPluginItowns-src.css ``` +

(back to top)

+ ### Intégration dans une page web @@ -167,6 +179,8 @@ Intégrez l'extension géoportail pour iTowns dans votre page web classiquement ``` +

(back to top)

+ ### Configuration de l'accès à la plateforme Géoportail @@ -271,6 +285,8 @@ Clés multiples : Si vous devez utiliser plusieurs clés d'accès, il est possib **Cependant, en cas de clés multiples, le plus simple reste de directement entrer la clé spécifique à utiliser au niveau du paramètre "url" de la couche ou "apiKey" du widget.** +

(back to top)

+ #### Optimisation du chargement : configuration locale Vous pouvez améliorer le temps de chargement de votre page en mettant en cache sur votre plateforme la configuration associée à votre clef d'accès. Il vous suffit pour cela de récupérer le fichier de configuration (autoconf.json) obtenu à l'aide [du formulaire de ce tutoriel](http://ignf.github.io/geoportal-access-lib/latest/jsdoc/tutorial-optimize-getconfig.html). @@ -327,6 +343,8 @@ Votre utilisation des fonctionnalités de l'extension Géoportail sera alors sim ``` +

(back to top)

+ ### Appel de l'extension dans un module ES6 Le module de l'extension expose de multiples exports nommés (dont le module itowns étendu). @@ -366,6 +384,8 @@ const globeView = new It.GlobeViewExtended(...) Gp.Services.getConfig(...) ``` +

(back to top)

+ ## Compatibilités ### Versions d'iTowns supportées @@ -391,6 +411,9 @@ Opera | Versions récentes (19+) Le webGL est une technologie qui exploite l'accélération matérielle de la carte graphique de la machine de l'utilisateur. En fonction du matériel de l'utilisateur, iTowns et l'extension Géoportail pour iTowns pourront donc ne pas fonctionner. Sur [cette page](https://get.webgl.org/), il est possible de tester en fonction du navigateur et du matériel si le contexte webGL est accessible. + +

(back to top)

+ ## Fonctionnalités @@ -406,6 +429,8 @@ NB : * Les définitions des systèmes de coordonnées du registre IGN-F peuvent être trouvées [ici](https://geodesie.ign.fr/contenu/fichiers/IGNF.xml). +

(back to top)

+ ### Affichage des couches WMTS Géoportail @@ -584,6 +609,7 @@ var orthoLayer = { globeView.addLayer(orthoLayer); ``` +

(back to top)

@@ -679,6 +705,71 @@ var regionLayer{ globeView.addLayer(regionLayer); ``` +

(back to top)

+ + + +### Affichage des couches Vecteur Tuilé Géoportail + +Le modèle de données iTowns prend en entrée des couches matérialisées sous forme d'objet JavaScript. Deux moyens existent pour afficher les couches Vecteur Tuilé Géoportail. + +1 - Via l'accès privilégié aux couches Vecteur Tuilé Géoportail fourni par l'extension Géoportail pour iTowns. + +2 - Directement avec la librairie iTowns. Pour cela, il faut se référer à la [documentation d'iTowns pour l'ajout d'une couche](http://www.itowns-project.org/itowns/API_Doc/GlobeView.html#addLayer). + +#### Utilisation de l'accès privilégié aux couches Vecteur Tuilé Géoportail + +L'affichage se fait par la création d'une nouvelle instance de la classe [Itowns.layer.VectorTileLayer](http://ignf.github.io/geoportal-extensions/itowns-latest/jsdoc/itowns.layer.VectorTileLayer.html), de la manière suivante : + +``` javascript +new itowns.layer.VectorTileLayer(options); +``` +Cette fonction retourne un objet **itowns.layer.VectorTileLayer**, qui peut ainsi être interprété par la fonction addLayer de la librairie Itowns pour l'ajout dans la carte. + +Il est possible de surcharger le paramétrage par défaut de la couche en passant l'option "itownsParams" lors de la création de l'instance de la couche Géoportail Vecteur Tuilé. + +#### Exemple d'utilisation + +``` javascript +const globeView = new itowns.GlobeViewExtended(viewerDiv, positionOnGlobe); + +globeView.addLayer(new itowns.layer.VectorTileLayer({ + layer: "PLAN.IGN", + id : "MVT", + url: 'https://wxs.ign.fr/static/vectorTiles/styles/PLAN.IGN/standard.json', + itownsParams : { + opacity : 0.5 + } +})); +``` + +#### Utilisation directe de la librairie iTowns + +Il est possible d'ajouter une couche Vecteur Tuilé Géoportail (ou autre) en utilisant directement le paramétrage d'iTowns. Ci-après, un exemple d'utilisation. + +#### Exemple d'utilisation + +``` javascript +var view = new itowns.GlobeView(viewerDiv, placement); + +var mvtSource = new itowns.VectorTilesSource({ + style: 'https://wxs.ign.fr/static/vectorTiles/styles/PLAN.IGN/standard.json', + // application de filtres + filter: (layer) => !layer['source-layer'].includes('oro_') && !layer['source-layer'].includes('parcellaire'), +} + +var mvtLayer = new itowns.ColorLayer('MVT', { + source: mvtSource, + effect_type: itowns.colorLayerEffects.removeLightColor, + effect_parameter: 2.5, + addLabelLayer: true, +} + +view.addLayer(mvtLayer); +``` + +

(back to top)

+ ### Affichage des couches MNT WMTS Géoportail pour affichage du relief @@ -807,6 +898,7 @@ var MNTLayer = { globeView.addLayer(ElevationLayer); ``` +

(back to top)

@@ -876,6 +968,9 @@ globeView.addWidget(layerSwitcher); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/b01pLz3m/embedded/result,js,html,css/) + +

(back to top)

+ ### Coordonnées et altitude en un point de la carte @@ -918,6 +1013,9 @@ map.addControl(mpControl); **Exemple d'utilisation avec paramétrage des systèmes de coordonnées** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/tmjdezkq/embedded/result,js,html,css/) + +

(back to top)

+ ### Affichage dynamique des attributions @@ -955,6 +1053,8 @@ globeView.addWidget( attribution ); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/r3or3tz9/embedded/result,js,html,css/) +

(back to top)

+ ### Affichage d'une mini-vue dynamique @@ -988,6 +1088,8 @@ globeView.addWidget( miniglobe ); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/xfq98cr1/embedded/result,js,html,css/) +

(back to top)

+ ### Affichage d'une échelle graphique @@ -1021,8 +1123,7 @@ globeView.addWidget( scalebar ); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/xwodbsfp/embedded/result,js,html,css/) - ------------------------------------------ +

(back to top)

@@ -1069,7 +1170,6 @@ globeView.addWidget( boostrelief ); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/1no0hyp2/embedded/result,js,html,css/) - ##### Utilisation avancée Ajout du widget en paramétrant des coéfficients minimum et maximum, et un pas spécifique pour le slider d'éxagération @@ -1107,7 +1207,7 @@ globeView.addWidget( boostrelief ); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/szj1n62k/embedded/result,js,html,css/) ------------------------------------------ +

(back to top)

@@ -1167,3 +1267,5 @@ globeView.addWidget( buildings ); ``` **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/2vLfh0rq/embedded/result,js,html,css/) + +

(back to top)

\ No newline at end of file diff --git a/doc/README-leaflet.md b/doc/README-leaflet.md index 5feebb634..288e042b3 100644 --- a/doc/README-leaflet.md +++ b/doc/README-leaflet.md @@ -1,6 +1,6 @@ # Extension Géoportail pour Leaflet -[![release](https://img.shields.io/badge/release%20-leaflet%202.2.4-brightgreen.svg?style=flat)](https://github.com/IGNF/geoportal-extensions/releases/tag/leaflet-2.2.4) +![GitHub package.json version](https://img.shields.io/github/package-json/v/IGNF/geoportal-extensions?filename=build%2Fscripts%2Frelease%2Fpackage-leaflet.json) @@ -51,6 +51,8 @@ + + L'extension Géoportail pour Leaflet propose les fonctionnalités suivantes à utiliser en complément de la biblothèque [Leaflet](http://leafletjs.com/) : * [affichage des couches WMTS Géoportail](#WMTS) @@ -82,7 +84,7 @@ L'utilisation de l'extension Géoportail pour Leaflet se fait via les étapes su * [Configuration de l'accès à la plateforme Géoportail](#config) - +Une documentation technique (**jsdoc**), une **demo** et un **generateur de carte** sont disponibles [ici](https://ignf.github.io/geoportal-extensions/). @@ -92,9 +94,7 @@ Vous pouvez récupérer l'extension Géoportail pour Leaflet soit par [télécha L'extension Géoportail pour Leaflet comprend l'arborescence de fichiers suivante : - ``` - / GpPluginLeaflet.js (version minifiée du code javascript pour une utilisation en production) GpPluginLeaflet.css (version minifiée des css pour une utilisation en production) GpPluginLeaflet-src.js (version non minifiée du code javascript pour une utilisation en développement) @@ -103,6 +103,8 @@ L'extension Géoportail pour Leaflet comprend l'arborescence de fichiers suivant Les scripts de Leaflet s'obtiennent sur [la page de téléchargement de Leaflet](http://leafletjs.com/download.html). +

(back to top)

+ #### Téléchargement direct @@ -111,6 +113,7 @@ Vous pouvez télécharger la dernière version de l'extension Géoportail pour L L'archive téléchargée (GpLeaflet.zip) comprend l'arborescence décrite ci-dessus. +

(back to top)

@@ -126,7 +129,6 @@ npm i geoportal-extensions-leaflet L'arborescence décrite ci-dessus sera alors accessible dans le répertoire `node_modules/geoportal-extensions-leaflet/dist/` de votre projet. - #### Accès direct Vous pouvez aussi choisir d'utiliser des fichiers hébergés en ligne, pour y accéder directement, lors de vos tests par exemple. Cependant, pour une utilisation en production, nous vous conseillons de télécharger ces fichiers et de les héberger vous-même, sur le même serveur qui héberge votre application. @@ -138,6 +140,8 @@ http://ignf.github.io/geoportal-extensions/leaflet-latest/dist/GpPluginLeaflet-s http://ignf.github.io/geoportal-extensions/leaflet-latest/dist/GpPluginLeaflet-src.css ``` +

(back to top)

+ ### Intégration dans une page web @@ -156,6 +160,8 @@ Intégrez l'extension géoportail pour leaflet dans votre page web classiquement ``` +

(back to top)

+ ### Configuration de l'accès à la plateforme Géoportail @@ -254,6 +260,8 @@ Clés multiples : Si vous devez utiliser plusieurs clés d'accès, il est possib ``` +

(back to top)

+ #### Optimisation du chargement : configuration locale Vous pouvez améliorer le temps de chargement de votre page en mettant en cache sur votre plateforme la configuration associée à votre clef d'accès. Il vous suffit pour cela de récupérer le fichier de configuration (autoconf.json) obtenu à l'aide [du formulaire de ce tutoriel](http://ignf.github.io/geoportal-access-lib/latest/jsdoc/tutorial-optimize-getconfig.html). @@ -310,6 +318,8 @@ Votre utilisation des fonctionnalités de l'extension Géoportail sera alors sim ``` +

(back to top)

+ ### Appel de l'extension dans un module ES6 Le module de l'extension expose de multiples exports nommés (dont le module leaflet étendu). @@ -349,6 +359,8 @@ var map = L.map(...) Gp.Services.getConfig(...) ``` +

(back to top)

+ ## Compatibilités ### Versions de Leaflet supportées @@ -376,6 +388,7 @@ Firefox | Versions récentes (28+) Edge | 12+ Safari | Versions récentes (6.1+) +

(back to top)

## Fonctionnalités @@ -423,6 +436,7 @@ L'extension Géoportail pour Leaflet définit par défaut la projection légale L.geoportalCRS.EPSG2154 ``` +

(back to top)

@@ -460,6 +474,8 @@ lyr.addTo(map); // ou map.addLayer(lyr); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/nqz6xmpa/embedded/result,js,html,css/) +

(back to top)

+ #### Affichage en Lambert 93 (EPSG:2154) La plateforme Géoportail diffuse aussi des ressources WMTS en projection Lambert 93. Pour permettre de les afficher, l'extension Géoportail pour Leaflet pré-définit la projection correspondante accessible via la constante : @@ -489,6 +505,7 @@ lyr.addTo(map); // ou map.addLayer(lyr); NB : D'autres systèmes de coordonnées peuvent être définis : [plus d'informations...](#crs) +

(back to top)

@@ -522,6 +539,7 @@ L.geoportalLayer.WMS({ **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/d9402Lba/embedded/result,js,html,css/) +

(back to top)

@@ -601,6 +619,8 @@ map.addControl( **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/0t1nLra7/embedded/result,js,html,css/) +

(back to top)

+ ### Barre de recherche @@ -640,6 +660,8 @@ map.addControl(searchCtrl); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/uLokwebc/embedded/result,js,html,css/) +

(back to top)

+ ### Adresse ou lieu en un point de la carte @@ -677,6 +699,8 @@ map.addControl(revCtrl); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/7tohyehs/embedded/result,js,html,css/) +

(back to top)

+ ### Calculs d'itinéraires @@ -714,6 +738,8 @@ map.addControl(routeCtrl); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/s30zo9eo/embedded/result,js,html,css/) +

(back to top)

+ ### Calculs d'isochrones / isodistances @@ -751,6 +777,8 @@ map.addControl(isoCtrl); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/z85j92hv/embedded/result,js,html,css/) +

(back to top)

+ ### Altitude en un point de la carte @@ -794,6 +822,8 @@ map.addControl(mpCtrl); **Exemple d'utilisation avec activation de l'édition de coordonnées pour localisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/jLcgeng9/embedded/result,js,html,css/) +

(back to top)

+ ### Profil altimétrique le long d'un traçé @@ -830,3 +860,5 @@ map.addControl(ep); ``` **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/L5ctL3nq/embedded/result,js,html,css/) + +

(back to top)

diff --git a/doc/README-openlayers.md b/doc/README-openlayers.md index 21a3f926c..39d2d0321 100644 --- a/doc/README-openlayers.md +++ b/doc/README-openlayers.md @@ -1,6 +1,6 @@ # Extension Géoportail pour OpenLayers -[![release](https://img.shields.io/badge/release%20-ol%203.2.11-brightgreen.svg?style=flat)](https://github.com/IGNF/geoportal-extensions/releases/tag/ol-3.2.11) +![GitHub package.json version](https://img.shields.io/github/package-json/v/IGNF/geoportal-extensions?filename=build%2Fscripts%2Frelease%2Fpackage-openlayers.json) @@ -31,6 +31,7 @@ - [Exemple d'utilisation](#exemple-dutilisation-2) - [Utilisation d'une source WMS Géoportail](#utilisation-dune-source-wms-géoportail) - [Exemple d'utilisation](#exemple-dutilisation-3) + - [Affichage d'une couche Vecteur Tuilé Géoportail](#affichage-dune-couche-vecteur-tuilé-géoportail) - [Widget de gestion d'empilement des couches](#widget-de-gestion-dempilement-des-couches) - [Exemples d'utilisation](#exemples-dutilisation) - [Utilisation simple](#utilisation-simple) @@ -70,12 +71,16 @@ + + L'extension Géoportail pour OpenLayers propose les fonctionnalités suivantes à utiliser en complément de la bibliothèque [OpenLayers dans ses versions 3 et supérieures](https://openlayers.org/) : * [affichage des couches WMTS Géoportail](#WMTS) * [affichage des couches WMS Géoportail](#WMS) +* [affichage d'une couche Vecteur Tuilé Géoportail](#VT) + * [affichage dynamique des attributions](#attributions) * [widget de gestion d'empilement des couches](#layerswitcher) @@ -111,7 +116,7 @@ L'utilisation de l'extension Géoportail pour OpenLayers se fait via les étapes * [Configuration de l'accès à la plateforme Géoportail](#config) - +Une documentation technique (**jsdoc**), une **demo** et un **generateur de carte** sont disponibles [ici](https://ignf.github.io/geoportal-extensions/). @@ -135,6 +140,7 @@ L'extension Géoportail pour OpenLayers comprend l'arborescence de fichiers suiv Les scripts d'OpenLayers s'obtiennent sur [la page de téléchargement d'OpenLayers](https://openlayers.org/download/). +

(back to top)

@@ -144,6 +150,7 @@ Vous pouvez télécharger la dernière version de l'extension Géoportail pour O L'archive téléchargée (.zip) comprend l'arborescence décrite ci-dessus. +

(back to top)

@@ -171,6 +178,8 @@ http://ignf.github.io/geoportal-extensions/openlayers-latest/dist/GpPluginOpenLa http://ignf.github.io/geoportal-extensions/openlayers-latest/dist/GpPluginOpenLayers-src.css ``` +

(back to top)

+ ### Intégration dans une page web @@ -189,6 +198,8 @@ Intégrez l'extension géoportail pour OpenLayers dans votre page web classiquem ``` +

(back to top)

+ ### Configuration de l'accès à la plateforme Géoportail @@ -286,6 +297,8 @@ Clés multiples : Si vous devez utiliser plusieurs clés d'accès, il est possib ``` +

(back to top)

+ #### Optimisation du chargement : configuration locale Vous pouvez améliorer le temps de chargement de votre page en mettant en cache sur votre plateforme la configuration associée à votre clef d'accès. Il vous suffit pour cela de récupérer le fichier de configuration (autoconf.json) obtenu à l'aide [du formulaire de ce tutoriel](http://ignf.github.io/geoportal-access-lib/latest/jsdoc/tutorial-optimize-getconfig.html). @@ -341,6 +354,8 @@ Votre utilisation des fonctionnalités de l'extension Géoportail sera alors sim ``` +

(back to top)

+ ### Appel de l'extension dans un module ES6 Le module de l'extension expose de multiples exports nommés (dont le module openlayers étendu). @@ -380,6 +395,8 @@ var map = new Ol.Map(...) Gp.Services.getConfig(...) ``` +

(back to top)

+ ## Compatibilités ### Versions de OpenLayers supportées @@ -404,6 +421,8 @@ Edge | 12+ Safari | Versions récentes (6.1+) +

(back to top)

+ ## Fonctionnalités @@ -452,6 +471,7 @@ NB : * Les définitions des systèmes de coordonnées du registre IGN-F peuvent être trouvées [ici](https://geodesie.ign.fr/contenu/fichiers/IGNF.xml). +

(back to top)

@@ -498,6 +518,8 @@ var map = new ol.Map({ **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/j5rdjt2z/embedded/result,js,html,css/) +

(back to top)

+ ##### Affichage en Lambert 93 (EPSG:2154) La plateforme Géoportail diffuse aussi des ressources WMTS en projection Lambert 93. Pour permettre de les afficher, l'extension Géoportail pour OpenLayers pré-définit l'alias "EPSG:2154" correspondant à cette projection. @@ -525,6 +547,8 @@ var map = new ol.Map({ NB : D'autres systèmes de coordonnées peuvent être définis et utilisés : [plus d'informations...](#crs) +

(back to top)

+ #### Utilisation d'une source WMTS Géoportail @@ -567,6 +591,8 @@ var map = new ol.Map({ **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/sdktaf9r/embedded/result,js,html,css/) +

(back to top)

+ ##### Affichage en Lambert 93 (EPSG:2154) La plateforme Géoportail diffuse aussi des ressources WMTS en projection Lambert 93. Pour permettre de les afficher, l'extension Géoportail pour OpenLayers pré-définit l'alias "EPSG:2154" correspondant à cette projection. @@ -597,6 +623,7 @@ var map = new ol.Map({ NB : D'autres systèmes de coordonnées peuvent être définis et utilisés : [plus d'informations...](#crs) +

(back to top)

@@ -644,6 +671,8 @@ var map = new ol.Map({ **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/jnfwc7k6/embedded/result,js,html,css/) +

(back to top)

+ #### Utilisation d'une source WMS Géoportail @@ -686,6 +715,58 @@ var map = new ol.Map({ **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/e36ur78k/embedded/result,js,html,css/) +

(back to top)

+ + + +### Affichage d'une couche Vecteur Tuilé Géoportail + +L'affichage se fait par la création d'une nouvelle instance de la classe [ol.layer.GeoportalMapBox](http://ignf.github.io/geoportal-extensions/ol-latest/jsdoc/ol.layer.GeoportalMapBox.html), de la manière suivante : + +``` javascript +new ol.layer.GeoportalMapBox(options); +``` + +Cette fonction retourne un objet **ol.layer.GeoportalMapBox**, qui hérite de l'objet OpenLayers *ol.layer.VectorTile*, qui peut ainsi être interprété par la librairie OpenLayers pour l'ajout dans la carte. + +**Exemple d'utilisation:** + +Affichage de la couche *PLAN.IGN* du Géoportail avec le style *classique* sur une carte en EPSG:4326. + +``` javascript +var map = new ol.Map({ + target: 'map', + layers: [ + new ol.layer.GeoportalMapBox({ + layer : "PLAN.IGN", + style : "classique" + }) + ], + view: new ol.View({ + center: [2, 46], + zoom: 12, + projection: "EPSG:4326" + }) +}); +``` + +Il est possible d'y ajouter des options : + +``` javascript +var LayerMapBox = new ol.layer.GeoportalMapBox({ + layer : "PLAN.IGN", + style : "classique", + source : "plan_ign", // cas de plusieurs sources + ssl: true +}, { + opacity: 0.7, + visible: true, + declutter: true + ... +}); +``` + +

(back to top)

@@ -753,6 +834,8 @@ map.addControl(lsControl); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/5f9wxsof/embedded/result,js,html,css/) +

(back to top)

+ ### Barre de recherche @@ -799,6 +882,7 @@ map.addControl(searchControl); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/qpcyp8nr/embedded/result,js,html,css/) +

(back to top)

@@ -844,6 +928,8 @@ map.addControl(routeControl); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/1ngLrhuj/embedded/result,js,html,css/) +

(back to top)

+ ### Calculs d'isochrones / isodistances @@ -888,6 +974,8 @@ map.addControl(isoControl); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/jpwf385t/embedded/result,js,html,css/) +

(back to top)

+ ### Coordonnées et altitude en un point de la carte @@ -937,6 +1025,8 @@ map.addControl(mpControl); **Exemple d'utilisation avec activation de l'édition de coordonnées pour localisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/jrL59w29/embedded/result,js,html,css/) +

(back to top)

+ ### Affichage dynamique des attributions @@ -983,6 +1073,8 @@ map.addControl(attControl); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/x1jrLavb/embedded/result,js,html,css/) +

(back to top)

+ ### Adresse ou lieu en un point de la carte @@ -1027,6 +1119,8 @@ map.addControl(rvControl); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/9y6dgq15/embedded/result,js,html,css/) +

(back to top)

+ ### Outils de croquis @@ -1071,6 +1165,7 @@ map.addControl(drawControl); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/2Lj85jf1/embedded/result,js,html,css/) +

(back to top)

@@ -1117,6 +1212,7 @@ map.addControl(lyrImport); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/u04nvno2/embedded/result,js,html,css/) +

(back to top)

@@ -1163,6 +1259,8 @@ map.addControl(ep); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/cwfsLge7/embedded/result,js,html,css/) +

(back to top)

+ ### Outils de mesures @@ -1208,6 +1306,7 @@ map.addControl(length); **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/cwfsLge7/embedded/result,js,html,css/) +

(back to top)

@@ -1263,3 +1362,5 @@ map.addControl(getfeatureinfo); ``` **Exemple d'utilisation** [![jsFiddle](https://jsfiddle.net/img/embeddable/logo-dark.png)](https://jsfiddle.net/ignfgeoportail/vg6dz7bn/embedded/result,js,html,css/) + +

(back to top)

\ No newline at end of file diff --git a/package.json b/package.json index 866dd257e..a2c323d98 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "geoportal-extensions", "description": "French Geoportal Extensions for OpenLayers, Leaflet and iTowns libraries", - "version": "2.7.4", - "date": "21/02/2023", + "version": "2.7.5", + "date": "23/03/2023", "leafletExtName": "French Geoportal Extension for Leaflet", "leafletExtVersion": "2.2.9", "olExtName": "French Geoportal Extension for OpenLayers", - "olExtVersion": "3.2.20", + "olExtVersion": "3.2.21", "itownsExtName": "French Geoportal Extension for Itowns", "itownsExtVersion": "2.3.10", "main": "dist/leaflet/GpPluginLeaflet.js, dist/openlayers/GpPluginOpenLayers.js, dist/itowns/GpPluginItowns.js", diff --git a/samples-src/pages/openlayers/Editor/pages-ol-editor-bundle-multi-editor.html b/samples-src/pages/openlayers/Editor/pages-ol-editor-bundle-multi-editor.html index b2180321f..f36337d11 100644 --- a/samples-src/pages/openlayers/Editor/pages-ol-editor-bundle-multi-editor.html +++ b/samples-src/pages/openlayers/Editor/pages-ol-editor-bundle-multi-editor.html @@ -180,8 +180,8 @@

editor-3:


}, pin : false, type : false, - filter : false, - style : false, + filter : true, + style : true, legend : true, group : true, editable : false diff --git a/samples-src/pages/openlayers/Editor/pages-ol-legends-bundle-test.html b/samples-src/pages/openlayers/Editor/pages-ol-legends-bundle-test.html index 3ead3027e..72ed985cd 100644 --- a/samples-src/pages/openlayers/Editor/pages-ol-legends-bundle-test.html +++ b/samples-src/pages/openlayers/Editor/pages-ol-legends-bundle-test.html @@ -9,47 +9,265 @@ body { margin: unset; } + table { + width: 100%; + } + .disable { + color: darkgray; + } + .use { + font-weight: bold; + } + .tg {border-collapse:collapse;border-spacing:0;} + .tg td{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px; + overflow:hidden;padding:10px 10px;word-break:normal;} + .tg th{border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px; + font-weight:normal;overflow:hidden;padding:10px 10px;word-break:normal;} + .tg .tg-0pky{border-color:inherit;text-align:left;vertical-align:top} {{/content}} {{#content "body"}}

Ajout du widget d'édition des legendes MapBox

- - -
+ +
+ + + + + + + + + + + + + + + + + + + +
linefillcirclesymbol:iconsymbol:textbackground
+
    +
  • paint/line-blur
  • +
  • layout/line-cap
  • +
  • paint/line-color
  • +
  • paint/line-dasharray
  • +
  • paint/line-gap-width
  • +
  • paint/line-gradient
  • +
  • layout/line-join
  • +
  • layout/line-miter-limit
  • +
  • paint/line-offset
  • +
  • paint/line-opacity
  • +
  • paint/line-pattern
  • +
  • layout/line-round-limit
  • +
  • layout/line-sort-key
  • +
  • paint/line-translate
  • +
  • paint/line-translate-anchor
  • +
  • paint/line-trim-offset
  • +
  • paint/line-width
  • +
  • layout/visibility
  • +
+
+
    +
  • paint/fill-antialias
  • +
  • paint/fill-color
  • +
  • paint/fill-opacity
  • +
  • paint/fill-outline-color
  • +
  • paint/fill-pattern
  • +
  • layout/fill-sort-key
  • +
  • paint/fill-translate
  • +
  • paint/fill-translate-anchor
  • +
  • layout/visibility
  • +
+
+
    +
  • paint/circle-blur
  • +
  • paint/circle-color
  • +
  • paint/circle-opacity
  • +
  • paint/circle-pitch-alignment
  • +
  • paint/circle-pitch-scale
  • +
  • paint/circle-radius
  • +
  • layout/circle-sort-key
  • +
  • paint/circle-stroke-color
  • +
  • paint/circle-stroke-opacity
  • +
  • paint/circle-stroke-width
  • +
  • paint/circle-translate
  • +
  • paint/circle-translate-anchor
  • +
  • layout/visibility
  • +
+
+
    +
  • layout/icon-allow-overlap
  • +
  • layout/icon-anchor
  • +
  • paint/icon-color
  • +
  • paint/icon-halo-blur
  • +
  • paint/icon-halo-color
  • +
  • paint/icon-halo-width
  • +
  • layout/icon-ignore-placement
  • +
  • layout/icon-image
  • +
  • layout/icon-keep-upright
  • +
  • layout/icon-offset
  • +
  • paint/icon-opacity
  • +
  • layout/icon-optional
  • +
  • layout/icon-padding
  • +
  • layout/icon-pitch-alignment
  • +
  • layout/icon-rotate
  • +
  • layout/icon-rotation-alignment
  • +
  • layout/icon-size
  • +
  • layout/icon-text-fit
  • +
  • layout/icon-text-fit-padding
  • +
  • paint/icon-translate
  • +
  • paint/icon-translate-anchor
  • +
+
+
    +
  • layout/text-allow-overlap
  • +
  • layout/text-anchor
  • +
  • paint/text-color
  • +
  • layout/text-field
  • +
  • layout/text-font
  • +
  • paint/text-halo-blur
  • +
  • paint/text-halo-color
  • +
  • paint/text-halo-width
  • +
  • layout/text-ignore-placement
  • +
  • layout/text-justify
  • +
  • layout/text-keep-upright
  • +
  • layout/text-letter-spacing
  • +
  • layout/text-line-height
  • +
  • layout/text-max-angle
  • +
  • layout/text-max-width
  • +
  • layout/text-offset
  • +
  • paint/text-opacity
  • +
  • layout/text-optional
  • +
  • layout/text-padding
  • +
  • layout/text-pitch-alignment
  • +
  • layout/text-radial-offset
  • +
  • layout/text-rotate
  • +
  • layout/text-rotation-alignment
  • +
  • layout/text-size
  • +
  • layout/text-transform
  • +
  • paint/text-translate
  • +
  • paint/text-translate-anchor
  • +
  • layout/text-variable-anchor
  • +
  • layout/ext-writing-mode
  • +
+
+
    +
  • paint/background-color
  • +
  • paint/background-opacity
  • +
  • paint/background-pattern
  • +
  • layout/visibility
  • +
+
+
+
+

cf. Spécifications

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
linefillcirclesymbol:iconsymbol:textbackground
+
{{/content}} {{#content "js"}} {{/content}} diff --git a/samples-src/pages/openlayers/Export/pages-ol-export-bundle-default.html b/samples-src/pages/openlayers/Export/pages-ol-export-bundle-default.html new file mode 100644 index 000000000..d11fc7655 --- /dev/null +++ b/samples-src/pages/openlayers/Export/pages-ol-export-bundle-default.html @@ -0,0 +1,112 @@ +{{#extend "ol-sample-bundle-layout"}} + +{{#content "head"}} + Sample openlayers +{{/content}} + +{{#content "style"}} + +{{/content}} + +{{#content "body"}} +

Ajout du widget Export

+ +
+
+
+
+{{/content}} + +{{#content "js"}} + +{{/content}} +{{/extend}} diff --git a/samples-src/pages/openlayers/Export/pages-ol-export-bundle-pluggable.html b/samples-src/pages/openlayers/Export/pages-ol-export-bundle-pluggable.html new file mode 100644 index 000000000..bf6245a99 --- /dev/null +++ b/samples-src/pages/openlayers/Export/pages-ol-export-bundle-pluggable.html @@ -0,0 +1,103 @@ +{{#extend "ol-sample-bundle-layout"}} + +{{#content "head"}} + Sample openlayers +{{/content}} + +{{#content "style"}} + +{{/content}} + +{{#content "body"}} +

Ajout du widget Export

+ +
+
+
+
+{{/content}} + +{{#content "js"}} + +{{/content}} +{{/extend}} diff --git a/samples-src/pages/openlayers/Export/pages-ol-export-bundle-render.html b/samples-src/pages/openlayers/Export/pages-ol-export-bundle-render.html new file mode 100644 index 000000000..756a7bfa8 --- /dev/null +++ b/samples-src/pages/openlayers/Export/pages-ol-export-bundle-render.html @@ -0,0 +1,112 @@ +{{#extend "ol-sample-bundle-layout"}} + +{{#content "head"}} + Sample openlayers +{{/content}} + +{{#content "style"}} + +{{/content}} + +{{#content "body"}} +

Ajout du widget Export

+ +
+
+
+
+{{/content}} + +{{#content "js"}} + +{{/content}} +{{/extend}} diff --git a/samples-src/pages/openlayers/Formats/pages-ol-geojsonextended-bundle-default.html b/samples-src/pages/openlayers/Formats/pages-ol-geojsonextended-bundle-default.html index 045021a42..0f7a5dbc9 100644 --- a/samples-src/pages/openlayers/Formats/pages-ol-geojsonextended-bundle-default.html +++ b/samples-src/pages/openlayers/Formats/pages-ol-geojsonextended-bundle-default.html @@ -152,7 +152,33 @@

Ajout d'une couche GeoJSON

console.log("Impossible to export : no features found."); return result; } - var json = new ol.format.GeoJSONExtended({}); + var json = new ol.format.GeoJSONExtended({ + extensions : { + root : { + typestring : "string", + typenumber : 22, + typefloat : 1.22, + typeobject : { + typestringone : "string1", + typestringtwo : null + }, + typearray : ["item1", "item2"], + typearrayofobject : [ + { + typestringone : "string1", + typestringtwo : null + }, + { + typestringone : "string1", + typestringtwo : "string2" + }, + ], + typearrayofarray : [ + [1, 2], null, [1, 3], null, [1, 4] + ] + } + } + }); result = json.writeFeatures(layer.getSource().getFeatures(), { dataProjection : "EPSG:4326", diff --git a/samples-src/pages/openlayers/Formats/pages-ol-gpxextended-bundle-default.html b/samples-src/pages/openlayers/Formats/pages-ol-gpxextended-bundle-default.html index 5dd10f2e8..090ec93b9 100644 --- a/samples-src/pages/openlayers/Formats/pages-ol-gpxextended-bundle-default.html +++ b/samples-src/pages/openlayers/Formats/pages-ol-gpxextended-bundle-default.html @@ -37,6 +37,29 @@

Ajout d'une couche GPX

source : new ol.source.Vector({ url: _url, format: new ol.format.GPXExtended({ + extensions : { + typestring : "string", + typenumber : 22, + typefloat : 1.22, + typeobject : { + typestringone : "string1", + typestringtwo : null + }, + typearray : ["item1", "item2"], + typearrayofobject : [ + { + typestringone : "string1", + typestringtwo : null + }, + { + typestringone : "string1", + typestringtwo : "string2" + }, + ], + typearrayofarray : [ + [1, 2], null, [1, 3], null, [1, 4] + ] + }, readExtensions : function (feature, node) { console.warn(feature, node); }, @@ -125,7 +148,31 @@

Ajout d'une couche GPX

console.log("Impossible to export : no features found."); return result; } - var gpx = new ol.format.GPXExtended({}); + var gpx = new ol.format.GPXExtended({ + extensions : { + typestring : "string", + typenumber : 22, + typefloat : 1.22, + typeobject : { + typestringone : "string1", + typestringtwo : null + }, + typearray : ["item1", "item2"], + typearrayofobject : [ + { + typestringone : "string1", + typestringtwo : null + }, + { + typestringone : "string1", + typestringtwo : "string2" + }, + ], + typearrayofarray : [ + [1, 2], null, [1, 3], null, [1, 4] + ] + } + }); result = gpx.writeFeatures(layer.getSource().getFeatures(), { dataProjection : "EPSG:4326", diff --git a/samples-src/pages/openlayers/Formats/pages-ol-kmlextended-bundle-default.html b/samples-src/pages/openlayers/Formats/pages-ol-kmlextended-bundle-default.html index b747d0ef5..b75a06bbc 100644 --- a/samples-src/pages/openlayers/Formats/pages-ol-kmlextended-bundle-default.html +++ b/samples-src/pages/openlayers/Formats/pages-ol-kmlextended-bundle-default.html @@ -102,7 +102,32 @@

Ajout d'une couche KML

return result; } var kml = new ol.format.KMLExtended({ - writeStyles : true + writeStyles : true, + extensions : { + root : { + typestring : "string", + typenumber : 22, + typefloat : 1.22, + typeobject : { + typestringone : "string1", + typestringtwo : null + }, + typearray : ["item1", "item2"], + typearrayofobject : [ + { + typestringone : "string1", + typestringtwo : null + }, + { + typestringone : "string1", + typestringtwo : "string2" + }, + ], + typearrayofarray : [ + [1, 2], null, [1, 3], null, [1, 4] + ] + } + } }); result = kml.writeFeatures(layer.getSource().getFeatures(), { diff --git a/samples-src/pages/openlayers/Layers/pages-ol-layermapbox-bundle-default.html b/samples-src/pages/openlayers/Layers/pages-ol-layermapbox-bundle-default.html new file mode 100644 index 000000000..a040ae2c7 --- /dev/null +++ b/samples-src/pages/openlayers/Layers/pages-ol-layermapbox-bundle-default.html @@ -0,0 +1,58 @@ +{{#extend "ol-sample-bundle-layout"}} + +{{#content "head"}} + Sample openlayers +{{/content}} + +{{#content "style"}} + +{{/content}} + +{{#content "body"}} +

Ajout d'une couche MapBox

+ +
+
+{{/content}} + +{{#content "js"}} + +{{/content}} + +{{/extend}} diff --git a/src/Common/Controls/ExportDOM.js b/src/Common/Controls/ExportDOM.js new file mode 100644 index 000000000..b0cae9264 --- /dev/null +++ b/src/Common/Controls/ExportDOM.js @@ -0,0 +1,14 @@ +var ExportDOM = { + + /** + * Add uuid to the tag ID + * @param {String} id - id selector + * @returns {String} uid - id selector with an unique id + */ + _addUID : function (id) { + var uid = (this.uid) ? id + "-" + this.uid : id; + return uid; + } +}; + +export default ExportDOM; diff --git a/src/Itowns/Layer/VectorTileLayer.js b/src/Itowns/Layer/VectorTileLayer.js index 234133a90..22d86d4ed 100644 --- a/src/Itowns/Layer/VectorTileLayer.js +++ b/src/Itowns/Layer/VectorTileLayer.js @@ -13,10 +13,9 @@ var logger = Logger.getLogger("vectorTileLayer"); /** * @classdesc - * Geoportal WMTS source creation + * Geoportal VT source creation * * @constructor - * @private * @alias itowns.layer.VectorTileLayer * @param {Object} options - options for function call. * @param {String} options.id - id to give to the layer diff --git a/src/OpenLayers/CSS/Controls/Editor/GPeditorOpenLayers.css b/src/OpenLayers/CSS/Controls/Editor/GPeditorOpenLayers.css index 6b4d88b0f..e52ed2202 100644 --- a/src/OpenLayers/CSS/Controls/Editor/GPeditorOpenLayers.css +++ b/src/OpenLayers/CSS/Controls/Editor/GPeditorOpenLayers.css @@ -464,8 +464,8 @@ LEGEND .legend-background {} .legend-fill , .legend-line { - margin: auto; - padding: 5px; + /* margin: auto; + padding: 5px; */ } .legend-icon {} .legend-text {} diff --git a/src/OpenLayers/CSS/Controls/Export/GPexportOpenLayers.css b/src/OpenLayers/CSS/Controls/Export/GPexportOpenLayers.css new file mode 100644 index 000000000..b4e91831f --- /dev/null +++ b/src/OpenLayers/CSS/Controls/Export/GPexportOpenLayers.css @@ -0,0 +1,102 @@ +/* main container */ +div[id^=GPexportContainer-] { + padding: 5px; +} + +div[id^=GPexportContainer-] > input.GPinputSubmit { + color: white; +} + +/* bouton */ +input[id^=GPexportButton-] { + min-width: fit-content; + padding-left: 25px; + padding-right: 5px; + background-image: url("img/GPexportSave.svg"); + background-size: 25px 25px; + background-repeat: no-repeat; +} + +/* menu */ +.GPexportMenuHidden { + visibility: hidden; +} + +.GPexportMenuContainer { + position: relative; + display: inline-block; +} + +.GPexportMenuContent { + display: none; + position: absolute; + background-color: #f1f1f1; + min-width: 80px; + padding: 8px; + border-radius: 10px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; +} + +.GPexportMenuContent a:hover { background-color: #f1f1f1; } + +.GPexportMenuContainer:hover .GPexportMenuContent { display: block; } + +/* menu des formats */ +.container { + display: block; + position: relative; + padding-left: 15px; + margin-bottom: 5px; + cursor: pointer; + font-size: 14px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.container input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + +.checkmark { + position: absolute; + top: 0; + left: 0; + height: 12px; + width: 12px; + background-color: #eee; + border-radius: 50%; +} + +.container:hover input ~ .checkmark { + background-color: #ccc; +} + +.container input:checked ~ .checkmark { + background-color: #366291; +} + +.checkmark:after { + content: ""; + position: absolute; + display: none; +} + +.container input:checked ~ .checkmark:after { + display: block; +} + +.container .checkmark:after { + top: 4px; + left: 4px; + width: 4px; + height: 4px; + border-radius: 50%; + background: white; +} \ No newline at end of file diff --git a/src/OpenLayers/CSS/Controls/Export/img/GPexportSave.svg b/src/OpenLayers/CSS/Controls/Export/img/GPexportSave.svg new file mode 100644 index 000000000..8f5d0b8ac --- /dev/null +++ b/src/OpenLayers/CSS/Controls/Export/img/GPexportSave.svg @@ -0,0 +1,28 @@ + + + + diff --git a/src/OpenLayers/Controls/Drawing.js b/src/OpenLayers/Controls/Drawing.js index 762f5d5bb..065d5229f 100644 --- a/src/OpenLayers/Controls/Drawing.js +++ b/src/OpenLayers/Controls/Drawing.js @@ -181,11 +181,11 @@ var Drawing = (function (Control) { this._initialize(options); // init control DOM container - var container = this._container = this._initContainer(); + this._container = this._initContainer(); // call ol.control.Control constructor Control.call(this, { - element : container, + element : this._container, target : options.target, render : options.render }); @@ -563,6 +563,24 @@ var Drawing = (function (Control) { } }; + /** + * Get vector layer + * + * @returns {Object} layer - isocurve layer + */ + Drawing.prototype.getLayer = function () { + return this.layer; + }; + + /** + * Get container + * + * @returns {DOMElement} container + */ + Drawing.prototype.getContainer = function () { + return this._container; + }; + // ################################################################### // // ######################## initialize control ####################### // // ################################################################### // diff --git a/src/OpenLayers/Controls/Editor/Legend.js b/src/OpenLayers/Controls/Editor/Legend.js index 176c4dc20..cf7bf504a 100644 --- a/src/OpenLayers/Controls/Editor/Legend.js +++ b/src/OpenLayers/Controls/Editor/Legend.js @@ -79,6 +79,58 @@ function Legend (options) { */ Legend.prototype.constructor = Legend; +// ################################################################### // +// ########################## CONSTANTES ############################# // +// ################################################################### // + +/** + * List of supported properties + */ +Legend.PROPERTIES = { + line : [ + "line-color", + "line-dasharray", + "line-opacity", + "line-width" + ], + fill : [ + "fill-color", + "fill-opacity", + "fill-outline-color", + "fill-pattern" + ], + background : [ + "background-color", + "background-opacity", + "background-pattern" + ], + circle : [ + "circle-color", + "circle-opacity", + "circle-stroke-color", + "circle-stroke-opacity", + "circle-stroke-width" + ], + icon : [ + "icon-color", + "icon-image", + "icon-opacity", + "__icon-size" + ], + text : [ + "__text-anchor", + "text-color", + "text-field", + "__text-font", + "__text-opacity", + "__text-size" + ] +}; + +// ################################################################### // +// ########################## init methods ########################### // +// ################################################################### // + /** * Initialize component * (called by constructor) @@ -114,15 +166,14 @@ Legend.prototype._initialize = function () { var _editable = this.options.obj.editable; this.editable = (typeof _editable !== "undefined") ? _editable : false; - // liste des caractéristiques de la legende + // liste des caractéristiques de la legende par defaut this.legendRender = { type : "fill", values : { width : 1, stroke : "#FFFFFF", color : "#000000", - opacity : 1, - icon : null + opacity : 1 } }; @@ -172,85 +223,77 @@ Legend.prototype._initContainer = function () { var div = document.createElement("div"); div.className = this.name.container; + // INFO // on recherche les informations dans le tag 'paint' en priorité, mais pour // les icones ou textes, les informations peuvent se trouver aussi dans le tag 'layout'... - var _bfoundStyle = false; - var _style = {}; - if (_obj.paint && _obj.layout) { - _bfoundStyle = true; - Object.assign(_style, _obj.paint, _obj.layout); - } else if (_obj.paint) { - _bfoundStyle = true; - Object.assign(_style, _obj.paint); - } else if (_obj.layout) { - _bfoundStyle = true; - Object.assign(_style, _obj.layout); - } else { - _bfoundStyle = false; + // on fusionnne paint et layout par facilité + var style = Object.assign({}, _obj.paint, _obj.layout); + + // liste des properties mapbox + // ex. fill-color + var keys = Object.keys(style); + if (keys.length === 0) { + logger.info("tag 'paint' or 'layout' is empty !"); + return; } - if (_bfoundStyle) { - var keys = Object.keys(_style); - if (keys.length === 0) { - logger.info("tag 'paint' or 'layout' is empty !"); - } - - // FIXME - // - gestion de type plus complexe : texte avec/sans symbole ou symbole ! - // - pour les textes ou icones, les info peuvent être aussi dans le tag 'layout' ! - var params = {}; - var bFound = false; - for (var i = 0; i < keys.length; i++) { - var _key = keys[i]; - if (/fill-/.test(_key) || - /line-/.test(_key) || - /circle-/.test(_key) || - /background-/.test(_key) || - /text-/.test(_key) || // FIXME not yet implemented... - /icon-/.test(_key) // FIXME not yet implemented... - ) { - // style geré & trouvé - bFound = true; - - var _title = _obj.title || ""; - - // le type texte ou icone est difficile à trouver, on le gère en - // symbole... - var _type = _key.split("-")[0]; - if (_type === "text" || _type === "icon") { - _type = "symbol"; - } - - if (this._getValues(_type, _style)) { - params = { - edit : this.editable, - title : _title, - type : this.legendRender.type, - values : this.legendRender.values - }; - div.appendChild(this._createElementIconLegend(params)); - } - - // on stoppe la recherche - break; + // FIXME + // - gestion de type plus complexe : texte avec/sans symbole ou symbole ! + // - pour les textes ou icones, les info peuvent être aussi dans le tag 'layout' ! + + var params = {}; + var bFound = false; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + // recherche du type + // ex. fill + if (/fill-/.test(key) || + /line-/.test(key) || + /circle-/.test(key) || + /background-/.test(key) || + /text-/.test(key) || + /icon-/.test(key) + ) { + // style geré & trouvé + bFound = true; + + var title = _obj.title || ""; + + // INFO + // le type texte ou icone est difficile à trouver car les 2 types cohabitent, + // on le gère en symbole... + var type = key.split("-")[0]; + if (type === "text" || type === "icon") { + type = "symbol"; } - } - } - // legende avec un style indeterminé ou non géré !? - if (!bFound) { - if (this._getValues("fill", _style)) { + this.legendRender = this._getProperties(type, style); params = { edit : this.editable, - title : _obj.title || "", + title : title, type : this.legendRender.type, values : this.legendRender.values }; - div.appendChild(this._createElementIconLegend(params)); + + // on stoppe la recherche + break; } } + // legende avec un style indeterminé ou non géré !? + if (!bFound) { + // on prend la legende par defaut + params = { + edit : this.editable, + title : "", + type : this.legendRender.type, + values : this.legendRender.values + }; + div.appendChild(this._createElementIconLegend(params)); + logger.warn("legend type unknown, default legend used..."); + } + // ajout mode edition graphique de la legende this.toolscontainer = this._createElementEditionLegend(params); div.appendChild(this.toolscontainer); @@ -259,49 +302,22 @@ Legend.prototype._initContainer = function () { this.container = div; }; +// ################################################################### // +// ##################### private methods ############################# // +// ################################################################### // + /** -* ... +* Get properties supported * * @param {Object} type - fill, line, circle, text, icon... -* @param {Object} value - see example -* @returns {Boolean} - see this.legendRender +* @param {Object} values - raw values from the JSON file +* @returns {Object} - { type : (fill | line | circle | symbol), values : valuesSupported } * * @private * @example -* // type simple for fill, line or circle type: -* // "paint": { -* // "fill-color": "#2BB3E1" -* // } * -* // TODO type complexe : not yet implemented ! -* // "paint": { -* // "fill-color": [ -* // "match", -* // ["get","symbo"], -* // "ZONE_BOISEE","#A7DA81", -* // "ZONE_MANGROVE","#7E8AB5", -* // "#A7DA81" -* // ] -* // } -* -* // TODO other type complexe : not yet implemented ! -* // "paint": { -* // "fill-color": { -* // "base": 1, -* // "stops": [ -* // [ -* // 15.5, -* // "#f2eae2" -* // ], -* // [ -* // 16, -* // "#dfdbd7" -* // ] -* // ] -* // } -* // } -* -* // TODO symbol with text (1) / symbol without text (2) / text (3) +* // TODO +* // symbol with text (1) / symbol without text (2) / text (3) * // "layout":{ * // "icon-image":"{maki}-11", * // "text-font":[ @@ -326,88 +342,56 @@ Legend.prototype._initContainer = function () { * // }, * */ -Legend.prototype._getValues = function (type, value) { - logger.trace("_getValues():", type, value); - - var _color = null; // couleur remplissage - var _stroke = null; // couleur trait - var _width = null; // epaisseur - var _opacity = null; // opacité - var _icon = null; - - // cas particulier : determiner pour un symbole complexe +Legend.prototype._getProperties = function (type, values) { + // cas particulier du symbole complexe + // il existe plusieurs types pour un symbole : + // - text + // - icon + // - icon with text if (type === "symbol") { - // il existe 2 type de symbole : - // - texte - // - icone avec ou sans texte - var _textValue = value["text-field"]; - var _iconValue = value["icon-image"]; - type = (_textValue && _iconValue) ? "icon" : (_textValue) ? "text" : (_iconValue) ? "icon" : "unknow"; + var isTextValue = values["text-field"]; + var isIconValue = values["icon-image"]; + type = (isTextValue && isIconValue) ? "icon" : (isTextValue) ? "text" : (isIconValue) ? "icon" : "unknow"; if (type === "unknow") { - logger.warn("_getValues() - Type inconnu :", type, value); - // on force le type texte !? - type = "text"; + logger.warn("type unknow !?"); + return; } } - switch (type) { - case "line": - _color = this._extract(value["line-color"]); - _width = this._extract(value["line-width"]); - _opacity = this._extract(value["line-opacity"]); - break; - case "text": - // FIXME c'est plus complexe !? - _color = this._extract(value["text-color"]); - break; - case "icon": - var bfound = false; - if (value["icon-image"] && this.options.sprites && Object.keys(this.options.sprites).length) { - if (this.options.sprites.json && this.options.sprites.json[value["icon-image"]]) { - bfound = true; + var valuesSupported = {}; + for (const key in values) { + if (Object.hasOwnProperty.call(values, key)) { + const val = values[key]; + if (Legend.PROPERTIES[type].includes(key)) { + var prop = key.replace(type, "").slice(1); + var value = this._getValue(val); + if (value) { + // cas particulier des sprites + if (prop === "pattern" || prop === "image") { + if (!this.options.sprites || + !this.options.sprites.json || + !this.options.sprites.json[value]) { + var k = type + ":" + prop; + logger.warn("sprites mandatory for key ", k); + break; + } + } + valuesSupported[prop] = value; } - } - if (bfound) { - _icon = value["icon-image"]; } else { - // FIXME c'est plus complexe !? - _color = this._extract(value["icon-color"]); + logger.warn("property not supported : ", key); } - break; - case "circle": - _color = this._extract(value["circle-color"]); - _stroke = this._extract(value["circle-stroke-color"]); - _opacity = this._extract(value["circle-opacity"]); - _width = this._extract(value["circle-stroke-width"]); - break; - case "background": - _color = this._extract(value["background-color"]); - break; - case "fill": - _color = this._extract(value["fill-color"]); - _opacity = this._extract(value["fill-opacity"]); - break; - default: - // return false; + } } - // save - this.legendRender = { + return { type : type, - values : { - color : _color || this.legendRender.values.color, - stroke : _stroke || this.legendRender.values.stroke, - width : _width || this.legendRender.values.width, - opacity : _opacity || this.legendRender.values.opacity, - icon : _icon - } + values : valuesSupported }; - - return true; }; /** -* ... +* Render thumbnail (SVG) * * @param {Object} type - fill, line, circle, text, ... * @param {Object} values - {"color":..., "width":..., "stroke":...., "opacity":...} @@ -417,7 +401,7 @@ Legend.prototype._getValues = function (type, value) { * @example * (...) */ -Legend.prototype._setValues = function (type, values) { +Legend.prototype._renderThumbnail = function (type, values) { // div de rendu de la legende var div = this.rendercontainer; @@ -425,118 +409,187 @@ Legend.prototype._setValues = function (type, values) { return false; } - // les valeurs - var _color = values.color || this.legendRender.values.color; // couleur remplissage - var _stroke = values.stroke || this.legendRender.values.stroke; // couleur trait - var _width = values.width || this.legendRender.values.width; // epaisseur - var _opacity = values.opacity || this.legendRender.values.opacity; // opacité - var _icon = values.icon || this.legendRender.values.icon; // nom de l'icone - var _style = ""; - // SVG var svg = null; // facteur grossissement (x10) pour le trait var factor = 3; + // valeur par defaut + if (!values.color) { + values.color = "#FFFFFF"; + } // en fonction du type, on y ajoute le style switch (type) { case "text": - _style = "font-size: 5em;font-weight: bold;"; + var styleText = "font-size: 5em;font-weight: bold;"; svg = "url(\"data:image/svg+xml;utf8, T \")"; div.style["background"] = svg - .replace("%color%", (_color.indexOf("rgb") === 0) ? _color : Color.hexToRgba(_color, 1)) - .replace("%opacity%", _opacity) - .replace("%style%", _style); + .replace("%color%", (values.color.indexOf("rgb") === 0) ? values.color : Color.hexToRgba(values.color, 1)) + .replace("%opacity%", values.opacity || 1) + .replace("%style%", styleText); break; case "icon": - if (_icon) { + if (values.image) { // FIXME on reste dans le paradigme d'utilisation du SVG..., // mais probleme de ratio de l'image !? - var template = ""; - svg = template - .replace("%x%", this.options.sprites.json[_icon].x) - .replace("%y%", this.options.sprites.json[_icon].y) - .replace(/%w%/g, this.options.sprites.json[_icon].width) - .replace(/%h%/g, this.options.sprites.json[_icon].height) + svg = "" + .replace("%x%", this.options.sprites.json[values.image].x) + .replace("%y%", this.options.sprites.json[values.image].y) + .replace(/%w%/g, this.options.sprites.json[values.image].width) + .replace(/%h%/g, this.options.sprites.json[values.image].height) .replace("%W%", this.options.sprites.size.w) .replace("%H%", this.options.sprites.size.h) .replace("%URL%", this.options.sprites.url); div.innerHTML = svg; } else { - _style = "fill: transparent;stroke-width: 10;"; + var styleTextIcon = "fill: transparent;stroke-width: 10;"; svg = "url(\"data:image/svg+xml;utf8,\")"; div.style["background"] = svg - .replace("%color%", (_color.indexOf("rgb") === 0) ? _color : Color.hexToRgba(_color, 1)) - .replace("%style%", _style); + .replace("%color%", (values.color.indexOf("rgb") === 0) ? values.color : Color.hexToRgba(values.color, 1)) + .replace("%style%", styleTextIcon); } break; - case "background": - div.style["background-color"] = _color; - break; case "line": - svg = "url(\"data:image/svg+xml;utf8,\")"; + var lstrockedasharray = (Array.isArray(values["dasharray"])) ? values["dasharray"].join(" ") : 0; + svg = "url(\"data:image/svg+xml;utf8,\")"; + // svg = "url(\"data:image/svg+xml;utf8,\")"; div.style["background"] = svg - .replace("%color%", (_color.indexOf("rgb") === 0) ? _color : Color.hexToRgba(_color, 1)) - .replace("%opacity%", _opacity) - .replace("%width%", _width * factor); + .replace("%color%", (values.color.indexOf("rgb") === 0) ? values.color : Color.hexToRgba(values.color, 1)) + .replace("%stroke-opacity%", values.opacity || 1) + .replace("%stroke-dasharray%", lstrockedasharray) + .replace("%stroke-width%", (values.width || 0) * factor); break; case "circle": - svg = "url(\"data:image/svg+xml;utf8,\")"; + var cstrockcolor = values["stroke-color"] || "#FFFFFF"; + svg = "url(\"data:image/svg+xml;utf8,\")"; div.style["background"] = svg - .replace("%color%", (_color.indexOf("rgb") === 0) ? _color : Color.hexToRgba(_color, 1)) - .replace("%opacity%", _opacity) - .replace("%stroke%", (_stroke.indexOf("rgb") === 0) ? _stroke : Color.hexToRgba(_stroke, 1)) - .replace("%width%", _width * factor); + .replace("%color%", (values.color.indexOf("rgb") === 0) ? values.color : Color.hexToRgba(values.color, 1)) + .replace("%opacity%", values.opacity || 1) + .replace("%stroke-color%", (cstrockcolor.indexOf("rgb") === 0) ? cstrockcolor : Color.hexToRgba(cstrockcolor, 1)) + .replace("%stroke-opacity%", values["stroke-opacity"] || 1) + .replace("%stroke-width%", (values["stroke-width"] || 0) * factor); break; + case "background": case "fill": - svg = "url(\"data:image/svg+xml;utf8,\")"; - div.style["background"] = svg - .replace("%color%", (_color.indexOf("rgb") === 0) ? _color : Color.hexToRgba(_color, 1)) - .replace("%opacity%", _opacity); + if (values.pattern) { + svg = "" + .replace("%x%", this.options.sprites.json[values.pattern].x) + .replace("%y%", this.options.sprites.json[values.pattern].y) + .replace(/%w%/g, this.options.sprites.json[values.pattern].width) + .replace(/%h%/g, this.options.sprites.json[values.pattern].height) + .replace("%W%", this.options.sprites.size.w) + .replace("%H%", this.options.sprites.size.h) + .replace("%URL%", this.options.sprites.url); + div.innerHTML = svg; + } else { + var fstrokecolor = values["outline-color"] || "#FFFFFF"; + svg = "url(\"data:image/svg+xml;utf8,\")"; + div.style["background"] = svg + .replace("%color%", (values.color.indexOf("rgb") === 0) ? values.color : Color.hexToRgba(values.color, 1)) + .replace("%opacity%", values.opacity || 1) + .replace("%stroke-color%", (fstrokecolor.indexOf("rgb") === 0) ? fstrokecolor : Color.hexToRgba(fstrokecolor, 1)); + } break; default: + logger.warn("type not found, no thumbnail..."); return false; } - // save - this.legendRender = { - type : type, - values : { - color : _color, - stroke : _stroke, - width : _width, - opacity : _opacity, - icon : _icon - } - }; - return true; }; /** - * ... + * Get value + * + * @param {*} value - value of a property (ex. "#2BB3E1") + * @returns {*} return a verified value (ex. color": "#2BB3E1") * - * @param {*} value - value - * @returns {String} extract value + * @private + * @example + * // type simple for fill, line or circle type with string : + * // "paint": { + * // "fill-color": "#2BB3E1" + * // } + * + * // type simple for fill, line or circle type with array : + * // "paint": { + * // "line-dasharray": [2,10] + * // } + * + * // TODO type complexe : not yet implemented ! + * // "paint": { + * // "fill-color": [ + * // "match", + * // ["get","symbo"], + * // "ZONE_BOISEE","#A7DA81", + * // "ZONE_MANGROVE","#7E8AB5", + * // "#A7DA81" + * // ] + * // } + * + * // other type complexe : + * // "paint": { + * // "fill-color": { + * // "base": 1, + * // "stops": [ + * // [ + * // 15.5, + * // "#f2eae2" + * // ], + * // [ + * // 16, + * // "#dfdbd7" + * // ] + * // ] + * // } + * // } */ -Legend.prototype._extract = function (value) { +Legend.prototype._getValue = function (value) { var result = null; if (typeof value === "string") { result = value; - } - if (Array.isArray(value)) { - result = null; - } - if (typeof value === "object") { + } else if (typeof value === "number") { + result = value; + } else if (Array.isArray(value)) { + // cas d'un tableau de valeurs numériques : [1,2,3] + var isNumber = true; + value.forEach(v => { + if (typeof v !== "number") { + isNumber = false; + } + }); + if (isNumber) { + result = value; + } + } else if (typeof value === "object") { result = null; if ("stops" in value) { + // on realise un ordre inversé sur les zooms + value.stops.sort((a, b) => { + var numA = a[0]; + var numB = b[0]; + if (numA > numB) { + return -1; + } + if (numA < numB) { + return 1; + } + return 0; + }); + // et, on prend le plus petit zoom var lastStopsValue = value.stops.slice(-1); result = lastStopsValue[0][1]; } + } else { + logger.warn("value not supported !"); } return result; }; +// ################################################################### // +// ######################### DOM methods ############################# // +// ################################################################### // + /** * Create a Graphical Legend Icon * @@ -554,7 +607,7 @@ Legend.prototype._extract = function (value) { * style="background: url("data:image/svg+xml;utf8,");"> * * vide... - +* */ Legend.prototype._createElementIconLegend = function (params) { // contexte @@ -579,45 +632,27 @@ Legend.prototype._createElementIconLegend = function (params) { } } - // couleur remplissage - var _color = params.values.color; - // couleur trait - var _stroke = params.values.stroke; - // epaisseur - var _width = params.values.width; - // opacité - var _opacity = params.values.opacity; - // type de legende - var _type = params.type; - - // si la couleur n'est pas definie, c'est que le type de syntaxe - // est non implementé ou non reconnu pour le moment... - if (!_color) { - // className - div.className += " legend-not-implemented"; + var type = params.type; + + // TODO className + // div.className += " legend-not-implemented"; + + // ajout du style sur la div de rendu + if (this._renderThumbnail(type, params.values)) { + // className possibles : + // " legend-text" + // " legend-icon" + // " legend-background" + // " legend-line" + // " legend-line-not-editable" + // " legend-circle" + // " legend-circle-not-editable" + // " legend-fill" + // " legend-fill-not-editable" + div.className += (params.edit) ? " legend-" + type : " legend-" + type + "-not-editable"; } else { - // ajout du style qur la div de rendu - if (this._setValues(_type, { - color : _color, - stroke : _stroke, - width : _width, - opacity : _opacity - })) { - // className possibles : - // " legend-text" - // " legend-icon" - // " legend-background" - // " legend-line" - // " legend-line-not-editable" - // " legend-circle" - // " legend-circle-not-editable" - // " legend-fill" - // " legend-fill-not-editable" - div.className += (params.edit) ? " legend-" + _type : " legend-" + _type + "-not-editable"; - } else { - div.className += " legend-unknow"; - } + div.className += " legend-unknow"; } container.appendChild(div); @@ -678,8 +713,23 @@ Legend.prototype._createElementEditionLegend = function (params) { // on ne traite que l'edition du mode 'traits' ou 'surfaciques' // mode 'line' - if (params.type === "line") { - // couleur du trait + switch (params.type) { + case "line": + createLineColor.call(self); + createLineWidth.call(self); + createLineOpacity.call(self); + break; + case "background": + case "fill": + createFillColor.call(self); + createFillOpacity.call(self); + break; + default: + break; + } + + // couleur du trait + function createLineColor () { var linecolor = document.createElement("div"); linecolor.className = "legend-styling-div"; var lLineColor = document.createElement("label"); @@ -695,24 +745,26 @@ Legend.prototype._createElementEditionLegend = function (params) { inputLineColor.setAttribute("data-id", "line-color"); if (inputLineColor.addEventListener) { inputLineColor.addEventListener("change", function (e) { - self._setValues(params.type, { + self._renderThumbnail(params.type, Object.assign(params.values, { color : e.target.value - }); + })); self.onChangeValueLegendMapBox(e); }); } else if (inputLineColor.attachEvent) { inputLineColor.attachEvent("onchange", function (e) { - self._setValues(params.type, { + self._renderThumbnail(params.type, Object.assign(params.values, { color : e.target.value - }); + })); self.onChangeValueLegendMapBox(e); }); } linecolor.appendChild(lLineColor); linecolor.appendChild(inputLineColor); container.appendChild(linecolor); + } - // epaisseur du trait + // epaisseur du trait + function createLineWidth () { var linewidth = document.createElement("div"); linewidth.className = "legend-styling-div"; var lLineWidth = document.createElement("label"); @@ -733,26 +785,28 @@ Legend.prototype._createElementEditionLegend = function (params) { inputLineWidth.addEventListener("change", function (e) { logger.trace(e); e.target.title = e.target.value; - self._setValues(params.type, { + self._renderThumbnail(params.type, Object.assign(params.values, { width : e.target.value - }); + })); self.onChangeValueLegendMapBox(e); }); } else if (inputLineWidth.attachEvent) { inputLineWidth.attachEvent("onchange", function (e) { logger.trace(e); e.target.title = e.target.value; - self._setValues(params.type, { + self._renderThumbnail(params.type, Object.assign(params.values, { width : e.target.value - }); + })); self.onChangeValueLegendMapBox(e); }); } linewidth.appendChild(lLineWidth); linewidth.appendChild(inputLineWidth); container.appendChild(linewidth); + } - // opacité du trait + // opacité du trait + function createLineOpacity () { var lineopacity = document.createElement("div"); lineopacity.className = "legend-styling-div"; var lLineOpacity = document.createElement("label"); @@ -773,18 +827,18 @@ Legend.prototype._createElementEditionLegend = function (params) { inputLineOpacity.addEventListener("change", function (e) { logger.trace(e); e.target.title = e.target.value; - self._setValues(params.type, { + self._renderThumbnail(params.type, Object.assign(params.values, { opacity : e.target.value - }); + })); self.onChangeValueLegendMapBox(e); }); } else if (inputLineOpacity.attachEvent) { inputLineOpacity.attachEvent("onchange", function (e) { logger.trace(e); e.target.title = e.target.value; - self._setValues(params.type, { + self._renderThumbnail(params.type, Object.assign(params.values, { opacity : e.target.value - }); + })); self.onChangeValueLegendMapBox(e); }); } @@ -792,9 +846,9 @@ Legend.prototype._createElementEditionLegend = function (params) { lineopacity.appendChild(inputLineOpacity); container.appendChild(lineopacity); } - // mode 'fill' - if (params.type === "fill") { - // couleur de remplissage + + // couleur de remplissage + function createFillColor () { var fillcolor = document.createElement("div"); fillcolor.className = "legend-styling-div"; var lFillColor = document.createElement("label"); @@ -810,24 +864,26 @@ Legend.prototype._createElementEditionLegend = function (params) { inputFillColor.setAttribute("data-id", "fill-color"); if (inputFillColor.addEventListener) { inputFillColor.addEventListener("change", function (e) { - self._setValues(params.type, { + self._renderThumbnail(params.type, Object.assign(params.values, { color : e.target.value - }); + })); self.onChangeValueLegendMapBox(e); }); } else if (inputFillColor.attachEvent) { inputFillColor.attachEvent("onchange", function (e) { - self._setValues(params.type, { + self._renderThumbnail(params.type, Object.assign(params.values, { color : e.target.value - }); + })); self.onChangeValueLegendMapBox(e); }); } fillcolor.appendChild(lFillColor); fillcolor.appendChild(inputFillColor); container.appendChild(fillcolor); + } - // opacité du remplissage + // opacité du remplissage + function createFillOpacity () { var fillopacity = document.createElement("div"); fillopacity.className = "legend-styling-div"; var lFillOpacity = document.createElement("label"); @@ -847,17 +903,17 @@ Legend.prototype._createElementEditionLegend = function (params) { if (inputFillOpacity.addEventListener) { inputFillOpacity.addEventListener("change", function (e) { e.target.title = e.target.value; - self._setValues(params.type, { + self._renderThumbnail(params.type, Object.assign(params.values, { opacity : e.target.value - }); + })); self.onChangeValueLegendMapBox(e); }); } else if (inputFillOpacity.attachEvent) { inputFillOpacity.attachEvent("onchange", function (e) { e.target.title = e.target.value; - self._setValues(params.type, { + self._renderThumbnail(params.type, Object.assign(params.values, { opacity : e.target.value - }); + })); self.onChangeValueLegendMapBox(e); }); } @@ -875,6 +931,7 @@ Legend.prototype._createElementEditionLegend = function (params) { /** * Add element into target DOM + * * @returns {Object} - Legend instance */ Legend.prototype.add = function () { diff --git a/src/OpenLayers/Controls/ElevationPath.js b/src/OpenLayers/Controls/ElevationPath.js index 65d699af0..4176ab8cf 100644 --- a/src/OpenLayers/Controls/ElevationPath.js +++ b/src/OpenLayers/Controls/ElevationPath.js @@ -30,10 +30,10 @@ import Interactions from "./Utils/Interactions"; import MeasureToolBox from "./MeasureToolBox"; import Measures from "./Measures/Measures"; import LayerSwitcher from "./LayerSwitcher"; +import ButtonExport from "./Export"; // DOM import ElevationPathDOM from "../../Common/Controls/ElevationPathDOM"; import ProfileElevationPathDOM from "../../Common/Controls/ProfileElevationPathDOM"; - var logger = Logger.getLogger("elevationpath"); /** @@ -49,6 +49,7 @@ var logger = Logger.getLogger("elevationpath"); * @param {String} [options.apiKey] - API key for services call (isocurve and autocomplete services), mandatory if autoconf service has not been charged in advance * @param {Boolean} [options.active = false] - specify if control should be actived at startup. Default is false. * @param {Boolean} [options.ssl = true] - use of ssl or not (default true, service requested using https protocol) + * @param {Boolean} [options.export = false] - Specify if button "Export" is displayed * @param {Object} [options.elevationPathOptions = {}] - elevation path service options. See {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~getAltitude Gp.Services.getAltitude()} for available options * @param {Object} [options.layerDescription = {}] - Layer informations to be displayed in LayerSwitcher widget (only if a LayerSwitcher is also added to the map) * @param {String} [options.layerDescription.title = "Profil altimétrique"] - Layer title to be displayed in LayerSwitcher @@ -71,9 +72,11 @@ var logger = Logger.getLogger("elevationpath"); * @fires elevationpath:drawstart * @fires elevationpath:drawend * @fires elevationpath:compute + * @fires export:compute * @example * * var measure = new ol.control.ElevationPath({ + * export : false, * stylesOptions : { * draw : { * finish : new ol.style.Stroke({ @@ -527,6 +530,20 @@ var ElevationPath = (function (Control) { if (!this.options.target) { MeasureToolBox.add(map, this); } + + // ajout d'un bouton d'export + if (this.options.export) { + var opts = Utils.assign({ control : this }, this.options.export); + this.export = new ButtonExport(opts); + this.export.render(); + var self = this; + this.export.on("export:compute", (e) => { + self.dispatchEvent({ + type : "export:compute", + content : e.content + }); + }); + } } // on appelle la méthode setMap originale d'OpenLayers @@ -577,6 +594,24 @@ var ElevationPath = (function (Control) { return this._data; }; + /** + * Get container + * + * @returns {DOMElement} container + */ + ElevationPath.prototype.getContainer = function () { + return this._container; + }; + + /** + * Get layer + * + * @returns {ol.layer.Vector} layer + */ + ElevationPath.prototype.getLayer = function () { + return this._measureVector; + }; + /** * clean */ @@ -597,46 +632,6 @@ var ElevationPath = (function (Control) { this._removeMeasureInteraction(map); }; - /** - * Remove measure - * @private - */ - ElevationPath.prototype._removeMeasure = function () { - // sketch - this._lastSketch = null; - this._currentSketch = null; - - if (this._measureSource) { - // marker - if (this._marker) { - this._measureSource.removeFeature(this._marker); - this._marker = null; - } - - // all other features - var _features = this._measureSource.getFeatures(); - for (var i = 0; i < _features.length; i++) { - this._measureSource.removeFeature(_features[i]); - } - } - }; - - /** - * Remove profile - * @private - */ - ElevationPath.prototype._removeProfile = function () { - // graph - this._profile = null; - - // on vide le container - if (this._profileContainer) { - while (this._profileContainer.firstChild) { - this._profileContainer.removeChild(this._profileContainer.firstChild); - } - } - }; - // ################################################################### // // ##################### init component ############################## // // ################################################################### // @@ -657,6 +652,7 @@ var ElevationPath = (function (Control) { render : null, active : false, apiKey : null, + export : false, elevationOptions : {}, layerDescription : { title : "Profil altimétrique", @@ -691,6 +687,9 @@ var ElevationPath = (function (Control) { // gestion de l'affichage du profil var _profile = options.displayProfileOptions || {}; + // bouton export + this.export = null; + // gestion de la fonction du profil var displayFunction = _profile.apply; this.options.displayProfileOptions.apply = (typeof displayFunction === "function") @@ -1543,6 +1542,46 @@ var ElevationPath = (function (Control) { } }; + /** + * Remove measure + * @private + */ + ElevationPath.prototype._removeMeasure = function () { + // sketch + this._lastSketch = null; + this._currentSketch = null; + + if (this._measureSource) { + // marker + if (this._marker) { + this._measureSource.removeFeature(this._marker); + this._marker = null; + } + + // all other features + var _features = this._measureSource.getFeatures(); + for (var i = 0; i < _features.length; i++) { + this._measureSource.removeFeature(_features[i]); + } + } + }; + + /** + * Remove profile + * @private + */ + ElevationPath.prototype._removeProfile = function () { + // graph + this._profile = null; + + // on vide le container + if (this._profileContainer) { + while (this._profileContainer.firstChild) { + this._profileContainer.removeChild(this._profileContainer.firstChild); + } + } + }; + // ################################################################### // // ####################### handlers events to dom #################### // // ################################################################### // diff --git a/src/OpenLayers/Controls/Export.js b/src/OpenLayers/Controls/Export.js new file mode 100644 index 000000000..e9dc1a697 --- /dev/null +++ b/src/OpenLayers/Controls/Export.js @@ -0,0 +1,669 @@ +// import CSS +import "../CSS/Controls/Export/GPexportOpenLayers.css"; + +// import OpenLayers +import Control from "ol/control/Control"; + +// import local +import ID from "../../Common/Utils/SelectorID"; +import Utils from "../../Common/Utils"; +import Logger from "../../Common/Utils/LoggerByDefault"; + +// import local with ol dependencies +import KMLExtended from "../Formats/KML"; +import GeoJSONExtended from "../Formats/GeoJSON"; +import GPXExtended from "../Formats/GPX"; + +// DOM +import ExportDOM from "../../Common/Controls/ExportDOM"; + +var logger = Logger.getLogger("export"); + +/** + * @classdesc + * + * Export button + * + * @constructor + * @alias ol.control.Export + * @param {Object} options - options for function call. + * @param {String} [options.format = "geojson"] - geojson / kml / gpx + * @param {String} [options.name = "export"] - export name + * @param {String} [options.title = "Exporter"] - button name + * @param {Boolean} [options.menu = false] - displays the format choice menu + * @param {Function} [options.onExport] - callback + * @param {DOMElement} [options.target] - target + * @param {Object} options.control - instance of control + * @fires export:compute + * @example + * // without adding to the map + * var export = new ButtonExport(options); + * export.setControl(iso); + * export.setTarget(); + * export.setName("export"); + * export.setFormat("geojson"); + * export.setTitle("Exporter"); + * export.setMenu(false); + * export.render(); // <-- direct call to render function ! + * export.on("export:compute", (data) => { console.log(data); }); + * + * // with adding to the map + * var export = new ButtonExport(options); + * export.setControl(iso); + * export.setTarget(); + * export.setName("export"); + * export.setFormat("geojson"); + * export.setTitle("Exporter"); + * export.setMenu(false); + * export.on("export:compute", (data) => { console.log(data); }); + * map.addControl(export); // <-- using the OpenLayers mechanism, don't call to render function ! + */ +class ButtonExport extends Control { + + /** + * See {@link ol.control.Export} + * @module ButtonExport + * @alias module:~Controls/ButtonExport + * @param {Object} [options] - options + * @example + * import ButtonExport from "src/OpenLayers/Controls/Export" + */ + constructor (options) { + logger.trace("[constructor] Export", options); + + super({ + element : document.createElement("div"), + render : options.render, + target : options.target + }); + + // options + this.options = options || { + control : null, + target : null, + format : "geojson", + name : "export", + title : "Exporter", + menu : false, + onExport : null + }; + + if (!(this instanceof ButtonExport)) { + throw new TypeError("ERROR CLASS_CONSTRUCTOR"); + } + + /** + * Response to the export of the route calculation + * (only for jsdoc) + * + * @example + * // GeoJSON format + * { + * "type":"FeatureCollection", + * "features":[...], + * "geoportail:compute":{ + * "points":[ [2.588024210134887, 48.84192678293002 ] ], + * "transport":"Voiture", + * "exclusions":[...], + * "computation":"fastest", + * "results":{ } + * } + * + * @see {@link https://ignf.github.io/geoportal-access-lib/latest/jsdoc/Gp.Services.RouteResponse.html|Service} + */ + // eslint-disable-next-line no-undef + this.EXPORT_ROUTE = {}; + + /** + * Response to the export of the isochron calculation + * (only for jsdoc) + * + * @example + * // GeoJSON format + * { + * "type":"FeatureCollection", + * "features":[...], + * "geoportail:compute":{ + * "transport":"Pieton", + * "computation":"time", + * "exclusions":[ + * + * ], + * "direction":"departure", + * "point":[ 2.587835382718464, 48.84192678293002 ], + * "results":{ + * "message":"", + * "id":"", + * "location":{ + * "x":"2.587835382718464", + * "y":"48.84192678293002" + * }, + * "srs":"EPSG:4326", + * "geometry":{ + * "type":"Polygon", + * "coordinates":[[...]] + * }, + * "time":180, + * "distance":"" + * } + * } + * } + * + * @see {@link https://ignf.github.io/geoportal-access-lib/latest/jsdoc/Gp.Services.IsoCurveResponse.html|Service} + */ + // eslint-disable-next-line no-undef + this.EXPORT_ISOCHRON = {}; + + /** + * Response to the export of the profile calculation + * (only for jsdoc) + * + * @example + * // GeoJSON format + * { + * "type":"FeatureCollection", + * "features":[...], + * "geoportail:compute":{ + * "greaterSlope":76, + * "meanSlope":7, + * "distancePlus":84, + * "distanceMinus":48, + * "ascendingElevation":5, + * "descendingElevation":-4, + * "altMin":"92,04", + * "altMax":"96,71", + * "distance":163, + * "unit":"m", + * "points":[ + * { + * "z":95.68, + * "lon":2.5874, + * "lat":48.8419, + * "acc":2.5, + * "dist":0, + * "slope":0 + * } + * ] + * } + * } + * + * @see {@link https://ignf.github.io/geoportal-access-lib/latest/jsdoc/Gp.Services.AltiResponse.html|Service} + */ + // eslint-disable-next-line no-undef + this.EXPORT_PROFILE = {}; + + // id unique + this.uid = this.options.id || ID.generate(); + + // export + this.extension = null; + this.mimeType = null; + + // dom + this.container = null; + this.button = null; + this.menu = null; + this.icon = "\u2630 "; + this.menuClassHidden = "GPexportMenuHidden"; + + this.initOptions(); + this.initContainer(); + } + + // ################################################################### // + // ##################### public methods ############################## // + // ################################################################### // + + /** + * Render DOM + * + * @public + */ + render () { + // container principal + if (!this.options.target) { + if (this.options.control) { + // insertion du composant dans le panneau du controle + var container = this.options.control.getContainer(); + // ex. GP(iso|route)Panel- + this.options.target = container.lastChild; + } + } + if (this.container) { + this.options.target.appendChild(this.container); + } + } + + // ################################################################### // + // #################### privates methods ############################# // + // ################################################################### // + + /** + * Initialize options + * (called by constructor) + * + * @private + */ + initOptions () { + if (this.options.control) { + // ... + } + + if (this.options.target) { + // ... + } + + var format = this.options.format; + (format) ? this.setFormat(format) : this.setFormat(""); + + if (!this.options.name) { + this.setName("export"); + } + + if (!this.options.title) { + this.setTitle("Exporter"); + } + + if (this.options.menu === undefined) { + this.setMenu(false); + } + } + + /** + * Initialize container + * (called by constructor) + * + * @private + * @todo menu des options + */ + initContainer () { + // TODO + // menu des options de l'export : + // * [ nom ] + // * format + // https://www.w3schools.com/howto/howto_css_dropdown.asp + // https://www.w3schools.com/howto/howto_css_custom_checkbox.asp + + // afficher l'icone du menu + var title = this.options.title; + if (this.options.menu) { + title = this.icon + this.options.title; + } + + var div = document.createElement("div"); + div.id = this._addUID("GPexportContainer"); + div.className = "GPexportMenuContainer"; + + // bouton Exporter + // utiliser les templates literals avec la substitution ${...} + var button = this.stringToHTML(` + + `); + + // add event click button + this.button = button.firstChild; + if (this.button) { + this.button.addEventListener("click", (e) => this.onClickButtonExport(e)); + } + div.appendChild(button.firstChild); + + // menu des options + // utiliser les templates literals avec la substitution ${...} + var menu = this.stringToHTML(` +
+ + + +
+ `); + + this.menu = menu.firstChild; + if (this.menu) { + if (this.options.menu) { + var className = this.menu.className; + this.menu.className = className.replace(this.menuClassHidden, ""); + } + var radios = this.menu.querySelectorAll(`input[type=radio][name="format"]`); + radios.forEach((radio) => { + // radio checked par defaut + if (radio.id.toUpperCase().includes(this.options.format.toUpperCase())) { + radio.checked = true; + } + // ecouteur pour changer de format + radio.addEventListener("change", (e) => { + this.setFormat(e.target.value); + }); + }); + } + div.appendChild(menu.firstChild); + + this.container = div; + } + + /** + * ... + * + * @param {String} str - ... + * @returns {DOMElement} - ... + * @private + */ + stringToHTML (str) { + var support = function () { + if (!window.DOMParser) return false; + var parser = new DOMParser(); + try { + parser.parseFromString("x", "text/html"); + } catch (err) { + return false; + } + return true; + }; + + // If DOMParser is supported, use it + if (support()) { + var parser = new DOMParser(); + var doc = parser.parseFromString(str, "text/html"); + return doc.body; + } + + // Otherwise, fallback to old-school method + var dom = document.createElement("div"); + dom.innerHTML = str; + return dom; + } + + /** + * ... + * @returns {Boolean} - ... + * @private + */ + isPluggableControl () { + // tester toutes les méthodes des widgets pluggable + // la méthode getData() n'est pas obligatoire car certains widgets + // n'ont pas de configuration. + if (this.options.control && + typeof this.options.control.getContainer === "function" && + typeof this.options.control.getLayer === "function") { + return true; + } + return false; + } + + /** + * ... + * @param {Object} layer - ... + * @param {Object} [data] - ... + * @returns {String} - ... + * @private + */ + exportFeatures (layer, data) { + var result = null; + if (!layer) { + logger.warn("Impossible to export : no layer is hosting features."); + return result; + } + if (!layer.getSource() || + !layer.getSource().getFeatures() || + !layer.getSource().getFeatures().length) { + logger.warn("Impossible to export : no features found."); + return result; + } + + // INFO + // les styles sont bien transmis pour l'outil de dessin + // mais, ce n'est pas toujours le cas pour certains widgets !? + // donc, on y ajoute les styles par defaut... + layer.getSource().getFeatures().forEach((feature) => { + var style = feature.getStyle(); + if (!style && typeof this.options.control.getStyle === "function") { + // pour le cas de feature de type POINT + // car les points de saisies ne sont pas disponibles dans les styles par defaut. + if (feature.getGeometry().getType() === "Point") { + feature.set("marker-symbol", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAmCAYAAABpuqMCAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAQxSURBVFiF3ZldaBxVFMd/d2ayTRtjQpo2mlilWBEMshoj+FAERZIHIdA3iw+V1icRREFIQAKNgsUHQfBFwZI2WgWxqYUiVTDBBj9ILC5Nu2tjdjemsR+mSZNNNvsxO8eHTTRuk+zMnQmCf9iHnXvO+Z//nDvn3rmjRIT/C6zAI4ZVFRbtKDpQNCM0AvXANIo/EC4inMbmLBFZDJJaBVaZJ9Sd2HQCrwDbXHikgfewOMKPMh9ECsGIeVx1IHxEsQJeMY3iEMNy2m8aht8AtKpOhH70hADUI/TTqjr9puKvMsUE3vabxCp0MSJHdJ31xRSnVj9BVPcfOCj26U45PTHFh30c/am1EaaxuF+nKejd1WLX2gwhAPXL8T3De2XCqooKbuCu/eoiTZ6dXtch75WxaMeNENOyOXx8kHOpGMPOIudSMQ4fH8S0bBcs25Z5PMF7ZVpVL3BgQxvTsvn6+kVq6sK3jc3NRGhraKZgl9t9HGNEXvCSmvfKKJrL2nQfHVpTCEBNXZjuo0OB8JTAu5jiXmtjPL3vLl/jbnlKoNPN6spaVFbt8jXulqcEOmKSZS0yi5O+xt3ylEBHTLSsxbf913yNu+UpgU4DKE/Sc3AvczORNcfmZiL0HNwbCE8JvItxWDvJ1SjYFm0NzZzpG2RpIYbIIksLMc70Dbpsy+54SqCzzlQAY8B9Xsk8YAJ4gBHJe3HyXpkRyaN407OfN7zlVQjobjTv4BgQ1/ItjzjV9Oo46okZEBuhS8u3PDoZEDf7t9vg903zBLBfP8C/4cAnD87teclIGyFlLoVyllWh8vmQYRgVAOI4OQmFciKSFZFsMpmck1UC/Il5VNViEgHu9StkQYyb7bNNH1wrmDm3PgqWUHLBhl+SyeRV/6czLepJDAbw8fos4HTNb+/9PFv9u3YMU/X6f38/L98B7/gJ8U2uasiPEADTcRqDOozoBn7WcbzqmFOvpnYM+uTPpvP5SDBiimvP8xRPKV3DFpV7fX7HyYyD44M96xicmpqaSgd3TDQsv6J4zYvLx5nqsz/kK29qcyq5kFpafD+RSMSKf4P+CvCY+hJFRzmzmB2KPTvb+JnX8CsdzDGM8/F4/PrqseC/AggvZlGXtyipXc8kLcbCy6mdrg/6lBIbR41DYXR8cjIqIoW17IIXc17+nHnEOnS3VfhiHQt5d7HmVMK2Nn6+DHLiOGMmRLdMVI+NymjZ9Sf4abaMqZbQp01G/rnS60P5rT8duNXw1TpuGaXksmMYlxKJxLiIt23NponhKVV5a874rdZwmlYuTTvmjWdmGj9Mifl3kkpJ2hGJGY4THb9yJS4i2p0t+Gm2ggHJxMNb94eNzIAJZgEKbyxsP5kS00ZJSkG0oFQ0mZyYkKDuqIhs6u/7hyt75luM2RMPVfft3rW7bU9T0z2bxbV50+w/wF8f81R5OpwBhwAAAABJRU5ErkJggg=="); + return; + } + feature.setStyle(this.options.control.getStyle()); + } + }); + + // ajouter les metadonnées de calcul et de configuration + var options = {}; + if (data) { + // properties ajoutées à la racine : + // ex. "geoportail:compute" : {} + options.extensions = { + "geoportail:compute" : data + }; + } + + var ClassName = null; + switch (this.options.format.toUpperCase()) { + case "KML": + options.writeStyles = true; + ClassName = new KMLExtended(options); + break; + case "GPX": + ClassName = new GPXExtended(options); + break; + case "GEOJSON": + ClassName = new GeoJSONExtended(options); + break; + default: + break; + } + + if (!ClassName) { + logger.warn("Impossible to export : format unknown !?"); + return result; + } + + var featProj = layer.getSource().getProjection(); + + // INFO + // on determine la projection de la carte + // si le composant a été ajouté sur la carte via le mécanisme d'OpenLayer... + var map = this.getMap(); + if (map) { + featProj = featProj || map.getView().getProjection(); + } + + // INFO + // par defaut, webmercator ou "EPSG:3857" + result = ClassName.writeFeatures(layer.getSource().getFeatures(), { + dataProjection : "EPSG:4326", + featureProjection : featProj || "EPSG:3857" + }); + + return result; + } + // ################################################################### // + // ######################## event dom ################################ // + // ################################################################### // + + /** + * ... + * @param {*} e - Click + */ + onClickButtonExport (e) { + if (!this.isPluggableControl()) { + logger.warn("Componant not pluggable with the control !"); + return; + } + + var layer = this.options.control.getLayer(); + var data = (this.options.control.getData !== undefined) ? this.options.control.getData() : {}; + + var content = this.exportFeatures(layer, data); + if (!content || content === "null") { + return; + } + + /** + * event triggered when the export is finished + * + * @event export:compute + * @typedef {Object} + * @property {Object} type - event + * @property {Object} target - instance Export + * @property {String} content - export data + * @example + * Export.on("export:compute", function (e) { + * console.log(e.target); + * }) + */ + this.dispatchEvent({ + type : "export:compute", + content : content + }); + + // INFO + // la callback annule le download du fichier. + if (this.options.onExport && typeof this.options.onExport === "function") { + this.options.onExport(content); + return; + } + + var link = document.createElement("a"); + // determiner le bon charset ! + var charset = "utf-8"; + link.setAttribute("href", "data:" + this.mimeType + ";charset=" + charset + "," + encodeURIComponent(content)); + link.setAttribute("download", this.options.name + this.extension); + if (document.createEvent) { + var event = document.createEvent("MouseEvents"); + event.initEvent("click", true, true); + link.dispatchEvent(event); + } else { + link.click(); + } + } + + // ################################################################### // + // ##################### public setters ############################## // + // ################################################################### // + /** + * ... + * @param {Object} control - ... + * @public + */ + setControl (control) { + this.options.control = control; + } + + /** + * ... + * @param {DOMElement} target - ... + * @public + */ + setTarget (target) { + this.options.target = target; + } + + /** + * ... + * @param {String} format - ... + * @public + */ + setFormat (format) { + this.options.format = format.toUpperCase(); + switch (this.options.format) { + case "KML": + this.extension = ".kml"; + this.mimeType = "application/vnd.google-earth.kml+xml"; + break; + case "GPX": + this.extension = ".gpx"; + this.mimeType = "application/gpx+xml"; + break; + case "GEOJSON": + this.extension = ".geojson"; + this.mimeType = "application/geo+json"; + break; + default: + // redefine format by default ! + this.options.format = "GEOJSON"; + this.extension = ".geojson"; + this.mimeType = "application/geo+json"; + break; + } + } + + /** + * ... + * @param {String} name - ... + * @public + */ + setName (name) { + this.options.name = name; + } + + /** + * ... + * @param {String} title - ... + * @public + */ + setTitle (title) { + this.options.title = title; + if (this.button) { + // afficher l'icone du menu / titre + this.button.value = (this.options.menu) ? this.icon + title : title; + } + } + + /** + * ... + * @param {Boolean} active - ... + * @public + */ + setMenu (active) { + this.options.menu = active; + if (this.button) { + // afficher l'icone du menu / titre + this.button.value = (this.options.menu) ? this.icon + this.options.title : this.options.title; + } + if (this.menu && this.options.menu) { + // afficher le menu + var className = this.menu.className; + this.menu.className = className.replace(this.menuClassHidden, ""); + // format par defaut + var radios = this.menu.querySelectorAll(`input[type=radio][name="format"]`); + radios.forEach((radio) => { + // radio checked par defaut + if (radio.id.toUpperCase().includes(this.options.format.toUpperCase())) { + radio.checked = true; + } + }); + } + } + +}; + +// on récupère les méthodes de la classe DOM +Utils.assign(ButtonExport.prototype, ExportDOM); + +export default ButtonExport; + +// Expose Export as ol.control.Export (for a build bundle) +if (window.ol && window.ol.control) { + window.ol.control.Export = ButtonExport; +} diff --git a/src/OpenLayers/Controls/Isocurve.js b/src/OpenLayers/Controls/Isocurve.js index 94b342902..0ac9f69d0 100644 --- a/src/OpenLayers/Controls/Isocurve.js +++ b/src/OpenLayers/Controls/Isocurve.js @@ -23,6 +23,8 @@ import Interactions from "./Utils/Interactions"; // import local with ol dependencies import LayerSwitcher from "./LayerSwitcher"; import LocationSelector from "./LocationSelector"; +import ButtonExport from "./Export"; + // DOM import IsoDOM from "../../Common/Controls/IsoDOM"; @@ -42,6 +44,7 @@ var logger = Logger.getLogger("isocurve"); * @param {Boolean} [options.ssl = true] - use of ssl or not (default true, service requested using https protocol) * @param {Boolean} [options.collapsed = true] - Specify if widget has to be collapsed (true) or not (false) on map loading. Default is true. * @param {Boolean} [options.draggable = false] - Specify if widget is draggable + * @param {Boolean} [options.export = false] - Specify if button "Export" is displayed * @param {Object} [options.exclusions = {"toll" : false, "tunnel" : false, "bridge" : false}] - list of exclusions with status (true = checked). By default : no exclusions checked. * @param {Array} [options.graphs = ["Voiture", "Pieton"]] - list of graph resources to be used for isocurve calculation, by default : ["Voiture", "Pieton"]. Possible values are "Voiture" and "Pieton". The first element is selected. * @param {Array} [options.methods = ["time", "distance"]] - list of methods, by default : ["time", "distance"]. Possible values are "time" and "distance". The first element is selected by default. @@ -58,10 +61,12 @@ var logger = Logger.getLogger("isocurve"); * @fires isocurve:drawstart * @fires isocurve:drawend * @fires isocurve:compute + * @fires export:compute * @example * var iso = ol.control.Isocurve({ * "collapsed" : false, * "draggable" : true, + * "export" : false, * "methods" : ["time", "distance"], * "exclusions" : { * "toll" : true, @@ -144,6 +149,20 @@ var Isocurve = (function (Control) { // enrichissement du DOM du container lors de l'ajout à la carte this._container = this._initContainer(map); + // ajout d'un bouton d'export + if (this.options.export) { + var opts = Utils.assign({ control : this }, this.options.export); + this.export = new ButtonExport(opts); + this.export.render(); + var self = this; + this.export.on("export:compute", (e) => { + self.dispatchEvent({ + type : "export:compute", + content : e.content + }); + }); + } + // mode "draggable" if (this.draggable) { Draggable.dragElement( @@ -219,6 +238,7 @@ var Isocurve = (function (Control) { // application des styles layer.setStyle(this._defaultFeatureStyle); + // sauvegarde this._geojsonLayer = layer; }; @@ -284,6 +304,24 @@ var Isocurve = (function (Control) { this._currentIsoResults = data.results; }; + /** + * Get container + * + * @returns {DOMElement} container + */ + Isocurve.prototype.getContainer = function () { + return this._container; + }; + + /** + * Get default style + * + * @returns {ol.style} style + */ + Isocurve.prototype.getStyle = function () { + return this._defaultFeatureStyle; + }; + /** * This method is public. * It allows to control the execution of a traitment. @@ -405,6 +443,7 @@ var Isocurve = (function (Control) { this.options = { collapsed : true, draggable : false, + export : false, methods : ["time", "distance"], graphs : ["Voiture", "Pieton"], exclusions : { @@ -469,6 +508,9 @@ var Isocurve = (function (Control) { this._geojsonLayer = null; this._geojsonObject = null; + // bouton export + this.export = null; + // si un calcul est en cours ou non this._waiting = false; // timer pour cacher la patience après un certain temps @@ -911,7 +953,7 @@ var Isocurve = (function (Control) { this._originPoint.setMap(map); // a la sélection d'un nouveau point, on réinitialise aussi le tracé var self = this; - /** click sur le pointer */ + // click sur le pointer document.getElementById("GPlocationOriginPointerImg_1-" + this._uid).onclick = function () { self._clearGeojsonLayer(); var map = self.getMap(); @@ -940,7 +982,7 @@ var Isocurve = (function (Control) { */ self.dispatchEvent("isocurve:drawstart"); }; - /** click sur le label */ + // click sur le label document.getElementById("GPlocationOriginLabel_1-" + this._uid).onclick = function () { self._clearGeojsonLayer(); self._formContainer.className = ""; @@ -953,7 +995,7 @@ var Isocurve = (function (Control) { ); self.dispatchEvent("isocurve:drawend"); }; - /** click sur la zone de saisie */ + // click sur la zone de saisie document.getElementById("GPlocationOrigin_1-" + this._uid).onclick = function () { self._clearGeojsonLayer(); /** diff --git a/src/OpenLayers/Controls/ReverseGeocode.js b/src/OpenLayers/Controls/ReverseGeocode.js index 1c0632e69..bef1f6a45 100644 --- a/src/OpenLayers/Controls/ReverseGeocode.js +++ b/src/OpenLayers/Controls/ReverseGeocode.js @@ -1710,18 +1710,7 @@ var ReverseGeocode = (function (Control) { var idx = tagid.substring(tagid.indexOf("_") + 1); // ex. 21 var f = this._resultsFeaturesSource.getFeatureById(parseInt(idx, 10)); - /** - * event triggered when an element of the results is clicked - * - * @event reversegeocode:onclickresult - * @property {Object} type - event - * @property {Object} location - location - * @property {Object} target - instance ReverseGeocode - * @example - * Reverse.on("reversegeocode:onclickresult", function (e) { - * console.log(e.location); - * }) - */ + this.dispatchEvent({ type : "reversegeocode:onclickresult", location : f.getProperties().location diff --git a/src/OpenLayers/Controls/Route.js b/src/OpenLayers/Controls/Route.js index 0b5a29fbc..2d967f75e 100644 --- a/src/OpenLayers/Controls/Route.js +++ b/src/OpenLayers/Controls/Route.js @@ -26,6 +26,7 @@ import Draggable from "../../Common/Utils/Draggable"; import Interactions from "./Utils/Interactions"; // import local with ol dependencies import LocationSelector from "./LocationSelector"; +import ButtonExport from "./Export"; import LayerSwitcher from "./LayerSwitcher"; // DOM import RouteDOM from "../../Common/Controls/RouteDOM"; @@ -46,6 +47,7 @@ var logger = Logger.getLogger("route"); * @param {Boolean} [options.ssl = true] - use of ssl or not (default true, service requested using https protocol) * @param {Boolean} [options.collapsed = true] - Specify if widget has to be collapsed (true) or not (false) on map loading. Default is true. * @param {Boolean} [options.draggable = false] - Specify if widget is draggable + * @param {Boolean} [options.export = false] - Specify if button "Export" is displayed * @param {Object} [options.exclusions = {"toll" : false, "tunnel" : false, "bridge" : false}] - list of exclusions with status (true = checked). By default : no exclusions checked. * @param {Array} [options.graphs = ["Voiture", "Pieton"]] - list of resources, by default : ["Voiture", "Pieton"]. The first element is selected. * @param {Object} [options.routeOptions = {}] - route service options. see {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~route Gp.Services.route()} to know all route options. @@ -59,10 +61,12 @@ var logger = Logger.getLogger("route"); * @fires route:drawstart * @fires route:drawend * @fires route:compute + * @fires export:compute * @example * var route = ol.control.Route({ * "collapsed" : true * "draggable" : true, + * "export" : false, * "exclusions" : { * "toll" : true, * "bridge" : false, @@ -152,6 +156,20 @@ var Route = (function (Control) { // enrichissement du DOM du container this._container = this._initContainer(map); + // ajout d'un bouton d'export + if (this.options.export) { + var opts = Utils.assign({ control : this }, this.options.export); + this.export = new ButtonExport(opts); + this.export.render(); + var self = this; + this.export.on("export:compute", (e) => { + self.dispatchEvent({ + type : "export:compute", + content : e.content + }); + }); + } + // mode "draggable" if (this.draggable) { Draggable.dragElement( @@ -299,6 +317,24 @@ var Route = (function (Control) { this._currentRouteInformations = data.results; }; + /** + * Get container + * + * @returns {DOMElement} container + */ + Route.prototype.getContainer = function () { + return this._container; + }; + + /** + * Get default style + * + * @returns {ol.style} style + */ + Route.prototype.getStyle = function () { + return this._defaultFeatureStyle; + }; + /** * This method is public. * It allows to init the control. @@ -396,6 +432,7 @@ var Route = (function (Control) { this.options = { collapsed : true, draggable : false, + export : false, graphs : ["Voiture", "Pieton"], exclusions : { toll : false, @@ -476,6 +513,9 @@ var Route = (function (Control) { // la geometrie des troncons au format GeoJSON this._geojsonObject = null; + // bouton export + this.export = null; + // le container de la popup (pour les troncons selectionnés) this._popupContent = null; this._popupDiv = this._initPopupDiv(); diff --git a/src/OpenLayers/Formats/GPX.js b/src/OpenLayers/Formats/GPX.js index 702408450..7b8df9a16 100644 --- a/src/OpenLayers/Formats/GPX.js +++ b/src/OpenLayers/Formats/GPX.js @@ -24,8 +24,9 @@ import Parser from "../../Common/Utils/Parser"; * @extends {ol.format.GPX} * @type {ol.format.GPXExtended} * @param {Object} options - Options - * @param {Object} options.defaultStyle - Styles by default - * @param {function} options.readExtensions - Reading extensions (native) + * @param {Object} [options.defaultStyle] - Styles by default + * @param {Object} [options.extensions] - Add properties to file root + * @param {function} [options.readExtensions] - Reading extensions (native) */ var GPX = (function (olGPX) { /** @@ -168,7 +169,7 @@ var GPX = (function (olGPX) { * @param {Object[]} features - Features. * @param {Object} options - Options. * - * @return {String} Result. + * @return {String} Result or null. */ GPX.prototype.writeFeatures = function (features, options) { // INFO @@ -305,37 +306,36 @@ var GPX = (function (olGPX) { } }); - var gpxStringInitial = olGPX.prototype.writeFeatures.call(this, features, options); - - var gpxDoc = Parser.parse(gpxStringInitial); + // nodes + var gpxNode = olGPX.prototype.writeFeaturesNode.call(this, features, options); + if (gpxNode === null) { + return null; + } - // au cas où..., si exception... - if (gpxDoc === null) { - return gpxStringInitial; + // on ajoute les extensions à la racine pour les metadonnées de calcul + if (this.options.hasOwnProperty("extensions")) { + processRootExtensions_(gpxNode, this.options.extensions); } // INFO // à chaque fois qu'un style est trouvé dans un feature, // on appelle la fonction d'insertion des balises extensions dans le DOM. - processExtensions_(gpxDoc, features, { + processExtensions_(gpxNode, features, { extensions : writeExtensions_ }); // dom -> string - var gpxStringExtended = Parser.toString(gpxDoc); - - // au cas où..., si exception... + var gpxStringExtended = Parser.toString(gpxNode); if (!gpxStringExtended) { - return gpxStringInitial; + return null; } // format string var gpxStringFormatted = Parser.format(gpxStringExtended); - - // au cas où..., si exception... if (gpxStringFormatted === "") { - return gpxStringInitial; + return null; } + return gpxStringFormatted; }; @@ -491,6 +491,104 @@ var GPX = (function (olGPX) { } }; + /** + * ... + * + * @param {*} doc - ... + * @param {*} extensions - ... + */ + function processRootExtensions_ (doc, extensions) { + // TODO namespace ? + var extensionsRoot = document.createElement("extensions"); + // INFO + // convert JSON to XML (dom) + // * type string : + // { typestring: "string" } -> string + // + // * type object : + // { typeobject: { typestring1: "string", typestring2: "string" } } + // -> + // string + // string + // + // + // * type array : + // { typearray : ["item1", "item2"] } + // -> + // item1 + // item2 + // + // + // * type array of array + // -> + // + // 1 + // 2 + // + // + // + // * type array of object + // -> + // + // string + // string + // + // + // string + // string + // + // + function toDOM (node, json) { + for (const key in json) { + if (Object.hasOwnProperty.call(json, key)) { + var element = json[key] || ""; // au cas où... + var tag = document.createElement(key); + // eslint-disable-next-line valid-typeof + if (typeof element === "string" || typeof element === "number") { + tag.innerHTML = element; + node.appendChild(tag); + } else if (element instanceof Array) { + tag.setAttribute("type", "array"); + tag.setAttribute("index", element.length); + for (let index = 0; index < element.length; index++) { + var item = element[index] || ""; // au cas où... + var n = document.createElement("value"); + if (typeof item === "string" || typeof item === "number") { + n.innerHTML = item; + tag.appendChild(n); + } else if (item instanceof Array) { + n.setAttribute("type", "array"); + n.setAttribute("index", item.length); + for (let i = 0; i < item.length; i++) { + var value = item[i] || ""; // au cas où... + var k = document.createElement("value"); + if (typeof value === "string" || typeof value === "number") { + k.innerHTML = value; + n.appendChild(k); + } + } + tag.appendChild(n); + } else if (item instanceof Object) { + tag.appendChild(toDOM(n, item)); + } else { + // "Unknown element !" + } + } + node.appendChild(tag); + } else if (element instanceof Object) { + node.appendChild(toDOM(tag, element)); + } else { + // "Unknown element !" + } + } + } + return node; + } + // insertion en 1ere place ! + var firstChild = doc.firstChild; + doc.insertBefore(toDOM(extensionsRoot, extensions), firstChild); + }; + /** * ... * @@ -529,8 +627,7 @@ var GPX = (function (olGPX) { // On peut y placer nos balises extensions. var index = -1; - var root = doc.documentElement; - var nodes = root.childNodes; + var nodes = doc.childNodes; for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; switch (node.nodeName) { diff --git a/src/OpenLayers/Formats/GeoJSON.js b/src/OpenLayers/Formats/GeoJSON.js index 23d909368..1cff31d76 100644 --- a/src/OpenLayers/Formats/GeoJSON.js +++ b/src/OpenLayers/Formats/GeoJSON.js @@ -24,12 +24,13 @@ import Color from "../../Common/Utils/ColorUtils"; * @extends {ol.format.GeoJSON} * @type {ol.format.GeoJSONExtended} * @param {Object} options - Options - * @param {Object} options.defaultStyle - Styles by default + * @param {Object} [options.defaultStyle] - Styles by default + * @param {Object} [options.extensions] - Add properties to file root */ var GeoJSON = (function (olGeoJSON) { /** * See {@link ol.format.GeoJSONExtended} - * @module SearchEngine + * @module GeoJSONExtended * @alias module:~Formats/GeoJSONExtended * @param {*} options - options * @example @@ -71,7 +72,7 @@ var GeoJSON = (function (olGeoJSON) { * * @see ol.format.GeoJSON.prototype.readFeatures * @param {Document|Node|ArrayBuffer|Object|String} source - Source. - * @param {olx.format.ReadOptions=} options - options. + * @param {olx.format.ReadOptions} [options] - Options. * @return {Array.} Features. */ GeoJSON.prototype.readFeatures = function (source, options) { @@ -268,15 +269,15 @@ var GeoJSON = (function (olGeoJSON) { * This function overloads ol.format.GeoJSON.writeFeatures ... * * @see ol.format.GeoJSON.prototype.writeFeatures - * @param {Object[]} features - Features. - * @param {Object} options - Options. + * @param {Array.} features - Features. + * @param {Object} [options] - Options. * * @return {String} Result. */ GeoJSON.prototype.writeFeatures = function (features, options) { // on met à jour les properties de styles features.forEach(function (feature) { - var style = feature.getStyle(); + var style = feature.getStyle() || feature.getStyleFunction(); if (style) { // style ajouté via une fonction, pour les styles par defaut par ex. if (typeof style === "function") { @@ -426,8 +427,17 @@ var GeoJSON = (function (olGeoJSON) { } }); - var geoJSONString = olGeoJSON.prototype.writeFeatures.call(this, features, options); - return geoJSONString; + var geoJSONObject = olGeoJSON.prototype.writeFeaturesObject.call(this, features, options); + + // ajout des properties à la racine du fichier + // ex. options : { + // extensions : { /* liste des objets à ajouter */ } + // } + if (this.options.hasOwnProperty("extensions")) { + Object.assign(geoJSONObject, this.options.extensions); + } + + return JSON.stringify(geoJSONObject); }; return GeoJSON; diff --git a/src/OpenLayers/Formats/KML.js b/src/OpenLayers/Formats/KML.js index 91141428c..2c9b0efd8 100644 --- a/src/OpenLayers/Formats/KML.js +++ b/src/OpenLayers/Formats/KML.js @@ -40,6 +40,7 @@ var logger = Logger.getLogger("extended KML format"); * @type {ol.format.KMLExtended} * @extends {ol.format.KML} * @param {Object} options - Options + * @param {Object} [options.extensions] - Add properties to file root */ var KML = (function (olKML) { /** @@ -101,6 +102,30 @@ var KML = (function (olKML) { */ KML.prototype.constructor = KML; + /** + * ... + * + * @param {*} kmlNode - ... + * @param {*} extensions - ... + */ + function _processRootExtensions (kmlNode, extensions) { + var extendDataElement = document.createElementNS(kmlNode.namespaceURI, "ExtendedData"); + // on boucle sur toutes les clefs + for (const key in extensions) { + if (Object.hasOwnProperty.call(extensions, key)) { + const value = extensions[key]; + var dataElement = document.createElementNS(kmlNode.namespaceURI, "Data"); + dataElement.setAttribute("name", key); + var data = document.createTextNode(JSON.stringify(value)); + dataElement.appendChild(data); + extendDataElement.appendChild(dataElement); + } + } + // insertion en 1ere place ! + var firstChild = kmlNode.firstChild; + kmlNode.insertBefore(extendDataElement, firstChild); + }; + /** * Fonction de lecture du KML avec fonction de traitement en fonction du type * PlaceMark (Label ou Marker). @@ -108,7 +133,7 @@ var KML = (function (olKML) { * - creation de styles étendus ou correctifs sur le KML * - ajout de styles étendus sur les features * - * @param {DOMElement} kmlDoc - kml document + * @param {DOMElement} kmlNode - kml nodes * @param {Object[]} features - features * @param {Object} process - process * @@ -120,15 +145,14 @@ var KML = (function (olKML) { * }); * * // lit des fonctionnalités du KML non impl. par OpenLayers - * _kmlRead(kmlDoc, { + * _kmlRead(kmlNode, { * labelStyle : getStyleToFeatureLabel, * iconStyle : getStyleToFeatureIcon, * extendedData : getExtendedData * }); */ - function _kmlRead (kmlDoc, features, process) { - var root = kmlDoc.documentElement; - var firstNodeLevel = root.childNodes; + function _kmlRead (kmlNode, features, process) { + var firstNodeLevel = kmlNode.childNodes; // Si le DOM contient un seul objet, le noeud est directement un PlaceMark // sinon, c'est un ensemble de noeuds PlaceMark contenus dans le noeud Document. @@ -270,42 +294,51 @@ var KML = (function (olKML) { }; /** - * Write Extend Styles for Features. + * Write Extend for Features. * This function overloads ol.format.KML.writeFeatures ... * * @see ol.format.KML.prototype.writeFeatures * @param {Object[]} features - Features. * @param {Object} options - Options. * - * @return {String} Result. + * @return {String} kml string formatted */ KML.prototype.writeFeatures = function (features, options) { - // KML.prototype._parentWriteFeatures = ol.format.KML.prototype.writeFeatures; logger.log("overload : ol.format.KML.writeFeatures"); - var kmlString = this._writeExtendStylesFeatures(features, options); - return kmlString; + var kmlNode = olKML.prototype.writeFeaturesNode.call(this, features, options); + if (kmlNode === null) { + return null; + } + + // on ajoute les extensions à la racine pour les metadonnées de calcul + if (this.options.hasOwnProperty("extensions")) { + _processRootExtensions(kmlNode, this.options.extensions); + } + + // On ajoute les styles étendus + var kmlStringExtended = this._writeExtendStylesFeatures(kmlNode, features, options); + + // On realise un formattage du KML + var kmlStringFormatted = Parser.format(kmlStringExtended); + if (kmlStringFormatted === "") { + return null; + } + + return kmlStringFormatted; }; /** - * _writeExtendStylesFeatures + * Write Extended Styles for each features * + * @param {DOMElement} kmlNode - kml nodes * @param {Object[]} features - features * @param {Object} options - options * - * @returns {String} kml string formatted + * @returns {String} kml string extended * * @private */ - KML.prototype._writeExtendStylesFeatures = function (features, options) { - var kmlString = olKML.prototype.writeFeatures.call(this, features, options); - - var kmlDoc = Parser.parse(kmlString); - - if (kmlDoc === null) { - // au cas où... - return kmlString; - } - + KML.prototype._writeExtendStylesFeatures = function (kmlNode, features, options) { /** * C'est un Label ! * On va donc y ajouter qq styles sur le Label (police, halo, ...) : @@ -361,7 +394,7 @@ var KML = (function (olKML) { var _font = "Sans"; // TODO if (style && style.getElementsByTagName("LabelStyleSimpleExtensionGroup").length === 0) { - var labelextend = kmlDoc.createElement("LabelStyleSimpleExtensionGroup"); + var labelextend = kmlNode.createElement("LabelStyleSimpleExtensionGroup"); labelextend.setAttribute("fontFamily", _font); labelextend.setAttribute("haloColor", _haloColor); labelextend.setAttribute("haloRadius", _haloRadius); @@ -421,7 +454,7 @@ var KML = (function (olKML) { } if (style && style.getElementsByTagName("hotSpot").length === 0) { - var hotspot = kmlDoc.createElement("hotSpot"); + var hotspot = kmlNode.createElement("hotSpot"); hotspot.setAttribute("x", x); hotspot.setAttribute("y", y); hotspot.setAttribute("xunits", xunits); @@ -449,14 +482,14 @@ var KML = (function (olKML) { var labelName = feature.getProperties().name; if (labelName) { - var name = kmlDoc.createElement("name"); + var name = kmlNode.createElement("name"); name.innerHTML = labelName; tags.appendChild(name); } }; // On ajoute les styles étendus dans le DOM - _kmlRead(kmlDoc, features, { + _kmlRead(kmlNode, features, { labelStyle : __createExtensionStyleLabel, iconStyle : __createHotSpotStyleIcon, iconLabelStyle : __createStyleToFeatureIconLabel, @@ -464,26 +497,16 @@ var KML = (function (olKML) { }); // On convertit le DOM en String... - var kmlStringExtended = Parser.toString(kmlDoc); - - // au cas où... + var kmlStringExtended = Parser.toString(kmlNode); if (!kmlStringExtended) { - kmlStringExtended = kmlString; - } - - // On realise un formattage du KML - var kmlStringFormatted = Parser.format(kmlStringExtended); - - // au cas où... - if (kmlStringFormatted === "") { - kmlStringFormatted = kmlString; + return null; } - return kmlStringFormatted; + return kmlStringExtended; }; /** - * Read Extend Styles for Features. + * Read Extend for Features. * This function overloads ol.format.KML.readFeatures ... * * @see ol.format.KML.prototype.readFeatures @@ -514,7 +537,7 @@ var KML = (function (olKML) { }; /** - * _readExtendStylesFeatures + * Read Extended Styles for each features * * @param {(Document|Node|ArrayBuffer|Object|String)} source - source * @param {olx.format.ReadOptions=} options - options diff --git a/src/OpenLayers/GfiUtils.js b/src/OpenLayers/GfiUtils.js index e07c2fefa..ddbcd9dff 100644 --- a/src/OpenLayers/GfiUtils.js +++ b/src/OpenLayers/GfiUtils.js @@ -239,7 +239,8 @@ var GfiUtils = { "name", // déjà traité "description", // déjà traité "styleUrl", - "extensionsNode_" // extensions GPX + "extensionsNode_", // extensions GPX + "icon" // ajouté par la 3D en cas de switch ]; for (p in props) { if (props[p] === undefined) { diff --git a/src/OpenLayers/Layers/LayerMapBox.js b/src/OpenLayers/Layers/LayerMapBox.js new file mode 100644 index 000000000..d5e49eb4e --- /dev/null +++ b/src/OpenLayers/Layers/LayerMapBox.js @@ -0,0 +1,334 @@ +// import openlayers +import VectorTileLayer from "ol/layer/VectorTile"; +import VectorTileSource from "ol/source/VectorTile"; +import TileJSONSource from "ol/source/TileJSON"; +import MVT from "ol/format/MVT"; +import { unByKey as observableUnByKey } from "ol/Observable"; +// import olms : module ES6 +import { applyStyle } from "ol-mapbox-style"; +// import local +import Utils from "../../Common/Utils"; +import Config from "../../Common/Utils/Config"; + +/** + * @classdesc + * Geoportal Layer Mapbox creation + * + * @constructor + * @extends {ol.layer.VectorTile} + * @alias ol.layer.GeoportalMapBox + * @type {ol.layer.GeoportalMapBox} + * @param {Object} options - options for function call. + * @param {String} options.layer - Layer name (e.g. "PLAN.IGN") + * @param {String} [options.style] - Style name (e.g. "classique") + * @param {String} [options.source] - Source name (e.g. "plan_ign") + * @param {Boolean} [options.ssl] - if set true, enforce protocol https (only for nodejs) + * @param {Object} [settings] - other options for ol.layer.VectorTile function (see {@link https://openlayers.org/en/latest/apidoc/module-ol_layer_VectorTile-VectorTileLayer.html ol.layer.VectorTile}) + * @example + * var LayerMapBox = new ol.layer.GeoportalMapBox({ + * layer : "PLAN.IGN", + * [style : "classique",] + * [source : "plan_ign",] + * [ssl: true] + * }, { + * opacity + * visible + * extent + * declutter + * ... + * }); + */ +var LayerMapBox = (function (VectorTileLayer) { + /** + * See {@link ol.layer.GeoportalMapBox} + * @module LayerMapBox + * @alias module:~Layers/GeoportalMapBox + * @param {*} options - options + * @param {*} [settings] - other settings + * @example + * import LayerMapBox from "src/OpenLayers/Layers/LayerMapBox" + */ + function LayerMapBox (options, settings) { + if (!(this instanceof LayerMapBox)) { + throw new TypeError("ERROR CLASS_CONSTRUCTOR"); + } + + if (!options.layer) { + throw new Error("ERROR PARAM_MISSING : layer"); + } + + if (typeof options.layer !== "string") { + throw new Error("ERROR WRONG TYPE : layer"); + } + + this.layerName = options.layer; + + // autres options facultatives + this.styleName = options.style; + this.sourceId = options.source; + + // par defaut + if (typeof options.ssl === "undefined") { + options.ssl = true; + } + + // si ssl = false on fait du http + // par défaut, ssl = true, on fait du https + this.protocol = options.ssl === false ? "http://" : "https://"; + + // WARNING : + // on fait le choix de ne pas utiliser la clef apiKey pour checker les droits sur la ressource + // car le service n'est pas securisé... + + // Check if configuration is loaded + if (!Config.isConfigLoaded()) { + throw new Error("ERROR : contract key configuration has to be loaded to load Geoportal layers."); + } + + /** + * Ex. reponse Autoconf + * (only for jsdoc) + * @example + * "PLAN.IGN$GEOPORTAIL:GPP:TMS":{ + * "hidden":true, + * "queryable":false, + * "serviceParams":{ + * "id":"GPP:TMS", + * "version":"1.0.0", + * "serverUrl":{ + * "essentiels":"https://wxs.ign.fr/essentiels/geoportail/tms/1.0.0" + * } + * }, + * "name":"PLAN.IGN", + * "title":"Plan IGN", + * "description":"Représentation vectorielle des bases de données IGN.", + * "globalConstraint":{ + * "crs":null, + * "bbox":{"left":-179.5,"right":179.5,"top":75,"bottom":-75}, + * "minScaleDenominator":2133, + * "maxScaleDenominator":559082265, + * "temporalExtent":["2017-05-23","2018-03-23"] + * }, + * "formats":[ + * {"current":true,"name":"application/x-protobuf"} + * ], + * "styles":[ + * { + * "name":"planign", + * "title":"Style de base", + * "current":true, + * "url":"https://wxs.ign.fr/static/vectorTiles/styles/PLAN.IGN/plan.json" + * },{ + * "name":"gris", + * "title":"Style en noir et blanc", + * "current":false, + * "url":"https://wxs.ign.fr/static/vectorTiles/styles/PLAN.IGN/gris.json" + * },{ + * "name":"sans_toponymes", + * "title":"Style sans toponymes", + * "current":false, + * "url":"https://wxs.ign.fr/static/vectorTiles/styles/PLAN.IGN/sans_toponymes.json" + * } + * ], + * "dimensions":{}, + * "thematics":[], + * "originators":[], + * "legends":[], + * "metadata":[], + * "apiKeys":["jhyvi0fgmnuxvfv0zjzorvdn"], + * "layerId":"PLAN.IGN::GEOPORTAIL:GPP:TMS", + * "defaultProjection":"EPSG:3857" + * } + */ + // eslint-disable-next-line no-undef + this.RESPONSE_AUTOCONG = null; + + // récupération des ressources utiles sur l'autoconf + var layerId = this.layerName + "$GEOPORTAIL:GPP:TMS"; + + var layerCfg = Config.configuration.layers[layerId]; + if (!layerCfg) { + throw new Error("ERROR : Layer ID not found into the catalogue !?"); + } + + this.styleUrl = null; + this.styleTitle = ""; + for (var i = 0; i < layerCfg.styles.length; i++) { + var style = layerCfg.styles[i]; + // si le nom du style est en option, on le recherche... + // sinon, on recherche le style par defaut ! + if (this.styleName && style.name === this.styleName) { + this.styleUrl = style.url; + this.styleTitle = style.title; + break; + } else { + if (!this.styleName && style.current) { + this.styleName = style.name; + this.styleUrl = style.url; + this.styleTitle = style.title; + break; + } + } + } + + if (!this.styleUrl) { + throw new Error("ERROR : Style URL not found !?"); + } + + this.styleUrl.replace(/(http|https):\/\//, this.protocol); + + // création de la source + var source = new VectorTileSource({ + state : "loading", // statut + format : new MVT() + }); + + source._originators = layerCfg.originators; + source._legends = layerCfg.legends; + source._metadata = layerCfg.metadata; + source._description = layerCfg.description; + source._title = layerCfg.title + " (" + this.styleTitle + ")"; + source._quicklookUrl = layerCfg.quicklookUrl; + + // options definies sur ol.layer.VectorTile + var layerVectorTileOptions = { + source : source + }; + + // récupération des autres paramètres passés par l'utilisateur + Utils.mergeParams(layerVectorTileOptions, settings); + + // création d'une ol.layer.VectorTile avec les options récupérées ci-dessus. + VectorTileLayer.call(this, layerVectorTileOptions); + + // récuperation du style + this.setStyleMapBox(); + } + + // Inherits from ol.layer.VectorTile + if (VectorTileLayer) LayerMapBox.__proto__ = VectorTileLayer; + + /* + * @lends module:LayerMapBox + */ + LayerMapBox.prototype = Object.create(VectorTileLayer.prototype, {}); + + /* + * Constructor (alias) + */ + LayerMapBox.prototype.constructor = LayerMapBox; + + /** + * Get Style MapBox + * @private + */ + LayerMapBox.prototype.setStyleMapBox = function () { + var self = this; + fetch(this.styleUrl, { + credentials : "same-origin" + }) + .then(function (response) { + if (response.ok) { + response.json().then(function (style) { + self.onStyleMapBoxLoad(style); + }); + } + }) + .catch(function (e) { + self.onStyleMapBoxError(e); + }); + }; + + /** + * Add Style + * @param {*} style - json style + */ + LayerMapBox.prototype.onStyleMapBoxLoad = function (style) { + // si on a plusieurs sources, on ne peut en prendre qu'une seule... + if (!this.sourceId) { + this.sourceId = Object.keys(style.sources)[0]; + } + + var styleSource = style.sources[this.sourceId]; + if (!styleSource) { + this.onStyleMapBoxError({ + message : "ERROR : Source ID not found !? !" + }); + return; + } + + if (styleSource.type !== "vector") { + this.onStyleMapBoxError({ + message : "ERROR : Source TYPE not permitted !" + }); + return; + } + + var source = this.getSource(); + + // WARNING : + // la clef renseignée dans les urls n'est pas forcement la bonne + // car la substitution avec la clef utilisateur n'est pas faite par le service... + if (styleSource.url) { + // protocole : http ou https + styleSource.url.replace(/(http|https):\/\//, this.protocol); + + var vectorTileJson = new TileJSONSource({ + url : styleSource.url + }); + var self = this; + var key = vectorTileJson.on("change", function () { + if (vectorTileJson.getState() === "ready") { + var doc = vectorTileJson.getTileJSON(); + if (!doc) { + return; + } + self.set("mapbox-extensions", doc); + var tiles = Array.isArray(doc.tiles) ? doc.tiles : [doc.tiles]; + // protocole : http ou https + for (var i = 0; i < styleSource.tiles.length; i++) { + tiles[i].replace(/(http|https):\/\//, this.protocol); + } + source.setUrls(tiles); + observableUnByKey(key); + } + }); + } + + if (styleSource.tiles) { + // protocole : http ou https + for (var j = 0; j < styleSource.tiles.length; j++) { + styleSource.tiles[j].replace(/(http|https):\/\//, this.protocol); + } + source.setUrls(styleSource.tiles); + } + + applyStyle(this, style, this.sourceId) + .then(() => { + source.setState("ready"); + this.set("mapbox-styles", style); + }) + .catch((error) => { + this.onStyleMapBoxError(error); + }); + }; + + /** + * Error + * @param {*} error - message + */ + LayerMapBox.prototype.onStyleMapBoxError = function (error) { + var source = this.getSource(); + source.setState("error"); + console.error(error.message); // eslint warning ! + }; + + return LayerMapBox; +}(VectorTileLayer)); + +export default LayerMapBox; + +// Expose LayerMapBox as ol.layer.GeoportalMapBox. (for a build bundle) +if (window.ol && window.ol.layer) { + window.ol.layer.GeoportalMapBox = LayerMapBox; +} diff --git a/src/OpenLayers/index.js b/src/OpenLayers/index.js index dfdd6b7e0..3bf535066 100644 --- a/src/OpenLayers/index.js +++ b/src/OpenLayers/index.js @@ -78,6 +78,7 @@ import SourceWMTS from "./Layers/SourceWMTS"; import SourceWMS from "./Layers/SourceWMS"; import LayerWMTS from "./Layers/LayerWMTS"; import LayerWMS from "./Layers/LayerWMS"; +import LayerMapBox from "./Layers/LayerMapBox"; import LayerSwitcher from "./Controls/LayerSwitcher"; import GetFeatureInfo from "./Controls/GetFeatureInfo"; import SearchEngine from "./Controls/SearchEngine"; @@ -94,6 +95,7 @@ import ElevationPath from "./Controls/ElevationPath"; import MeasureLength from "./Controls/Measures/MeasureLength"; import MeasureArea from "./Controls/Measures/MeasureArea"; import MeasureAzimuth from "./Controls/Measures/MeasureAzimuth"; +import ButtonExport from "./Controls/Export"; // import Proj4 from "proj4"; @@ -219,7 +221,7 @@ Ol.source.GeoportalWMS = SourceWMS; Ol.layer = Ol.layer || {}; Ol.layer.GeoportalWMTS = LayerWMTS; Ol.layer.GeoportalWMS = LayerWMS; - +Ol.layer.GeoportalMapBox = LayerMapBox; Ol.control = Ol.control || {}; Ol.control.LayerSwitcher = LayerSwitcher; Ol.control.GeoportalAttribution = GeoportalAttribution; @@ -237,6 +239,7 @@ Ol.control.MeasureAzimuth = MeasureAzimuth; Ol.control.DefaultMarkers = Markers; Ol.control.ElevationPath = ElevationPath; Ol.control.LocationSelector = LocationSelector; +Ol.control.Export = ButtonExport; // Expose extensions openlayers extended export {