From af7245b90277337e44427c0df7a307102fc481c1 Mon Sep 17 00:00:00 2001
From: ncastejon <castejon.nicolas@gmail.com>
Date: Fri, 8 Jun 2018 09:06:37 +0200
Subject: [PATCH] Add Openlayers map, WMS & WFS layers, popup and interaction.
 All WIP code. To refactor

---
 webapp/.angular-cli.json                      |   7 +-
 webapp/package-lock.json                      | 120 +++--
 webapp/package.json                           |  11 +-
 .../dataset-map/dataset-map.component.html    |   7 +-
 .../dataset-map/dataset-map.component.scss    |  45 +-
 .../dataset-map/dataset-map.component.ts      | 314 +++++++----
 webapp/src/assets/leaflet.wms.js              | 495 ------------------
 webapp/src/tsconfig.app.json                  |   6 +-
 8 files changed, 351 insertions(+), 654 deletions(-)
 delete mode 100644 webapp/src/assets/leaflet.wms.js

diff --git a/webapp/.angular-cli.json b/webapp/.angular-cli.json
index a0ecf192..9755499b 100644
--- a/webapp/.angular-cli.json
+++ b/webapp/.angular-cli.json
@@ -20,13 +20,10 @@
       "prefix": "app",
       "styles": [
         "styles.scss",
-        "../node_modules/leaflet/dist/leaflet.css",
-        "../node_modules/leaflet.markercluster/dist/MarkerCluster.css",
-        "../node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css"
+        "../node_modules/ol/ol.css"
       ],
       "scripts": [
-        "../node_modules/leaflet.markercluster/dist/leaflet.markercluster.js",
-        "../node_modules/proj4leaflet/lib/proj4-compressed.js"
+        "../node_modules/ol-layerswitcher/dist/ol-layerswitcher.js"
       ],
       "environmentSource": "environments/environment.ts",
       "environments": {
diff --git a/webapp/package-lock.json b/webapp/package-lock.json
index 00dd7ed8..38cf3225 100644
--- a/webapp/package-lock.json
+++ b/webapp/package-lock.json
@@ -283,11 +283,6 @@
         "semver-intersect": "1.3.1"
       }
     },
-    "@types/geojson": {
-      "version": "7946.0.3",
-      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.3.tgz",
-      "integrity": "sha512-BYHiG1vQJ7T93uswzuXZ0OBPWqj5tsAPtaMDQADV8sn2InllXarwg9llr6uaW22q1QCwBZ81gVajOpYWzjesug=="
-    },
     "@types/jasmine": {
       "version": "2.8.6",
       "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.6.tgz",
@@ -303,43 +298,30 @@
         "@types/jasmine": "2.8.6"
       }
     },
-    "@types/leaflet": {
-      "version": "1.2.7",
-      "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.2.7.tgz",
-      "integrity": "sha512-y9KEOzVB1uIPVPOc8FBqQjBvFykb/WhNfHDN6eTL8FlUy6F7FaqGMWHj5OhDSBHMWoqc70eR3FGiXdzbg1rqtg==",
-      "requires": {
-        "@types/geojson": "7946.0.3"
-      }
-    },
-    "@types/leaflet.markercluster": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.0.3.tgz",
-      "integrity": "sha512-rz4xQcsD3Ha9TcX4nMba9wpNe7HPQ03Hvo8Osi3SLpfaDCydHMoTquOG1IsjQ2aFm/LIHz4Uo4hYoeLv7q082w==",
-      "requires": {
-        "@types/leaflet": "1.2.7"
-      }
-    },
     "@types/node": {
       "version": "6.0.102",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.102.tgz",
       "integrity": "sha512-EhNufyBoC1Kqaj+XMHGzi6mPUC8wVABOMTPE5XaSJc49LIVvXsyrV1HYMAPTUViT7E4wLUB38OdDmb+HshjGmA==",
       "dev": true
     },
+    "@types/ol": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/@types/ol/-/ol-4.6.2.tgz",
+      "integrity": "sha512-nXmOOkNjrHpTLHIN6H1Y7q9u6s/uSSnUi1va9n37FFjtf8O6IYSAll/uzKv+jqNOoN9SFx/RYSyhRK8Hs460BA==",
+      "requires": {
+        "@types/openlayers": "4.6.9"
+      }
+    },
+    "@types/openlayers": {
+      "version": "4.6.9",
+      "resolved": "https://registry.npmjs.org/@types/openlayers/-/openlayers-4.6.9.tgz",
+      "integrity": "sha512-Q668pU0J+8QwrCTpk9Yz1jTHqjZv0+NvfZ1UuyucsaW8/BXnTzdsOfkInJRX22XGwFmE1AXcDkB9lydkwmCxCQ=="
+    },
     "@types/proj4": {
       "version": "2.3.4",
       "resolved": "https://registry.npmjs.org/@types/proj4/-/proj4-2.3.4.tgz",
       "integrity": "sha1-84i2AgnEy3XsIaF6S0rFWnGlhVg="
     },
