From f7e069dc27559e66b59f4055182a2a942f8a87fe Mon Sep 17 00:00:00 2001
From: esikkala <esko.ikkala@aalto.fi>
Date: Thu, 25 Mar 2021 09:30:51 +0200
Subject: [PATCH] LeafletMap: add proper configs for GeoJSON layers

---
 package-lock.json                             |  83 ++++++
 package.json                                  |   3 +-
 .../components/facet_results/LeafletMap.js    | 252 +++++-------------
 .../perspectives/sampo/Perspective1.js        | 112 +++++++-
 src/server/index.js                           |  34 +++
 src/server/openapi.yaml                       |  66 ++++-
 6 files changed, 350 insertions(+), 200 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index b62d35da..d495f20d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5203,6 +5203,69 @@
         }
       }
     },
+    "@turf/bbox": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.3.0.tgz",
+      "integrity": "sha512-N4ue5Xopu1qieSHP2MA/CJGWHPKaTrVXQJjzHRNcY1vtsO126xbSaJhWUrFc5x5vVkXp0dcucGryO0r5m4o/KA==",
+      "requires": {
+        "@turf/helpers": "^6.3.0",
+        "@turf/meta": "^6.3.0"
+      }
+    },
+    "@turf/buffer": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-6.3.0.tgz",
+      "integrity": "sha512-B0GWgJzmTaaw1GvTd+Df+ToKSYphz9d6hPCOwXbE2vS5DdZryoxBfxQ32LSX/hW/vx7TLf7E4M0VJBb+Sn1DKA==",
+      "requires": {
+        "@turf/bbox": "^6.3.0",
+        "@turf/center": "^6.3.0",
+        "@turf/helpers": "^6.3.0",
+        "@turf/meta": "^6.3.0",
+        "@turf/projection": "^6.3.0",
+        "d3-geo": "1.7.1",
+        "turf-jsts": "*"
+      }
+    },
+    "@turf/center": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@turf/center/-/center-6.3.0.tgz",
+      "integrity": "sha512-41g/ZYwoBs2PK7tpAHhf4D6llHdRvY827HLXCld5D0IOnzsWPqDk7WnV8P5uq4g/gyH1/WfKQYn5SgfSj4sSfw==",
+      "requires": {
+        "@turf/bbox": "^6.3.0",
+        "@turf/helpers": "^6.3.0"
+      }
+    },
+    "@turf/clone": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-6.3.0.tgz",
+      "integrity": "sha512-GAgN89/9GCqUKECB1oY2hcTs0K2rZj+a2tY6VfM0ef9wwckuQZCKi+kKGUzhKVrmHee15jKV8n6DY0er8OndKg==",
+      "requires": {
+        "@turf/helpers": "^6.3.0"
+      }
+    },
+    "@turf/helpers": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.3.0.tgz",
+      "integrity": "sha512-kr6KuD4Z0GZ30tblTEvi90rvvVNlKieXuMC8CTzE/rVQb0/f/Cb29zCXxTD7giQTEQY/P2nRW23wEqqyNHulCg=="
+    },
+    "@turf/meta": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.3.0.tgz",
+      "integrity": "sha512-qBJjaAJS9H3ap0HlGXyF/Bzfl0qkA9suafX/jnDsZvWMfVLt+s+o6twKrXOGk5t7nnNON2NFRC8+czxpu104EQ==",
+      "requires": {
+        "@turf/helpers": "^6.3.0"
+      }
+    },
+    "@turf/projection": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-6.3.0.tgz",
+      "integrity": "sha512-IpSs7Q6G6xi47ynVlYYVegPLy6Jc0yo3/DcIm83jaJa4NnzPFXIFZT0v9Fe1N8MraHZqiqaSPbVnJXCGwR12lg==",
+      "requires": {
+        "@turf/clone": "^6.3.0",
+        "@turf/helpers": "^6.3.0",
+        "@turf/meta": "^6.3.0"
+      }
+    },
     "@types/anymatch": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
