Source: custom-plugins/plugin-parcel.js

/** @module */

import { configuration } from '../modules/configuration.js'
import { queryItem } from '../modules/api.js'
import { state } from '../state/index.js'
import { getImageXY, getElevation } from '@dataforsyningen/saul'
import { toJS } from 'mobx'
import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import Feature from 'ol/Feature'
import Polygon from 'ol/geom/Polygon'
import Style from 'ol/style/Style'
import Fill from 'ol/style/Fill'
import Stroke from 'ol/style/Stroke'
import { showToast } from '@dataforsyningen/designsystem/assets/designsystem.js'


function fetchParcel(id) {
  const idSplit = id.split('-')
  return fetch(`https://api.dataforsyningen.dk/jordstykker/${ idSplit[0] }/${ idSplit[1] }?format=geojson&srid=25832&token=${ configuration.API_STAC_TOKEN }`)
  .then(function(response) {
    return response.json()
  })
  .then(data => {
    return data
  })
}

/**
 * Requests MATRIKLEN WFS service and fetches "Jordstykker" filtered by Ejerlav and Matrikel.
 * @param {number} ejerlav 
 * @param {string} matrikel 
 * @returns {Array} Polygon array consisting of coordinate pairs
 */
function fetchParcelWFS(ejerlav, matrikel) {
  return fetch(`https://wfs.datafordeler.dk/MATRIKLEN2/MatGaeldendeOgForeloebigWFS/1.0.0/WFS?USERNAME=${ configuration.API_DHM_TOKENA }&PASSWORD=${ configuration.API_DHM_TOKENB }&SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=mat:Jordstykke_Gaeldende&CQL_FILTER=ejerlavskode%3D${ ejerlav }%20AND%20matrikelnummer%3D'${ matrikel }'`)
  .then((response) => response.text())
  .then((xml) => new DOMParser().parseFromString(xml, "text/xml"))
  .then((data) => {
    const gmlData = data.getElementsByTagName('gml:posList')[0]
    if (!gmlData) {
      return false
    }
    const polygonData = gmlData.childNodes[0].nodeValue.split(' ')
    const polygon = []
    for (let i = 0; i < polygonData.length; i = i + 3) {
      polygon.push([Number(polygonData[i]), Number(polygonData[i + 1])])
    }
    return polygon
  })
  .catch(err => {
    console.error('Could not fetch matrikel Polygon from WFS', err)
  })
}

function fetchParcels(ids) {
  if (!ids) {
    return Promise.resolve([])
  }

  const splitIds = ids.split(';')
  const promises = []
  const parcels = []

  if (configuration.ENABLE_PARCEL_WFS) {
    splitIds.forEach((id) => {
      const [ejerlav, matrikel] = id.split('-')
      promises.push(fetchParcelWFS(ejerlav, matrikel))
    })

    return Promise.all(promises).then((results) => {
      let failCounter = 0
      results.forEach((result) => {
        if (result) {
          parcels.push(result)
        } else {
          failCounter += 1
        }
      })
      if (failCounter > 0) {
        showToast({
          message: `${failCounter} ${failCounter > 1 ? 'matrikler' : 'matrikel'} kunne ikke indlæses`,
          duration: 20000
        })
      }
      return parcels
    })

  } else {

    splitIds.forEach((id) => {
      promises.push(fetchParcel(id)
        .then((parcel_data) => {
          return parcel_data.geometry ? parcel_data.geometry.coordinates[0] : undefined
        })
      )
    })

    return Promise.all(promises)
    .then((results) => {
      results.forEach((result) => {
        if (result) {
          parcels.push(result)
        }
      })
      return parcels
    })

  }
}

function getPolygonElevations(coords, terrain) {

  const promises = []
  coords.forEach(function(coor) {
    promises.push(getElevation(coor[0], coor[1], terrain))
  })

  return Promise.all(promises)
  .then(function(values) {
    const improved_polygon = coords.map(function(coor, idx) {
      coor[2] = values[idx]
      return coor
    })
    return improved_polygon
  })
}

function generateFeature(polygon, image_id) {
  return queryItem(image_id)
  .then(function(image_data) {
    const new_polygon = polygon.map(function(coor) {
      // Convert every coordinate to image x,y
      return getImageXY(image_data, coor[0], coor[1], coor[2])
    })
    return new Feature({
      geometry: new Polygon([new_polygon])
    })
  })
}

function generateParcelVectorLayer() {
  const source = new VectorSource({
    features: []
  })
  const colorsetting2 = configuration.COLOR_SETTINGS.parcelColorFill
  const fill = new Fill({
    color: colorsetting2,
  })
  const colorSetting = configuration.COLOR_SETTINGS.parcelColorStroke
  const stroke = new Stroke({
    color: colorSetting, // highlight
    width: 2,
  })
  const style = new Style({
    fill: fill,
    stroke: stroke,
  })
  const layer = new VectorLayer({
    source: source,
    style: style,
    title: 'Parcels'
  })
  return layer
}

/**
 * Fetches the parcel polygons based on the ids
 * and draws that polygon over an image in an OpenLayers map object
 */
function drawParcels({parcels, imageId, map, elevationdata}) {
  if (!parcels[0] || !imageId) {
    return
  }
  const promises = []
  parcels.forEach((parcel) => {
    promises.push(getPolygonElevations(parcel, elevationdata)
      .then((improved_polygon) => {
        return generateFeature(improved_polygon, imageId)
          .then(function(feature) {
            return feature
          })
      }))
  })

  // generate a map layer for parcel polygons
  const layer = generateParcelVectorLayer()

  Promise.all(promises)
    .then((results) => {
      results.forEach((result) => {
        if (result) {
          layer.getSource().addFeature(result)
        }
      })
      // update map
      const oldLayer = map.getLayers().getArray().find((pLayer) => {
        return pLayer.get('title') === 'Parcels'
      })
      map.removeLayer(oldLayer)
      map.addLayer(layer)
    })
}

/**
 * Starts fetching the relevant data to draw the parcels on map
 */
async function renderParcels(viewport, itemId) {
  if (state.parcels.length < 1) {
    // No parcels to draw
    return
  }
  await state.updateTerrain()
  drawParcels({
    parcels: toJS(state.parcels), // Using `toJS` to clone array and avoid manipulating state object directly
    imageId: itemId,
    map: viewport.map,
    elevationdata: state.terrain.data
  })
}

export {
  fetchParcels,
  renderParcels,
  generateParcelVectorLayer
}