-    "@types/proj4leaflet": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/@types/proj4leaflet/-/proj4leaflet-1.0.5.tgz",
-      "integrity": "sha512-xqw5V5wk0iSjpDSN2X1tuEo6qL4QhfffaTpnHN6ISz3vnT4Z4m4aKdH1vQHlpJ6aJ0TBvcRmI8X2/Lv7t/0qOw==",
-      "requires": {
-        "@types/geojson": "7946.0.3",
-        "@types/leaflet": "1.2.7",
-        "@types/proj4": "2.3.4"
-      }
-    },
     "@types/q": {
       "version": "0.0.32",
       "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz",
@@ -5388,8 +5370,7 @@
     "ieee754": {
       "version": "1.1.10",
       "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.10.tgz",
-      "integrity": "sha512-byWFX8OyW/qeVxcY21r6Ncxl0ZYHgnf0cPup2h34eHXrCJbOp7IuqnJ4Q0omfyWl6Z++BTI6bByf31pZt7iRLg==",
-      "dev": true
+      "integrity": "sha512-byWFX8OyW/qeVxcY21r6Ncxl0ZYHgnf0cPup2h34eHXrCJbOp7IuqnJ4Q0omfyWl6Z++BTI6bByf31pZt7iRLg=="
     },
     "iferr": {
       "version": "0.1.5",
@@ -6383,16 +6364,6 @@
         "invert-kv": "1.0.0"
       }
     },
-    "leaflet": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.3.1.tgz",
-      "integrity": "sha512-adQOIzh+bfdridLM1xIgJ9VnJbAUY3wqs/ueF+ITla+PLQ1z47USdBKUf+iD9FuUA8RtlT6j6hZBfZoA6mW+XQ=="
-    },
-    "leaflet.markercluster": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.3.0.tgz",
-      "integrity": "sha512-bHBaI8kTaLrZxI/ZP7pnNaS8WCKKz98BU1//B5OH9b/kTk6IriduvDtMFGlGBYYr/ymuXCB9lO7HsGBr5dSw5w=="
-    },
     "less": {
       "version": "2.7.3",
       "resolved": "https://registry.npmjs.org/less/-/less-2.7.3.tgz",
@@ -7815,6 +7786,21 @@
       "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
       "dev": true
     },
+    "ol": {
+      "version": "4.6.5",
+      "resolved": "https://registry.npmjs.org/ol/-/ol-4.6.5.tgz",
+      "integrity": "sha512-FtXsY3WY07c5vEP53rUkkw3jN7orcmg07lbmTfeS2hGyIZ0Noy1xC5urP+n6XTggnQZv0NlOkAt9lxNEmAQWQw==",
+      "requires": {
+        "pbf": "3.1.0",
+        "pixelworks": "1.1.0",
+        "rbush": "2.0.1"
+      }
+    },
+    "ol-layerswitcher": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ol-layerswitcher/-/ol-layerswitcher-2.0.0.tgz",
+      "integrity": "sha512-Gf1o+iZMR15aOTUUz5HKOgNtNRKR+VXm6JYzfe6VvF7QWVa5WTcsU2qdtcRHBOwOsMyxggZAFzUxre6g9Dg35Q=="
+    },
     "on-finished": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -8217,6 +8203,15 @@
         "pify": "3.0.0"
       }
     },
+    "pbf": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.1.0.tgz",
+      "integrity": "sha512-/hYJmIsTmh7fMkHAWWXJ5b8IKLWdjdlAFb3IHkRBn1XUhIYBChVGfVwmHEAV3UfXTxsP/AKfYTXTS/dCPxJd5w==",
+      "requires": {
+        "ieee754": "1.1.10",
+        "resolve-protobuf-schema": "2.0.0"
+      }
+    },
     "pbkdf2": {
       "version": "3.0.14",
       "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz",
@@ -8257,6 +8252,11 @@
         "pinkie": "2.0.4"
       }
     },
+    "pixelworks": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/pixelworks/-/pixelworks-1.1.0.tgz",
+      "integrity": "sha1-Hwla1I3Ki/ihyCWOAJIDGkTyLKU="
+    },
     "pkg-dir": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
@@ -8454,14 +8454,6 @@
         "wkt-parser": "1.2.1"
       }
     },
-    "proj4leaflet": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/proj4leaflet/-/proj4leaflet-1.0.2.tgz",
-      "integrity": "sha512-6GdDeUlhX/tHUiMEj80xQhlPjwrXcdfD0D5OBymY8WvxfbmZcdhNqQk7n7nFf53ue6QdP9ls9ZPjsAxnbZDTsw==",
-      "requires": {
-        "proj4": "2.4.4"
-      }
-    },
     "promise": {
       "version": "7.3.1",
       "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
@@ -8478,6 +8470,11 @@
       "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
       "dev": true
     },
+    "protocol-buffers-schema": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-2.2.0.tgz",
+      "integrity": "sha1-0pxs1z+2VZePtpiWkRgNuEQRn2E="
+    },
     "protractor": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.1.2.tgz",
@@ -8712,6 +8709,11 @@
       "integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=",
       "dev": true
     },
+    "quickselect": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz",
+      "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ=="
+    },
     "randomatic": {
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
@@ -8796,6 +8798,14 @@
       "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=",
       "dev": true
     },
+    "rbush": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.1.tgz",
+      "integrity": "sha1-TPrKKMMGS8DudUMaG3mZDode76k=",
+      "requires": {
+        "quickselect": "1.1.1"
+      }
+    },
     "read-cache": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -9171,6 +9181,14 @@
       "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
       "dev": true
     },