@@ -9661,6 +9724,21 @@
       "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
       "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA=="
     },
+    "d3-geo": {
+      "version": "1.7.1",
+      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz",
+      "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==",
+      "requires": {
+        "d3-array": "1"
+      },
+      "dependencies": {
+        "d3-array": {
+          "version": "1.2.4",
+          "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
+          "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
+        }
+      }
+    },
     "d3-hexbin": {
       "version": "0.2.2",
       "resolved": "https://registry.npmjs.org/d3-hexbin/-/d3-hexbin-0.2.2.tgz",
@@ -23983,6 +24061,11 @@
         "safe-buffer": "^5.0.1"
       }
     },
+    "turf-jsts": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz",
+      "integrity": "sha512-Ja03QIJlPuHt4IQ2FfGex4F4JAr8m3jpaHbFbQrgwr7s7L6U8ocrHiF3J1+wf9jzhGKxvDeaCAnGDot8OjGFyA=="
+    },
     "tweetnacl": {
       "version": "0.14.5",
       "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
diff --git a/package.json b/package.json
index 342c34da..d4792197 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,8 @@
     "@material-ui/icons": "^4.9.1",
     "@material-ui/pickers": "^3.2.10",
     "@shakacode/recompose": "^0.30.3",
+    "@turf/buffer": "^6.3.0",
+    "Leaflet.extra-markers": "git+https://github.com/SemanticComputing/Leaflet.ExtraMarkers.git",
     "apexcharts": "^3.26.0",
     "axios": "^0.21.1",
     "core-js": "^3.8.1",
@@ -49,7 +51,6 @@
     "leaflet-fullscreen": "^1.0.2",
     "leaflet-usermarker": "git+https://github.com/SemanticComputing/leaflet-usermarker.git",
     "leaflet.control.opacity": "^1.3.0",
-    "Leaflet.extra-markers": "git+https://github.com/SemanticComputing/Leaflet.ExtraMarkers.git",
     "leaflet.markercluster": "^1.4.1",
     "leaflet.zoominfo": "git+https://github.com/SemanticComputing/Leaflet.zoominfo.git",
     "lodash": "^4.17.20",
diff --git a/src/client/components/facet_results/LeafletMap.js b/src/client/components/facet_results/LeafletMap.js
index 1b2c00eb..9e68721c 100644
--- a/src/client/components/facet_results/LeafletMap.js
+++ b/src/client/components/facet_results/LeafletMap.js
@@ -4,7 +4,7 @@ import { withStyles } from '@material-ui/core/styles'
 import intl from 'react-intl-universal'
 import L from 'leaflet'
 import { has, orderBy, isEqual } from 'lodash'
-// import buffer from '@turf/buffer'
+import buffer from '@turf/buffer'
 import CircularProgress from '@material-ui/core/CircularProgress'
 import { purple } from '@material-ui/core/colors'
 import history from '../../History'
@@ -97,7 +97,7 @@ class LeafletMap extends React.Component {
   constructor (props) {
     super(props)
     this.state = {
-      activeOverlays: props.activeOverlays ? props.activeOverlays : [],
+      activeLayers: props.activeLayers ? props.activeLayers : [],
       prevZoomLevel: null,
       mapMode: props.mapMode
     }
@@ -297,9 +297,9 @@ class LeafletMap extends React.Component {
 
   fetchDefaultGeoJSONLayers = () => {
     this.props.clearGeoJSONLayers()
-    if (this.state.activeOverlays.length > 0 && this.isSafeToLoadLargeLayers()) {
+    if (this.state.activeLayers.length > 0 && this.isSafeToLoadLargeLayers()) {
       this.props.fetchGeoJSONLayers({
-        layerIDs: this.state.activeOverlays,
+        layerIDs: this.state.activeLayers,
         bounds: this.leafletMap.getBounds()
       })
     }
@@ -346,17 +346,18 @@ class LeafletMap extends React.Component {
   initMapEventListeners = () => {
     // Fired when an overlay is selected using layer controls
     this.leafletMap.on('overlayadd', event => {
-      if (event.layer.options.type === 'geoJSON') {
-        const layerID = event.layer.options.id
-        // https://www.robinwieruch.de/react-state-array-add-update-remove
-        this.setState(state => {
-          return {
-            activeOverlays: [...state.activeOverlays, layerID]
-          }
-        })
+      if (event.layer.options.type === 'GeoJSON') {
+        this.props.clearGeoJSONLayers()
         if (this.isSafeToLoadLargeLayers()) {
+          const layerID = event.layer.options.id
+          // https://www.robinwieruch.de/react-state-array-add-update-remove
+          this.setState(state => {
+            return {
+              activeLayers: [...state.activeLayers, layerID]
+            }
+          })
           this.props.fetchGeoJSONLayers({
-            layerIDs: this.state.activeOverlays,
+            layerIDs: this.state.activeLayers,
             bounds: this.leafletMap.getBounds()
           })
         } else {
@@ -369,12 +370,12 @@ class LeafletMap extends React.Component {
     })
     // Fired when an overlay is selected using layer controls
     this.leafletMap.on('overlayremove', event => {
-      if (event.layer.options.type === 'geoJSON') {
+      if (event.layer.options.type === 'GeoJSON') {
         const layerIDremoved = event.layer.options.id
         this.clearOverlay(layerIDremoved)
         this.setState(state => {
-          const activeOverlays = state.activeOverlays.filter(layerID => layerID !== layerIDremoved)
-          return { activeOverlays }
+          const activeLayers = state.activeLayers.filter(layerID => layerID !== layerIDremoved)
+          return { activeLayers }
         })
       }
     })
@@ -384,18 +385,18 @@ class LeafletMap extends React.Component {
     })
     // Fired when zooming ends
     this.leafletMap.on('zoomend', () => {
-      if (this.state.activeOverlays.length > 0 && this.isSafeToLoadLargeLayersAfterZooming()) {
+      if (this.state.activeLayers.length > 0 && this.isSafeToLoadLargeLayersAfterZooming()) {
         this.props.fetchGeoJSONLayers({
-          layerIDs: this.state.activeOverlays,
+          layerIDs: this.state.activeLayers,
           bounds: this.leafletMap.getBounds()
         })
       }
     })
     // Fired when dragging ends
     this.leafletMap.on('dragend', () => {
-      if (this.state.activeOverlays.length > 0 && this.isSafeToLoadLargeLayers()) {
+      if (this.state.activeLayers.length > 0 && this.isSafeToLoadLargeLayers()) {
         this.props.fetchGeoJSONLayers({
-          layerIDs: this.state.activeOverlays,
+          layerIDs: this.state.activeLayers,
           bounds: this.leafletMap.getBounds()
         })
       }
@@ -410,118 +411,51 @@ class LeafletMap extends React.Component {
   isSafeToLoadLargeLayers = () => this.leafletMap.getZoom() >= 13
 
   initOverLays = basemaps => {
-    const fhaArchaeologicalSiteRegistryAreas = L.layerGroup([], {
-      id: 'arkeologiset_kohteet_alue',
-      type: 'geoJSON',
-      source: 'FHA',
-      // this layer includes only GeoJSON Polygons, define style for them
-      geoJSONPolygonOptions: {
-        color: '#dd2c00',
-        cursor: 'pointer'
-        // dashArray: '3, 5'
-      },
-      geoJSONPolygonBoundaryOptions: {
-        color: '#dd2c00',
-        // cursor: 'pointer',
-        dashArray: '3, 5'
-      }
-    })
-    const fhaArchaeologicalSiteRegistryPoints = L.layerGroup([], {
-      id: 'arkeologiset_kohteet_piste',
-      type: 'geoJSON',
-      source: 'FHA',
-      // this layer includes only GeoJSON points, define style for them
-      geoJSONMarkerOptions: {
-        radius: 8,
-        fillColor: '#dd2c00',
-        weight: 1,
-        opacity: 1,
-        fillOpacity: 0.8
+    this.overlayLayers = {}
+    const opacityLayers = {}
+    let showOpacityController = false
+    this.props.layerConfigs.map(config => {
+      switch (config.type) {
+        case 'GeoJSON':
+          this.overlayLayers[intl.get(`leafletMap.externalLayers.${config.id}`)] =
+            L.layerGroup([], config)
+          break
+        case 'WMS':
+          this.overlayLayers[intl.get(`leafletMap.externalLayers.${config.id}`)] =
+            L.tileLayer.wms(config.url, {
+              layers: config.layers,
+              version: config.version,
+              minZoom: config.minZoom,
+              maxZoom: config.maxZoom,
+              attribution: config.attribution
+            })
+          break
+        case 'WMTS': {
+          const wmtsLayer = L.tileLayer(config.url, {
+            attribution: config.attribution
+          })
+          const translatedLayerID = intl.get(`leafletMap.externalLayers.${config.id}`)
+          this.overlayLayers[translatedLayerID] = wmtsLayer
+          if (config.opacityControl) {
+            opacityLayers[translatedLayerID] = wmtsLayer
+            showOpacityController = true
+          }
+          break
+        }
       }
     })
-    // const fhaLidar = L.tileLayer.wms(`${process.env.API_URL}/fha-wms`, {
-    //   layers: 'NBA:lidar',
-    //   version: '1.3.0',
-    //   attribution: 'FHA',
-    //   minZoom: 12,
-    //   maxZoom: 16
-    // })
-    // const kotusParishes1938 = L.layerGroup([], {
-    //   id: 'kotus:pitajat',
-    //   type: 'geoJSON',
-    //   source: 'kotus',
-    //   // this layer includes only GeoJSON Polygons, define style for them
-    //   geoJSONPolygonOptions: {
-    //     color: '#dd2c00',
-    //     cursor: 'pointer',
-    //     dashArray: '3, 5'
-    //   }
-    // })
-    // const kotusParishesDialecticalRegions = L.layerGroup([], {
-    //   id: 'kotus:rajat-sms-alueet',
-    //   type: 'geoJSON',
-    //   source: 'kotus',
-    //   // this layer includes only GeoJSON Polygons, define style for them
-    //   geoJSONPolygonOptions: {
-    //     color: '#fca903',
-    //     cursor: 'pointer',
-    //     dashArray: '3, 5'
-    //   }
-    // })
-    // const kotusParishesDialecticalSubRegions = L.layerGroup([], {
-    //   id: 'kotus:rajat-sms-alueosat',
-    //   type: 'geoJSON',
-    //   source: 'kotus',
-    //   // this layer includes only GeoJSON Polygons, define style for them
-    //   geoJSONPolygonOptions: {
-    //     color: '#119100',
-    //     cursor: 'pointer',
-    //     dashArray: '3, 5'
-    //   }
-    // })
-    // const kotusParishesDialecticalBorder = L.layerGroup([], {
-    //   id: 'kotus:rajat-lansi-ita',
-    //   type: 'geoJSON',
-    //   source: 'kotus',
-    //   // this layer includes only GeoJSON Polygons, define style for them
-    //   geoJSONPolygonOptions: {
-    //     color: '#2403fc',
-    //     cursor: 'pointer',
-    //     dashArray: '3, 5'
-    //   }
-    // })
-    const karelianMaps = L.tileLayer('https:///mapwarper.onki.fi/mosaics/tile/4/{z}/{x}/{y}.png', {
-      type: 'tile',
-      attribution: 'SeCo'
-    })
-    const senateAtlas = L.tileLayer('https:///mapwarper.onki.fi/mosaics/tile/5/{z}/{x}/{y}.png', {
-      type: 'tile',
-      attribution: 'SeCo'
-    })
-    this.overlayLayers = {
-      [intl.get('leafletMap.externalLayers.arkeologiset_kohteet_alue')]: fhaArchaeologicalSiteRegistryAreas,
-      [intl.get('leafletMap.externalLayers.arkeologiset_kohteet_piste')]: fhaArchaeologicalSiteRegistryPoints,
-      // [intl.get('leafletMap.externalLayers.fhaLidar')]: fhaLidar,
-      [intl.get('leafletMap.externalLayers.karelianMaps')]: karelianMaps,
-      [intl.get('leafletMap.externalLayers.senateAtlas')]: senateAtlas
-      // [intl.get('leafletMap.externalLayers.kotus:pitajat')]: kotusParishes1938,
-      // [intl.get('leafletMap.externalLayers.kotus:rajat-sms-alueet')]: kotusParishesDialecticalRegions,
-      // [intl.get('leafletMap.externalLayers.kotus:rajat-sms-alueosat')]: kotusParishesDialecticalSubRegions,
-      // [intl.get('leafletMap.externalLayers.kotus:rajat-lansi-ita')]: kotusParishesDialecticalBorder
-    }
 
     // Add default active overlays directly to the map
-    this.state.activeOverlays.map(overlay =>
+    this.state.activeLayers.map(overlay =>
       this.leafletMap.addLayer(this.overlayLayers[intl.get(`leafletMap.externalLayers.${overlay}`)]))
 
     // Add all basemaps and all overlays via the control to the map
     L.control.layers(basemaps, this.overlayLayers, { collapsed: !this.props.layerControlExpanded }).addTo(this.leafletMap)
 
-    const opacityLayers = {
-      [intl.get('leafletMap.externalLayers.karelianMaps')]: karelianMaps,
-      [intl.get('leafletMap.externalLayers.senateAtlas')]: senateAtlas
+    // Create opacity controller if needed
+    if (showOpacityController) {
+      this.createOpacitySlider(opacityLayers)
     }
-    this.createOpacitySlider(opacityLayers)
   }
 
   populateOverlay = layerObj => {
@@ -530,37 +464,33 @@ class LeafletMap extends React.Component {
       and Layer objects as values
     */
 
-    // console.log(layerObj.geoJSON)
-    // const bufferedGeoJSON = buffer(layerObj.geoJSON, 0.2, { units: 'kilometres' })
-    // const bufferedGeoJSON = transformScale(layerObj.geoJSON, 0.3)
-    // console.log(bufferedGeoJSON)
     const leafletOverlay = this.overlayLayers[intl.get(`leafletMap.externalLayers.${layerObj.layerID}`)]
     leafletOverlay.clearLayers()
+
+    // Only the layer that is added last is clickable, so add buffer first
+    if (leafletOverlay.options.buffer) {
+      const { distance, units, style } = leafletOverlay.options.buffer
+      const bufferedGeoJSON = buffer(layerObj.geoJSON, distance, { units })
+      const leafletGeoJSONBufferLayer = L.geoJSON(bufferedGeoJSON, {
+        // style for GeoJSON Polygons
+        style
+      })
+      leafletGeoJSONBufferLayer.addTo(leafletOverlay).addTo(this.leafletMap)
+    }
+
+    const { geojsonMarkerOptions, geojsonPolygonOptions, createPopup } = leafletOverlay.options
     const leafletGeoJSONLayer = L.geoJSON(layerObj.geoJSON, {
       // style for GeoJSON Points
       pointToLayer: (feature, latlng) => {
-        return L.circleMarker(latlng, leafletOverlay.options.geojsonMarkerOptions)
+        return L.circleMarker(latlng, geojsonMarkerOptions)
       },
       // style for GeoJSON Polygons
-      style: leafletOverlay.options.geoJSONPolygonOptions,
+      style: geojsonPolygonOptions,
       // add popups
       onEachFeature: (feature, layer) => {
-        layer.bindPopup(this.createPopUpContentGeoJSON(layerObj.layerID, feature.properties))
+        layer.bindPopup(createPopup(feature.properties))
       }
     })
-    // const leafletGeoJSONBoundaryLayer = L.geoJSON(bufferedGeoJSON, {
-    //   // style for GeoJSON Points
-    //   pointToLayer: (feature, latlng) => {
-    //     return L.circleMarker(latlng, leafletOverlay.options.geojsonMarkerOptions)
-    //   },
-    //   // style for GeoJSON Polygons
-    //   style: leafletOverlay.options.geoJSONPolygonBoundaryOptions
-    //   // add popups
-    //   // onEachFeature: (feature, layer) => {
-    //   //   layer.bindPopup(this.createPopUpContentGeoJSON(layerObj.layerID, feature.properties))
-    //   // }
-    // })
-    // leafletGeoJSONBoundaryLayer.addTo(leafletOverlay).addTo(this.leafletMap)
     leafletGeoJSONLayer.addTo(leafletOverlay).addTo(this.leafletMap)
   }
 
@@ -954,44 +884,6 @@ class LeafletMap extends React.Component {
     return p
   }
 
-  createPopUpContentGeoJSON = (layerID, properties) => {
-    if (layerID === 'arkeologiset_kohteet_alue' || layerID === 'arkeologiset_kohteet_piste') {
-      return this.createPopUpContentGeoJSONFHA(properties)
-    } else {
-      return this.createPopUpContentGeoJSONKotus(properties)
-    }
-  }
-
-  createPopUpContentGeoJSONFHA = properties => {
-    let popupText = ''
-    const name = properties.kohdenimi
-      ? `<b>Kohteen nimi:</b> ${properties.kohdenimi}</p>` : ''
-    const type = properties.laji ? `<b>Kohteen tyyppi:</b> ${properties.laji}</p>` : ''
-    const municipality = properties.kunta ? `<b>Kunta:</b> ${properties.kunta}</p>` : ''
-    const link = properties.mjtunnus
-      ? `<a href="https://www.kyppi.fi/to.aspx?id=112.${properties.mjtunnus}" target="_blank">Avaa kohde Muinaisjäännösrekisterissä</a></p>` : ''
-    popupText += `
-      <div>
-        ${name}
-        ${type}
-        ${municipality}
-        ${link}
-      </div>
-      `
-    return popupText
-  }
-
-  createPopUpContentGeoJSONKotus = properties => {
-    let popupText = ''
-    const name = `<b>Pitäjän nimi:</b> ${properties.NIMI}</p>`
-    popupText += `
-      <div>
-        ${name}
-      </div>
-      `
-    return popupText
-  }
-
   createInstanceListing = instances => {
     let root
     if (Array.isArray(instances)) {
diff --git a/src/client/components/perspectives/sampo/Perspective1.js b/src/client/components/perspectives/sampo/Perspective1.js
index 42818680..821f164a 100644
--- a/src/client/components/perspectives/sampo/Perspective1.js
+++ b/src/client/components/perspectives/sampo/Perspective1.js
@@ -53,7 +53,7 @@ const Perspective1 = props => {
             zoom={2}
             // locateUser
             // center={[60.187, 24.821]}
-            // zoom={15}
+            // zoom={13}
             results={props.placesResults.results}
             layers={props.leafletMapLayers}
             pageType='facetResults'
@@ -73,10 +73,112 @@ const Perspective1 = props => {
             showInstanceCountInClusters
             updateFacetOption={props.updateFacetOption}
             showExternalLayers
-            // activeOverlays={[
-            //   'arkeologiset_kohteet_alue',
-            //   'arkeologiset_kohteet_piste'
-            // ]}
+            layerConfigs={[
+              // {
+              //   id: 'arkeologiset_kohteet_alue',
+              //   type: 'GeoJSON',
+              //   minZoom: 13,
+              //   buffer: {
+              //     distance: 200,
+              //     units: 'metres',
+              //     style: {
+              //       color: '#6E6E6E',
+              //       dashArray: '3, 5'
+              //     }
+              //   },
+              //   // this layer includes only GeoJSON Polygons, define style for them
+              //   geojsonPolygonOptions: {
+              //     color: '#dd2c00',
+              //     cursor: 'pointer'
+              //   },
+              //   createPopup: data => {
+              //     let html = ''
+              //     const name = data.kohdenimi
+              //       ? `<b>Kohteen nimi:</b> ${data.kohdenimi}</p>` : ''
+              //     const type = data.laji ? `<b>Kohteen tyyppi:</b> ${data.laji}</p>` : ''
+              //     const municipality = data.kunta ? `<b>Kunta:</b> ${data.kunta}</p>` : ''
+              //     const link = data.mjtunnus
+              //       ? `<a href="https://www.kyppi.fi/to.aspx?id=112.${data.mjtunnus}" target="_blank">Avaa kohde Muinaisjäännösrekisterissä</a></p>` : ''
+              //     html += `
+              //     <div>
+              //       ${name}
+              //       ${type}
+              //       ${municipality}
+              //       ${link}
+              //     </div>
+              //     `
+              //     return html
+              //   }
+              // },
+              // {
+              //   id: 'arkeologiset_kohteet_piste',
+              //   type: 'GeoJSON',
+              //   minZoom: 13,
+              //   buffer: {
+              //     distance: 200,
+              //     units: 'metres',
+              //     style: {
+              //       color: '#6E6E6E',
+              //       dashArray: '3, 5'
+              //     }
+              //   },
+              //   // this layer includes only GeoJSON points, define style for them
+              //   geojsonMarkerOptions: {
+              //     radius: 8,
+              //     fillColor: '#dd2c00',
+              //     color: '#000',
+              //     weight: 1,
+              //     opacity: 1,
+              //     fillOpacity: 0.8
+              //   },
+              //   createPopup: data => {
+              //     let html = ''
+              //     const name = data.kohdenimi
+              //       ? `<b>Kohteen nimi:</b> ${data.kohdenimi}</p>` : ''
+              //     const type = data.laji ? `<b>Kohteen tyyppi:</b> ${data.laji}</p>` : ''
+              //     const municipality = data.kunta ? `<b>Kunta:</b> ${data.kunta}</p>` : ''
+              //     const link = data.mjtunnus
+              //       ? `<a href="https://www.kyppi.fi/to.aspx?id=112.${data.mjtunnus}" target="_blank">Avaa kohde Muinaisjäännösrekisterissä</a></p>` : ''
+              //     html += `
+              //     <div>
+              //       ${name}
+              //       ${type}
+              //       ${municipality}
+              //       ${link}
+              //     </div>
+              //     `
+              //     return html
+              //   }
+              // },
+              // {
+              //   id: 'fhaLidar',
+              //   type: 'WMS',
+              //   url: `${process.env.API_URL}/fha-wms`,
+              //   layers: 'NBA:lidar',
+              //   version: '1.3.0',
+              //   attribution: 'FHA',
+              //   minZoom: 13,
+              //   maxZoom: 16
+              // },
+              {
+                id: 'karelianMaps',
+                type: 'WMTS',
+                url: 'https:///mapwarper.onki.fi/mosaics/tile/4/{z}/{x}/{y}.png',
+                opacityControl: true,
+                attribution: 'Semantic Computing Research Group'
+              },
+              {
+                id: 'senateAtlas',
+                type: 'WMTS',
+                url: 'https:///mapwarper.onki.fi/mosaics/tile/5/{z}/{x}/{y}.png',
+                opacityControl: true,
+                attribution: 'Semantic Computing Research Group'
+              }
+            ]}
+            activeLayers={[
+              // 'arkeologiset_kohteet_alue'
+              // 'arkeologiset_kohteet_piste'
+            ]}
             layerControlExpanded
             showError={props.showError}
           />}
diff --git a/src/server/index.js b/src/server/index.js
index b2ed76d3..57450331 100644
--- a/src/server/index.js
+++ b/src/server/index.js
@@ -18,6 +18,7 @@ import { fetchGeoJSONLayer } from './wfs/WFSApi'
 import swaggerUi from 'swagger-ui-express'
 import { OpenApiValidator } from 'express-openapi-validator'
 import yaml from 'js-yaml'
+import querystring from 'querystring'
 const DEFAULT_PORT = 3001
 const app = express()
 app.set('port', process.env.PORT || DEFAULT_PORT)
@@ -268,6 +269,39 @@ new OpenApiValidator({
       }
     })
 
+    app.get(`${apiPath}/fha-wms`, async (req, res, next) => {
+      const headers = {
+        Authorization: `Basic ${process.env.FHA_WMS_BASIC_AUTH}`
+      }
+      const { service, request, layers, styles, format, transparent, version, width, height, crs, bbox } = req.query
+      const mapServerParams = {
+        service,
+        request,
+        layers,
+        styles,
+        format,
+        transparent,
+        version,
+        width,
+        height,
+        crs,
+        bbox
+      }
+      const url = `http://137.116.207.73/geoserver/ows?${querystring.stringify(mapServerParams)}`
+      try {
+        const response = await axios({
+          method: 'get',
+          url,
+          responseType: 'arraybuffer',
+          headers
+        })
+        res.end(response.data, 'base64')
+      } catch (error) {
+        console.log(error)
+        next(error)
+      }
+    })
+
     app.get(`${apiPath}/wfs`, async (req, res, next) => {
       const layerIDs = castArray(req.query.layerID)
       try {
diff --git a/src/server/openapi.yaml b/src/server/openapi.yaml
index f5948106..4afd7624 100644
--- a/src/server/openapi.yaml
+++ b/src/server/openapi.yaml
@@ -404,37 +404,75 @@ paths:
             application/json:
               schema:
                 type: array
-  /nls-wmts:
+  /fha-wms:
     get:
-      summary: Route for password protected WMTS layers. 
+      summary: Route for password protected WMS layers.
       parameters:
         - in: query
-          name: layerID
-          schema: 
+          name: service
+          schema:
             type: string
           required: true
         - in: query
-          name: x
-          schema: 
+          name: request
+          schema:
+            type: string
+          required: true 
+        - in: query
+          name: layers
+          schema:
+            type: string
+          required: true  
+        - in: query
+          name: styles
+          schema:
             type: string
+          allowEmptyValue : true
+          required: true  
+        - in: query
+          name: format
+          schema:
+            type: string
+          required: true    
+        - in: query
+          name: transparent
+          schema:
+            type: boolean
           required: true
         - in: query
-          name: y 
-          schema: 
+          name: version
+          schema:
             type: string
+          required: true   
+        - in: query
+          name: width
+          schema:
+            type: number
           required: true
         - in: query
-          name: z
-          schema: 
+          name: height
+          schema:
+            type: number
+          required: true
+        - in: query
+          name: crs
+          schema:
             type: string
-          required: true  
+          required: true 
+        - in: query
+          name: bbox
+          schema:
+            type: string
+          allowReserved: true  
+          required: true       
       responses:
         '200':
-          description: An array of GeoJSON layers.
+          description: Image
           content:
-            application/json:
+            image/jpeg:
               schema:
-                type: array              
+                type: string
+                format: binary     
   /void/{perspectiveID}:
     get:
       summary: Retrieve a VoID description
-- 
GitLab