+    "resolve-protobuf-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.0.0.tgz",
+      "integrity": "sha1-5nsGKmfwLRG9aIbnDv2niEB+D7Q=",
+      "requires": {
+        "protocol-buffers-schema": "2.2.0"
+      }
+    },
     "resolve-url": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
diff --git a/webapp/package.json b/webapp/package.json
index 16e52070..ad1260a3 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -30,15 +30,14 @@
     "@angular/platform-browser": "^5.2.0",
     "@angular/platform-browser-dynamic": "^5.2.0",
     "@angular/router": "^5.2.0",
-    "@types/leaflet": "^1.2.7",
-    "@types/leaflet.markercluster": "^1.0.3",
-    "@types/proj4leaflet": "^1.0.5",
+    "@types/ol": "^4.6.2",
+    "@types/proj4": "^2.3.4",
     "bulma": "^0.7.1",
     "core-js": "^2.4.1",
     "font-awesome": "^4.7.0",
-    "leaflet": "^1.3.1",
-    "leaflet.markercluster": "^1.3.0",
-    "proj4leaflet": "^1.0.2",
+    "ol": "^4.6.5",
+    "ol-layerswitcher": "^2.0.0",
+    "proj4": "^2.4.4",
     "rxjs": "^5.5.6",
     "zone.js": "^0.8.19"
   },
diff --git a/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.html b/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.html
index c0c8e3ce..6b38e6a0 100644
--- a/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.html
+++ b/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.html
@@ -1,3 +1,6 @@
-<div id="frugalmap">
- 
+<div id="map">
+</div>
+<div id="popup" class="ol-popup">
+  <a href="#" id="popup-closer" class="ol-popup-closer"></a>
+  <div id="popup-content"></div>
 </div>
\ No newline at end of file
diff --git a/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.scss b/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.scss
index 4fb3c0b7..a8b33f5f 100644
--- a/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.scss
+++ b/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.scss
@@ -1,3 +1,46 @@
-#frugalmap {
+#map {
   height: 600px;
 }
+
+.ol-popup {
+  position: absolute;
+  background-color: white;
+  -webkit-filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2));
+  filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2));
+  padding: 15px;
+  border-radius: 10px;
+  border: 1px solid #cccccc;
+  bottom: 12px;
+  left: -50px;
+  min-width: 280px;
+}
+.ol-popup:after, .ol-popup:before {
+  top: 100%;
+  border: solid transparent;
+  content: " ";
+  height: 0;
+  width: 0;
+  position: absolute;
+  pointer-events: none;
+}
+.ol-popup:after {
+  border-top-color: white;
+  border-width: 10px;
+  left: 48px;
+  margin-left: -10px;
+}
+.ol-popup:before {
+  border-top-color: #cccccc;
+  border-width: 11px;
+  left: 48px;
+  margin-left: -11px;
+}
+.ol-popup-closer {
+  text-decoration: none;
+  position: absolute;
+  top: 2px;
+  right: 8px;
+}
+.ol-popup-closer:after {
+  content: "✖";
+}
\ No newline at end of file
diff --git a/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.ts b/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.ts
index bfa9bc44..3e866bdc 100644
--- a/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.ts
+++ b/webapp/src/app/geosource/components/dataset-detail/dataset-map/dataset-map.component.ts
@@ -1,12 +1,34 @@
 import { Component, OnInit, Input } from '@angular/core';
-import * as L from 'leaflet';
-import 'leaflet.markercluster';
-import 'proj4leaflet';
 import { ActivatedRoute, ParamMap } from '@angular/router';
 import { DatasetService } from '../../../services';
 import { Metadata, Data } from '../../../models';
 import { MapService } from '../../../services/map.service';
-import * as wms from '../../../../../assets/leaflet.wms.js';
+import proj4 from 'proj4';
+import olMap from 'ol/map';
+import olView from 'ol/view';
+import olOverlay from 'ol/overlay';
+import olSourceOSM from 'ol/source/osm';
+import olTileWMS from 'ol/source/tilewms';
+import olSourceXYZ from 'ol/source/xyz';
+import olPoint from 'ol/geom/point';
+import olLayerTile from 'ol/layer/tile';
+import olLayerVector from 'ol/layer/vector';
+import olTileImage from 'ol/source/tileimage';
+import olFormatGeoJson from 'ol/format/geojson';
+import olFormatWFS from 'ol/format/wfs';
+import olSourceVector from 'ol/source/vector';
+import olSourceCluster from 'ol/source/cluster';
+import olTileGrid from 'ol/tilegrid/tilegrid';
+import olProj from 'ol/proj';
+import olLoadingStrategy from 'ol/loadingstrategy';
+import olStyle from 'ol/style/style';
+import olStroke from 'ol/style/stroke';
+import olFill from 'ol/style/fill';
+import olCircle from 'ol/style/circle';
+import olSelect from 'ol/interaction/select';
+import olModify from 'ol/interaction/modify';
+import olInteraction from 'ol/interaction';
+import olDragRotateAndZoom from 'ol/interaction/dragrotateandzoom';
 
 @Component({
   selector: 'app-dataset-map',
@@ -32,110 +54,220 @@ export class DatasetMapComponent implements OnInit {
   }
   constructMap(metadata: Metadata) {
     // Déclaration de la carte avec les coordonnées du centre et le niveau de zoom.
-    const attribution = 'Data Grand Lyon';
-    const satellite = L.tileLayer('https://openstreetmap.data.grandlyon.com/3857/tms/1.0.0/ortho2015@GoogleMapsCompatible/{z}/{x}/{-y}.png', { id: 'MapID', attribution: attribution });
-    const grayscale = L.tileLayer('https://openstreetmap.data.grandlyon.com/3857/tms/1.0.0/osm_grandlyon_nb@GoogleMapsCompatible/{z}/{x}/{-y}.png', { id: 'MapID', attribution: attribution });
-    const plan = L.tileLayer('https://openstreetmap.data.grandlyon.com/3857/tms/1.0.0/osm_grandlyon@GoogleMapsCompatible/{z}/{x}/{-y}.png', { id: 'MapID', attribution: attribution });
+    // const attribution = 'Data Grand Lyon';
+    // const satellite = L.tileLayer('https://openstreetmap.data.grandlyon.com/3857/tms/1.0.0/ortho2015@GoogleMapsCompatible/{z}/{x}/{-y}.png', { id: 'MapID', attribution: attribution });
+    // const grayscale = L.tileLayer('https://openstreetmap.data.grandlyon.com/3857/tms/1.0.0/osm_grandlyon_nb@GoogleMapsCompatible/{z}/{x}/{-y}.png', { id: 'MapID', attribution: attribution });
+    // const plan = L.tileLayer('https://openstreetmap.data.grandlyon.com/3857/tms/1.0.0/osm_grandlyon@GoogleMapsCompatible/{z}/{x}/{-y}.png', { id: 'MapID', attribution: attribution });
 
-    const dataGrandLyonMap = L.map('frugalmap', { minZoom: 9, maxZoom: 18, layers: [plan] })
-      .setView([45.75, 4.85], 12);
+    // const dataGrandLyonMap = L.map('frugalmap', { minZoom: 9, maxZoom: 18, layers: [plan] })
+    //   .setView([45.75, 4.85], 12);
 
 
-    const baseMaps = {
-      'Plan': plan,
-      'Niveau de gris': grayscale,
-      'Satellite': satellite
+    // const baseMaps = {
+    //   'Plan': plan,
+    //   'Niveau de gris': grayscale,
+    //   'Satellite': satellite
 
+    // };
+    // L.control.layers(baseMaps).addTo(dataGrandLyonMap);
+    // // Set icon marker
+    // const myIcon = L.icon({
+    //   iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/images/marker-icon.png'
+    // });
+
+    const container = document.getElementById('popup');
+    const content = document.getElementById('popup-content');
+    const closer = document.getElementById('popup-closer');
+    const overlay = new olOverlay({
+      element: container,
+      autoPan: true,
+      autoPanAnimation: {
+        duration: 250
+      }
+    });
+    /**
+     * Add a click handler to hide the popup.
+     * @return {boolean} Don't follow the href.
+     */
+    closer.onclick = function () {
+      overlay.setPosition(undefined);
+      closer.blur();
+      return false;
+    };
+
+    const defaultStyle = {
+      'Point': new olStyle({
+        image: new olCircle({
+          fill: new olFill({
+            color: 'lightgreen'
+          }),
+          radius: 8,
+          stroke: new olStroke({
+            color: 'red',
+            width: 1
+          })
+        })
+      }),
+      'LineString': new olStyle({
+        stroke: new olStroke({
+          color: '#f00',
+          width: 3
+        })
+      }),
+      'Polygon': new olStyle({
+        fill: new olFill({
+          color: 'rgba(170, 214, 204, 0.2)',
+        }),
+        stroke: new olStroke({
+          color: 'blue',
+          width: 1
+        })
+      }),
+      'MultiPoint': new olStyle({
+        image: new olCircle({
+          fill: new olFill({
+            color: 'rgba(255,0,255,0.5)'
+          }),
+          radius: 5,
+          stroke: new olStroke({
+            color: '#f0f',
+            width: 1
+          })
+        })
+      }),
+      'MultiLineString': new olStyle({
+        stroke: new olStroke({
+          color: '#0f0',
+          width: 3
+        })
+      }),
+      'MultiPolygon': new olStyle({
+        fill: new olFill({
+          color: 'rgba(0,0,255,0.5)'
+        }),
+        stroke: new olStroke({
+          color: '#00f',
+          width: 1
+        })
+      })
+    };
+
+    const styleFunction = function (feature, resolution) {
+      const featureStyleFunction = feature.getStyleFunction();
+      if (featureStyleFunction) {
+        return featureStyleFunction.call(feature, resolution);
+      } else {
+        return defaultStyle[feature.getGeometry().getType()];
+      }
     };
-    L.control.layers(baseMaps).addTo(dataGrandLyonMap);
-    // Set icon marker
-    const myIcon = L.icon({
-      iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/images/marker-icon.png'
+
+
+    const map = new olMap({
+      target: 'map',
+      overlays: [overlay],
+      interactions: olInteraction.defaults().extend([
+        new olDragRotateAndZoom()
+      ]),
+    });
+
+    const planLayer = new olLayerTile({
+      source: new olSourceXYZ({
+        attributions: ['Grand Lyon'],
+        url: 'https://openstreetmap.data.grandlyon.com/3857/tms/1.0.0/osm_grandlyon@GoogleMapsCompatible/{z}/{x}/{-y}.png',
+      })
     });
+    olProj.setProj4(proj4);
+    proj4.defs('EPSG:4171', '+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs ');
+    map.setView(new olView({
+      maxZoom: 18,
+      center: olProj.fromLonLat([4.85, 45.75]),
+      zoom: 12
+    }));
+
+    map.addLayer(planLayer);
 
 
-    const group = [];
-    const cluster = L.markerClusterGroup({
-      maxClusterRadius: 120
+    /* Lyon image */
+    const vectorSource = new olSourceVector({
+      format: new olFormatGeoJson(),
+      url: function (extent) {
+        return 'https://download.data.grandlyon.com/wfs/grandlyon?SERVICE=WFS&VERSION=2.0.0&outputformat=GEOJSON&maxfeatures=30&request=GetFeature&typename=' + metadata.uri[0].name + '&srsname=EPSG:3857&' +
+          ',EPSG:3857';
+      },
+      strategy: olLoadingStrategy.bbox,
     });
-    const hasWFS = metadata.uri.filter(function (e) { return e.protocol === 'OGC:WFS'; }).length > 0;
-    metadata.uri.forEach(element => {
-      if (hasWFS && element.protocol === 'OGC:WFS') {
-        const options = {
-          'name': element.name
-        };
-        this.mapService.getWFS(options).subscribe(results => {
-          console.log(results);
-          if (results.features[0].geometry.type === 'Point') {
-            L.geoJSON(results, {
-              pointToLayer: function (feature, latlng) {
-                return L.marker(latlng, { icon: myIcon }); // The basic style
-              }
-            }).addTo(dataGrandLyonMap);
-          } else if (results.features[0].geometry.type === 'Polygon') {
-            L.geoJSON(results, {
-              onEachFeature: function (feature, layer) {
-                let popContent = '';
-                for (const key in feature.properties) {
-                  if (feature.properties.hasOwnProperty(key)) {
-                    popContent += '<p>' + key + ': ' + feature.properties[key] + '</p>';
-                  }
-                }
-                layer.bindPopup(popContent);
-              }
-            }).addTo(dataGrandLyonMap);
-          }
-        });
-      } else if (!hasWFS && element.protocol === 'OGC:WMS') {
-        const crs = new L.Proj.CRS('EPSG:4171',
-      '+proj=longlat +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +no_defs ');
-      L.tileLayer.wms('https://download.data.grandlyon.com/wms/grandlyon?', {
-          crs: crs,
-          transparent: true,
-          layers: element.name,
-          format: 'image/png',
-          zIndex: 1000
-        }).addTo(dataGrandLyonMap);
-        // source.addSubLayer(element.name);
-        // source.addTo(dataGrandLyonMap);
-      }
+
+
+    const layerCluster = new olLayerVector({
+      source: vectorSource,
+      style: styleFunction,
+      zIndex: 1000
+    });
+
+    const modify = new olModify({ source: vectorSource });
+    map.addInteraction(modify);
+    const modifiedFeatures = [];
+    modify.on('modifyend', function(e) {
+        const features = e['features'].getArray();
+        console.log(features.length);
+        for (let i = 0; i < features.length; i ++) {
+            const rev = features[i].getRevision();
+            if (rev > 1) {
+                console.log('feature with revision:' + rev + ' has been modified');
+                modifiedFeatures.push(features[i]);
+            }
+        }
+        console.log(modifiedFeatures);
     });
+    map.addLayer(layerCluster);
+
 
+    const displayFeatureInfo = function (pixel, coordinate) {
+      const features = [];
+      map.forEachFeatureAtPixel(pixel, function (feature, layer) {
+        console.log(feature);
+        content.innerHTML = '<p>You clicked here:</p><code>' + feature['values_'].identifiant +
+          '</code>';
+        overlay.setPosition(coordinate);
+      });
+
+    };
 
-    // results.forEach(element => {
-    //   console.log(element);
-
-    //   switch (element.geometry.type) {
-    //     case 'Point':
-    //       const marker = L.marker([element.geometry.coordinates[1], element.geometry.coordinates[0]], { icon: myIcon })
-    //         .bindPopup(element.properties.nom).addTo(dataGrandLyonMap);
-    //       // group.push(marker);
-    //       cluster.addLayer(marker);
-    //       break;
-    //     case 'Polygon':
-    //       // create a red polygon from an array of LatLng points
-    //       element.geometry.coordinates.forEach(coordinates => {
-    //         coordinates.forEach(coord => {
-    //           coord.reverse();
-    //         });
-    //         const polygon = L.polygon(coordinates, { color: 'red' }).addTo(dataGrandLyonMap);
-    //         group.push(polygon);
-    //       });
-
-    //       break;
-    //   }
+    // map.on('click', function (evt) {
+    //   const coordinate = evt['coordinate'];
+    //   const pixel = evt['pixel'];
+    //   displayFeatureInfo(pixel, coordinate);
     // });
-    // dataGrandLyonMap.addLayer(cluster);
-    // const featureGroup = L.featureGroup(group);
-    // // zoom the map to the polygon
-    // dataGrandLyonMap.fitBounds(featureGroup.getBounds());
-    L.control.scale().addTo(dataGrandLyonMap);
+
+    // WMS Functionnality -----------------------
+    const wmsSource = new olTileWMS({
+      url: 'https://download.data.grandlyon.com/wms/grandlyon?',
+      params: { 'LAYERS': metadata.uri[0].name, 'TILED': true },
+      // Countries have transparency, so do not fade tiles:
+      // transition: 0
+    });
+    const layerTile = new olLayerTile({
+      source: wmsSource
+    });
+    map.on('click', function (evt) {
+
+      const viewResolution = /** @type {number} */ (map.getView().getResolution());
+      console.log(viewResolution);
+
+      const url = wmsSource.getGetFeatureInfoUrl(
+        evt['coordinate'], viewResolution, 'EPSG:3857',
+        { 'INFO_FORMAT': 'text/html' });
+      console.log(url);
+      if (url) {
+        content.innerHTML = '<p>You clicked here:</p><code>' + url +
+          '</code>';
+
+      }
+    });
   }
 
   onEachFeature(feature, layer) {
     // does this feature have a property named popupContent?
-    console.log(feature);
-    console.log(layer);
     if (feature.properties && feature.properties.nom) {
       layer.bindPopup(feature.properties.nom);
     }
diff --git a/webapp/src/assets/leaflet.wms.js b/webapp/src/assets/leaflet.wms.js
deleted file mode 100644
index 6a65d604..00000000
--- a/webapp/src/assets/leaflet.wms.js
+++ /dev/null
@@ -1,495 +0,0 @@
-/*!
- * leaflet.wms.js
- * A collection of Leaflet utilities for working with Web Mapping services.
- * (c) 2014-2016, Houston Engineering, Inc.
- * MIT License
- */
-
-(function (factory) {
-  // Module systems magic dance, Leaflet edition
-  if (typeof define === 'function' && define.amd) {
-      // AMD
-      define(['leaflet'], factory);
-  } else if (typeof module !== 'undefined') {
-      // Node/CommonJS
-      module.exports = factory(require('leaflet'));
-  } else {
-      // Browser globals
-      if (typeof this.L === 'undefined')
-          throw 'Leaflet must be loaded first!';
-      // Namespace
-      this.L.WMS = this.L.wms = factory(this.L);
-  }
-}(function (L) {
-
-// Module object
-var wms = {};
-
-// Quick shim for Object.keys()
-if (!('keys' in Object)) {
-  Object.keys = function(obj) {
-      var result = [];
-      for (var i in obj) {
-          if (obj.hasOwnProperty(i)) {
-              result.push(i);
-          }
-      }
-      return result;
-  };
-}
-
-/*
-* wms.Source
-* The Source object manages a single WMS connection.  Multiple "layers" can be
-* created with the getLayer function, but a single request will be sent for
-* each image update.  Can be used in non-tiled "overlay" mode (default), or
-* tiled mode, via an internal wms.Overlay or wms.TileLayer, respectively.
-*/
-wms.Source = L.Layer.extend({
-  'options': {
-      'untiled': true,
-      'identify': true
-  },
-
-  'initialize': function(url, options) {
-      L.setOptions(this, options);
-      if (this.options.tiled) {
-          this.options.untiled = false;
-      }
-      this._url = url;
-      this._subLayers = {};
-      this._overlay = this.createOverlay(this.options.untiled);
-  },
-
-  'createOverlay': function(untiled) {
-      // Create overlay with all options other than untiled & identify
-      var overlayOptions = {};
-      for (var opt in this.options) {
-          if (opt != 'untiled' && opt != 'identify') {
-              overlayOptions[opt] = this.options[opt];
-          }
-      }
-      if (untiled) {
-          return wms.overlay(this._url, overlayOptions);
-      } else {
-          return wms.tileLayer(this._url, overlayOptions);
-      }
-  },
-
-  'onAdd': function() {
-      this.refreshOverlay();
-  },
-
-  'getEvents': function() {
-      if (this.options.identify) {
-          return {'click': this.identify};
-      } else {
-          return {};
-      }
-  },
-
-  'setOpacity': function(opacity) {
-       this.options.opacity = opacity;
-       if (this._overlay) {
-           this._overlay.setOpacity(opacity);
-       }
-  },
-  
-  'bringToBack': function() {
-       this.options.isBack = true;
-       if (this._overlay) {
-           this._overlay.bringToBack();
-       }
-  },
-
-  'bringToFront': function() {
-       this.options.isBack = false;
-       if (this._overlay) {
-           this._overlay.bringToFront();
-       }
-  },
-
-  'getLayer': function(name) {
-      return wms.layer(this, name);
-  },
-
-  'addSubLayer': function(name) {
-      this._subLayers[name] = true;
-      this.refreshOverlay();
-  },
-
-  'removeSubLayer': function(name) {
-      delete this._subLayers[name];
-      this.refreshOverlay();
-  },
-
-  'refreshOverlay': function() {
-      var subLayers = Object.keys(this._subLayers).join(",");
-      if (!this._map) {
-          return;
-      }
-      if (!subLayers) {
-          this._overlay.remove();
-      } else {
-          this._overlay.setParams({'layers': subLayers});
-          this._overlay.addTo(this._map);
-      }
-  },
-
-  'identify': function(evt) {
-      // Identify map features in response to map clicks. To customize this
-      // behavior, create a class extending wms.Source and override one or
-      // more of the following hook functions.
-
-      var layers = this.getIdentifyLayers();
-      if (!layers.length) {
-          return;
-      }
-      this.getFeatureInfo(
-          evt.containerPoint, evt.latlng, layers,
-          this.showFeatureInfo
-      );
-  },
-
-  'getFeatureInfo': function(point, latlng, layers, callback) {
-      // Request WMS GetFeatureInfo and call callback with results
-      // (split from identify() to faciliate use outside of map events)
-      var params = this.getFeatureInfoParams(point, layers),
-          url = this._url + L.Util.getParamString(params, this._url);
-
-      this.showWaiting();
-      this.ajax(url, done);
-
-      function done(result) {
-          this.hideWaiting();
-          var text = this.parseFeatureInfo(result, url);
-          callback.call(this, latlng, text);
-      }
-  },
-
-  'ajax': function(url, callback) {
-      ajax.call(this, url, callback);
-  },
-
-  'getIdentifyLayers': function() {
-      // Hook to determine which layers to identify
-      if (this.options.identifyLayers)
-          return this.options.identifyLayers;
-      return Object.keys(this._subLayers);
-   },
-
-  'getFeatureInfoParams': function(point, layers) {
-      // Hook to generate parameters for WMS service GetFeatureInfo request
-      var wmsParams, overlay;
-      if (this.options.untiled) {
-          // Use existing overlay
-          wmsParams = this._overlay.wmsParams;
-      } else {
-          // Create overlay instance to leverage updateWmsParams
-          overlay = this.createOverlay(true);
-          overlay.updateWmsParams(this._map);
-          wmsParams = overlay.wmsParams;
-          wmsParams.layers = layers.join(',');
-      }
-      var infoParams = {
-          'request': 'GetFeatureInfo',
-          'query_layers': layers.join(','),
-          'X': Math.round(point.x),
-          'Y': Math.round(point.y)
-      };
-      return L.extend({}, wmsParams, infoParams);
-  },
-
-  'parseFeatureInfo': function(result, url) {
-      // Hook to handle parsing AJAX response
-      if (result == "error") {
-          // AJAX failed, possibly due to CORS issues.
-          // Try loading content in <iframe>.
-          result = "<iframe src='" + url + "' style='border:none'>";
-      }
-      return result;
-  },
-
-  'showFeatureInfo': function(latlng, info) {
-      // Hook to handle displaying parsed AJAX response to the user
-      if (!this._map) {
-          return;
-      }
-      this._map.openPopup(info, latlng);
-  },
-
-  'showWaiting': function() {
-      // Hook to customize AJAX wait animation
-      if (!this._map)
-          return;
-      this._map._container.style.cursor = "progress";
-  },
-
-  'hideWaiting': function() {
-      // Hook to remove AJAX wait animation
-      if (!this._map)
-          return;
-      this._map._container.style.cursor = "default";
-  }
-});
-
-wms.source = function(url, options) {
-  return new wms.Source(url, options);
-};
-
-/*
-* Layer
-* Leaflet "layer" with all actual rendering handled via an underlying Source
-* object.  Can be called directly with a URL to automatically create or reuse
-* an existing Source.  Note that the auto-source feature doesn't work well in
-* multi-map environments; so for best results, create a Source first and use
-* getLayer() to retrieve wms.Layer instances.
-*/
-
-wms.Layer = L.Layer.extend({
-  'initialize': function(source, layerName, options) {
-      L.setOptions(this, options);
-      if (!source.addSubLayer) {
-          // Assume source is a URL
-          source = wms.getSourceForUrl(source, options);
-      }
-      this._source = source;
-      this._name = layerName;
-  },
-  'onAdd': function() {
-      if (!this._source._map)
-          this._source.addTo(this._map);
-      this._source.addSubLayer(this._name);
-  },
-  'onRemove': function() {
-      this._source.removeSubLayer(this._name);
-  },
-  'setOpacity': function(opacity) {
-      this._source.setOpacity(opacity);
-  },
-  'bringToBack': function() {
-      this._source.bringToBack();
-  },
-  'bringToFront': function() {
-      this._source.bringToFront();
-  }
-});
-
-wms.layer = function(source, options) {
-  return new wms.Layer(source, options);
-};
-
-// Cache of sources for use with wms.Layer auto-source option
-var sources = {};
-wms.getSourceForUrl = function(url, options) {
-  if (!sources[url]) {
-      sources[url] = wms.source(url, options);
-  }
-  return sources[url];
-};
-
-
-// Copy tiled WMS layer from leaflet core, in case we need to subclass it later
-wms.TileLayer = L.TileLayer.WMS;
-wms.tileLayer = L.tileLayer.wms;
-
-/*
-* wms.Overlay:
-* "Single Tile" WMS image overlay that updates with map changes.
-* Portions of wms.Overlay are directly extracted from L.TileLayer.WMS.
-* See Leaflet license.
-*/
-wms.Overlay = L.Layer.extend({
-  'defaultWmsParams': {
-      'service': 'WMS',
-      'request': 'GetMap',
-      'version': '1.3.0',
-      'layers': '',
-      'styles': '',
-      'format': 'image/jpeg',
-      'transparent': false
-  },
-
-  'options': {
-      'crs': null,
-      'uppercase': false,
-      'attribution': '',
-      'opacity': 1,
-      'isBack': false,
-      'minZoom': 0,
-      'maxZoom': 18
-  },
-
-  'initialize': function(url, options) {
-      this._url = url;
-
-      // Move WMS parameters to params object
-      var params = {}, opts = {};
-      for (var opt in options) {
-           if (opt in this.options) {
-               opts[opt] = options[opt];
-           } else {
-               params[opt] = options[opt];
-           }
-      }
-      L.setOptions(this, opts);
-      this.wmsParams = L.extend({}, this.defaultWmsParams, params);
-  },
-
-  'setParams': function(params) {
-      L.extend(this.wmsParams, params);
-      this.update();
-  },
-
-  'getAttribution': function() {
-      return this.options.attribution;
-  },
-
-  'onAdd': function() {
-      this.update();
-  },
-
-  'onRemove': function(map) {
-      if (this._currentOverlay) {
-          map.removeLayer(this._currentOverlay);
-          delete this._currentOverlay;
-      }
-      if (this._currentUrl) {
-          delete this._currentUrl;
-      }
-  },
-
-  'getEvents': function() {
-      return {
-          'moveend': this.update
-      };
-  },
-
-  'update': function() {
-      if (!this._map) {
-          return;
-      }
-      // Determine image URL and whether it has changed since last update
-      this.updateWmsParams();
-      var url = this.getImageUrl();
-      if (this._currentUrl == url) {
-          return;
-      }
-      this._currentUrl = url;
-
-      // Keep current image overlay in place until new one loads
-      // (inspired by esri.leaflet)
-      var bounds = this._map.getBounds();
-      var overlay = L.imageOverlay(url, bounds, {'opacity': 0});
-      overlay.addTo(this._map);
-      overlay.once('load', _swap, this);
-      function _swap() {
-          if (!this._map) {
-              return;
-          }
-          if (overlay._url != this._currentUrl) {
-              this._map.removeLayer(overlay);
-              return;
-          } else if (this._currentOverlay) {
-              this._map.removeLayer(this._currentOverlay);
-          }
-          this._currentOverlay = overlay;
-          overlay.setOpacity(
-              this.options.opacity ? this.options.opacity : 1
-          );
-          if (this.options.isBack === true) {
-              overlay.bringToBack();
-          }
-          if (this.options.isBack === false) {
-              overlay.bringToFront();
-          }
-      }
-      if ((this._map.getZoom() < this.options.minZoom) ||
-          (this._map.getZoom() > this.options.maxZoom)){
-          this._map.removeLayer(overlay);
-      }
-  },
-
-  'setOpacity': function(opacity) {
-       this.options.opacity = opacity;
-       if (this._currentOverlay) {
-           this._currentOverlay.setOpacity(opacity);
-       }
-  },
-
-  'bringToBack': function() {
-      this.options.isBack = true;
-      if (this._currentOverlay) {
-          this._currentOverlay.bringToBack();
-      }
-  },
-
-  'bringToFront': function() {
-      this.options.isBack = false;
-      if (this._currentOverlay) {
-          this._currentOverlay.bringToFront();
-      }
-  },
-
-  // See L.TileLayer.WMS: onAdd() & getTileUrl()
-  'updateWmsParams': function(map) {
-      if (!map) {
-          map = this._map;
-      }
-      // Compute WMS options
-      var bounds = map.getBounds();
-      var size = map.getSize();
-      var wmsVersion = parseFloat(this.wmsParams.version);
-      var crs = this.options.crs || map.options.crs;
-      var projectionKey = wmsVersion >= 1.3 ? 'crs' : 'srs';
-      var nw = crs.project(bounds.getNorthWest());
-      var se = crs.project(bounds.getSouthEast());
-
-      // Assemble WMS parameter string
-      var params = {
-          'width': size.x,
-          'height': size.y
-      };
-      params[projectionKey] = crs.code;
-      params.bbox = (
-          wmsVersion >= 1.3 && crs === L.CRS.EPSG4326 ?
-          [se.y, nw.x, nw.y, se.x] :
-          [nw.x, se.y, se.x, nw.y]
-      ).join(',');
-
-      L.extend(this.wmsParams, params);
-  },
-
-  'getImageUrl': function() {
-      var uppercase = this.options.uppercase || false;
-      var pstr = L.Util.getParamString(this.wmsParams, this._url, uppercase);
-      return this._url + pstr;
-  }
-});
-
-wms.overlay = function(url, options) {
-  return new wms.Overlay(url, options);
-};
-
-// Simple AJAX helper (since we can't assume jQuery etc. are present)
-function ajax(url, callback) {
-  var context = this,
-      request = new XMLHttpRequest();
-  request.onreadystatechange = change;
-  request.open('GET', url);
-  request.send();
-
-  function change() {
-      if (request.readyState === 4) {
-          if (request.status === 200) {
-              callback.call(context, request.responseText);
-          } else {
-              callback.call(context, "error");
-          }
-      }
-  }
-}
-
-return wms;
-
-}));
diff --git a/webapp/src/tsconfig.app.json b/webapp/src/tsconfig.app.json
index edeee7ac..a4ef5de7 100644
--- a/webapp/src/tsconfig.app.json
+++ b/webapp/src/tsconfig.app.json
@@ -3,12 +3,12 @@
   "compilerOptions": {
     "outDir": "../out-tsc/app",
     "baseUrl": "./",
+    "allowSyntheticDefaultImports": true,
     "allowJs": true,
     "module": "es2015",
     "types": [
-      "leaflet",
-      "leaflet.markercluster",
-      "proj4leaflet"
+      "ol",
+      "proj4"
     ]
   },
   "exclude": [
-- 
GitLab