Skip to content
Snippets Groups Projects
LeafletMap.js 29.8 KiB
Newer Older
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
esikkala's avatar
esikkala committed
import intl from 'react-intl-universal'
import L from 'leaflet'
import { has, orderBy } from 'lodash'
import CircularProgress from '@material-ui/core/CircularProgress'
import { purple } from '@material-ui/core/colors'
import { MAPBOX_ACCESS_TOKEN, MAPBOX_STYLE } from '../../configs/sampo/GeneralConfig'
esikkala's avatar
esikkala committed
import 'leaflet/dist/leaflet.css' // Official Leaflet styles
import './LeafletMap.css' // Customizations to Leaflet styles
// Leaflet plugins
import 'leaflet-fullscreen/dist/fullscreen.png'
import 'leaflet-fullscreen/dist/leaflet.fullscreen.css'
import 'leaflet-fullscreen/dist/Leaflet.fullscreen.min.js'
import 'leaflet.markercluster/dist/MarkerCluster.css'
import 'leaflet.markercluster/dist/MarkerCluster.Default.css'
import 'leaflet.markercluster/dist/leaflet.markercluster.js'
import 'leaflet.control.opacity/dist/L.Control.Opacity.css'
import 'leaflet.control.opacity'
import 'Leaflet.extra-markers/dist/js/leaflet.extra-markers.min.js'
import 'Leaflet.extra-markers/dist/css/leaflet.extra-markers.min.css'
import 'Leaflet.extra-markers/dist/img/markers_default.png'
import 'Leaflet.extra-markers/dist/img/markers_shadow.png'
import 'leaflet-draw/dist/leaflet.draw.js'
import 'leaflet-draw/dist/leaflet.draw.css'
esikkala's avatar
esikkala committed
import 'leaflet.zoominfo/dist/L.Control.Zoominfo'
import 'leaflet.zoominfo/dist/L.Control.Zoominfo.css'
// import 'leaflet.gridlayer.googlemutant/Leaflet.GoogleMutant.js'

import markerShadowIcon from '../../img/markers/marker-shadow.png'
import markerIconViolet from '../../img/markers/marker-icon-violet.png'
import markerIconGreen from '../../img/markers/marker-icon-green.png'
import markerIconRed from '../../img/markers/marker-icon-red.png'
import markerIconOrange from '../../img/markers/marker-icon-orange.png'
import markerIconYellow from '../../img/markers/marker-icon-yellow.png'
const styles = theme => ({
  leafletContainerfacetResults: {
    height: 400,
    [theme.breakpoints.up('md')]: {
      height: 'calc(100% - 72px)'
esikkala's avatar
esikkala committed
    },
    position: 'relative'
esikkala's avatar
esikkala committed
  },
  leafletContainerclientFSResults: {
    height: 400,
    [theme.breakpoints.up('md')]: {
      height: 'calc(100% - 72px)'
    },
    position: 'relative'
  },
  leafletContainerinstancePage: {
    height: 400,
    [theme.breakpoints.up('md')]: {
      height: '100%'
esikkala's avatar
esikkala committed
    },
    position: 'relative'
esikkala's avatar
esikkala committed
  mapElement: {
    width: '100%',
    height: '100%'
  },
esikkala's avatar
esikkala committed
  spinnerContainer: {
    height: 40,
    width: 40,
    position: 'absolute',
    left: '50%',
    top: '50%',
    transform: 'translate(-50%,-50%)',
    zIndex: 500
// https://github.com/pointhi/leaflet-color-markers
const ColorIcon = L.Icon.extend({
  options: {
    shadowUrl: markerShadowIcon,
    iconSize: [25, 41],
    iconAnchor: [12, 41],
    popupAnchor: [1, -34],
    shadowSize: [41, 41]
  }
Esko Ikkala's avatar
Esko Ikkala committed

class LeafletMap extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      activeOverlays: [],
      prevZoomLevel: null,
      mapMode: props.mapMode
    }
  componentDidMount = () => {
    if (this.props.mapMode && this.props.pageType === 'facetResults') {
      this.props.fetchResults({
        resultClass: this.props.resultClass,
        facetClass: this.props.facetClass,
esikkala's avatar
esikkala committed
        sortBy: null
esikkala's avatar
esikkala committed
    this.initMap()
    if (this.props.mapMode && this.props.pageType === 'clientFSResults') {
      this.drawPointData()
    }
esikkala's avatar
esikkala committed
  }

  componentDidUpdate = (prevProps, prevState) => {
    this.props.facetedSearchMode === 'clientFS'
      ? this.clientFScomponentDidUpdate(prevProps) : this.serverFScomponentDidUpdate(prevProps, prevState)
  }

  componentWillUnmount = () => {
    this.leafletMap.remove()
  }

  clientFScomponentDidUpdate = prevProps => {
    if (prevProps.facetUpdateID !== this.props.facetUpdateID) {
      this.drawPointData()
    }
    if (this.props.showExternalLayers &&
      (this.props.layers.updateID !== prevProps.layers.updateID)) {
      this.props.layers.layerData.map(layerObj => this.populateOverlay(layerObj))
    }
  }

  serverFScomponentDidUpdate = (prevProps, prevState) => {
esikkala's avatar
esikkala committed
    // check if filters have changed
    if (has(prevProps, 'facetUpdateID') && prevProps.facetUpdateID !== this.props.facetUpdateID) {
      this.props.fetchResults({
        resultClass: this.props.resultClass,
        facetClass: this.props.facetClass,
esikkala's avatar
esikkala committed
        sortBy: null
esikkala's avatar
esikkala committed
      })
    }

    // check if results data have changed
    if (prevProps.results !== this.props.results) {
esikkala's avatar
esikkala committed
      this.drawPointData()
esikkala's avatar
esikkala committed
    }

    // check if map mode has changed
    if (prevState.mapMode !== this.state.mapMode) {
      this.props.fetchResults({
        resultClass: this.props.resultClass,
        facetClass: this.props.facetClass,
esikkala's avatar
esikkala committed
        sortBy: null
esikkala's avatar
esikkala committed
    // check if instance have changed
    if ((this.props.instance !== null) && prevProps.instance !== this.props.instance) {
      this.markers[this.props.instance.id]
        .bindPopup(this.createPopUpContent(this.props.instance), {
          maxHeight: 300,
          maxWidth: 400,
          minWidth: 400
          // closeButton: false,
esikkala's avatar
esikkala committed
        })
        .openPopup()
    }

esikkala's avatar
esikkala committed
    if (this.props.showExternalLayers &&
      (this.props.layers.updateID !== prevProps.layers.updateID)) {
esikkala's avatar
esikkala committed
      this.props.layers.layerData.map(layerObj => this.populateOverlay(layerObj))
esikkala's avatar
esikkala committed
    if (has(prevProps, 'facet') && prevProps.facet.filterType !== this.props.facet.filterType) {
      if (this.props.facet.filterType === 'spatialFilter') {
        this.addDrawButtons()
      } else {
        this.removeDrawButtons()
      }
    }
  }
esikkala's avatar
esikkala committed
  initMap = () => {
    // Base layer(s)
    const mapboxBaseLayer = L.tileLayer(`https://api.mapbox.com/styles/v1/mapbox/${MAPBOX_STYLE}/tiles/{z}/{x}/{y}?access_token=${MAPBOX_ACCESS_TOKEN}`, {
      attribution: '&copy; <a href="https://www.mapbox.com/map-feedback/">Mapbox</a> &copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
      tileSize: 512,
      zoomOffset: -1
    // const googleRoadmap = L.gridLayer.googleMutant({
    //   type: 'roadmap'
    // })
    // const topographicalMapNLS = L.tileLayer(this.createNLSUrl('maastokartta'), {
    //   attribution: 'National Land Survey of Finland'
    // })
    // // https://www.maanmittauslaitos.fi/kartat-ja-paikkatieto/asiantuntevalle-kayttajalle/kartta-ja-paikkatietojen-rajapintapalvelut-19
    // const backgroundMapNLS = L.tileLayer(this.createNLSUrl('taustakartta'), {
    //   attribution: 'National Land Survey of Finland'
    // })
esikkala's avatar
esikkala committed
    // layer for markers
    this.resultMarkerLayer = L.layerGroup()
    const container = this.props.container ? this.props.container : 'map'

    this.leafletMap = L.map(container, {
      center: this.props.center,
      zoom: this.props.zoom,
esikkala's avatar
esikkala committed
      zoomControl: false,
      zoominfoControl: true,
        mapboxBaseLayer,
        this.resultMarkerLayer
      fullscreenControl: true
    })
esikkala's avatar
esikkala committed
    // initialize layers from external sources
    if (this.props.showExternalLayers) {
esikkala's avatar
esikkala committed
      const basemaps = {
        [intl.get(`leafletMap.basemaps.mapbox.${MAPBOX_STYLE}`)]: mapboxBaseLayer
      // [intl.get('leafletMap.basemaps.googleRoadmap')]: googleRoadmap,
      // [intl.get('leafletMap.basemaps.topographicalMapNLS')]: topographicalMapNLS,
      // [intl.get('leafletMap.basemaps.backgroundMapNLS')]: backgroundMapNLS
esikkala's avatar
esikkala committed
      }
esikkala's avatar
esikkala committed
      this.initOverLays(basemaps)
esikkala's avatar
esikkala committed
    }

    // Add scale
    L.control.scale().addTo(this.leafletMap)

    // create layer for bounding boxes
    if (has(this.props, 'facet') && this.props.facet.filterType === 'spatialFilter') {
      this.addDrawButtons()
    if (this.props.showMapModeControl) { this.addMapModeControl() }
    if (this.props.updateMapBounds) {
      this.props.updateMapBounds(this.boundsToValues())
      this.leafletMap.on('moveend', () => {
        this.props.updateMapBounds(this.boundsToValues())
      })
    }
  }

  createNLSUrl = layerID => {
    // return 'https://avoin-karttakuva.maanmittauslaitos.fi/avoin/wmts/1.0.0/' +
    // layerID + '/default/WGS84_Pseudo-Mercator/{z}/{x}/{y}.png';
    return 'https://avoin-karttakuva.maanmittauslaitos.fi/avoin/wmts?service=WMTS' +
    '&request=GetTile&version=1.0.0&layer=' + layerID + '&style=default' +
    '&format=image/png&TileMatrixSet=WGS84_Pseudo-Mercator&TileMatrix={z}&TileRow={y}&TileCol={x}'
  }

  boundsToValues = () => {
    const bounds = this.leafletMap.getBounds()
    const latMin = bounds._southWest.lat
    const longMin = bounds._southWest.lng
    const latMax = bounds._northEast.lat
    const longMax = bounds._northEast.lng
    return {
      latMin: latMin,
      longMin: longMin,
      latMax: latMax,
      longMax: longMax,
      zoom: this.leafletMap.getZoom()
    }
esikkala's avatar
esikkala committed
  drawPointData = () => {
    const { results } = this.props
    this.resultMarkerLayer.clearLayers()
    switch (this.state.mapMode) {
esikkala's avatar
esikkala committed
      case 'cluster':
        this.updateMarkersAndCluster(results)
        break
      case 'marker':
        this.updateMarkers(results)
        break
esikkala's avatar
esikkala committed
      // case 'heatmap':
      //   this.drawHeatmap(this.createLatLngArray(results))
      //   break
esikkala's avatar
esikkala committed
      default:
        this.updateMarkersAndCluster(results)
        break
    }
  }

  createLatLngArray = results => {
    return results.map(result => [+result.lat, +result.long])
  }

esikkala's avatar
esikkala committed
  initMapEventListeners = () => {
esikkala's avatar
esikkala committed
    // 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]
          }
esikkala's avatar
esikkala committed
        })
        if (this.isSafeToLoadLargeLayers()) {
          this.props.fetchGeoJSONLayers({
            layerIDs: this.state.activeOverlays,
            bounds: this.leafletMap.getBounds()
          })
        }
esikkala's avatar
esikkala committed
    // Fired when an overlay is selected using layer controls
    this.leafletMap.on('overlayremove', event => {
      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 }
        })
      }
esikkala's avatar
esikkala committed
    })
esikkala's avatar
esikkala committed
    // Fired when zooming starts
    this.leafletMap.on('zoomstart', () => {
      this.setState({ prevZoomLevel: this.leafletMap.getZoom() })
    })
    // Fired when zooming ends
    // this.leafletMap.on('zoomend', () => {
    //   if (this.state.activeOverlays.length > 0 && this.isSafeToLoadLargeLayersAfterZooming()) {
    //     this.props.fetchGeoJSONLayers({
    //       layerIDs: this.state.activeOverlays,
    //       bounds: this.leafletMap.getBounds()
    //     })
    //   }
    // })
esikkala's avatar
esikkala committed
    // Fired when dragging ends
    // this.leafletMap.on('dragend', () => {
    //   if (this.state.activeOverlays.length > 0 && this.isSafeToLoadLargeLayers()) {
    //     this.props.fetchGeoJSONLayers({
    //       layerIDs: this.state.activeOverlays,
    //       bounds: this.leafletMap.getBounds()
    //     })
    //   }
    // })
esikkala's avatar
esikkala committed
  }

  isSafeToLoadLargeLayersAfterZooming = () => {
    return (this.leafletMap.getZoom() === 13 ||
    (this.leafletMap.getZoom() >= 13 && this.state.prevZoomLevel > this.leafletMap.getZoom()))
  }

  isSafeToLoadLargeLayers = () => this.leafletMap.getZoom() >= 13

  initOverLays = basemaps => {
    const fhaArchaeologicalSiteRegistryAreas = L.layerGroup([], {
      id: 'arkeologiset_kohteet_alue',
esikkala's avatar
esikkala committed
      // this layer includes only GeoJSON Polygons, define style for them
      geojsonMPolygonOptions: {
        color: '#dd2c00',
        cursor: 'pointer',
        dashArray: '3, 5'
      }
    })
    const fhaArchaeologicalSiteRegistryPoints = L.layerGroup([], {
      id: 'arkeologiset_kohteet_piste',
      // this layer includes only GeoJSON points, define style for them
      geojsonMarkerOptions: {
        radius: 8,
        fillColor: '#dd2c00',
        weight: 1,
        opacity: 1,
        fillOpacity: 0.8
    // const kotusParishes1938 = L.layerGroup([], {
    //   id: 'kotus:pitajat',
    //   type: 'geoJSON',
    //   // this layer includes only GeoJSON Polygons, define style for them
    //   geojsonMPolygonOptions: {
    //     color: '#dd2c00',
    //     cursor: 'pointer',
    //     dashArray: '3, 5'
    //   }
    // })
    // const kotusParishesDialecticalRegions = L.layerGroup([], {
    //   id: 'kotus:rajat-sms-alueet',
    //   type: 'geoJSON',
    //   // this layer includes only GeoJSON Polygons, define style for them
    //   geojsonMPolygonOptions: {
    //     color: '#fca903',
    //     cursor: 'pointer',
    //     dashArray: '3, 5'
    //   }
    // })
    // const kotusParishesDialecticalSubRegions = L.layerGroup([], {
    //   id: 'kotus:rajat-sms-alueosat',
    //   type: 'geoJSON',
    //   // this layer includes only GeoJSON Polygons, define style for them
    //   geojsonMPolygonOptions: {
    //     color: '#119100',
    //     cursor: 'pointer',
    //     dashArray: '3, 5'
    //   }
    // })
    // const kotusParishesDialecticalBorder = L.layerGroup([], {
    //   id: 'kotus:rajat-lansi-ita',
    //   type: 'geoJSON',
    //   // this layer includes only GeoJSON Polygons, define style for them
    //   geojsonMPolygonOptions: {
    //     color: '#2403fc',
    //     cursor: 'pointer',
    //     dashArray: '3, 5'
    //   }
    // })
    const karelianMaps = L.tileLayer('http:///mapwarper.onki.fi/mosaics/tile/4/{z}/{x}/{y}.png', {
      type: 'tile',
      attribution: 'SeCo'
    })
    const senateAtlas = L.tileLayer('http:///mapwarper.onki.fi/mosaics/tile/5/{z}/{x}/{y}.png', {
      type: 'tile',
      attribution: 'SeCo'
    })
esikkala's avatar
esikkala committed
    this.overlayLayers = {
      [intl.get('leafletMap.externalLayers.arkeologiset_kohteet_alue')]: fhaArchaeologicalSiteRegistryAreas,
      [intl.get('leafletMap.externalLayers.arkeologiset_kohteet_piste')]: fhaArchaeologicalSiteRegistryPoints,
      [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
esikkala's avatar
esikkala committed
    }
    L.control.layers(basemaps, this.overlayLayers).addTo(this.leafletMap)
    const opacityLayers = {
      [intl.get('leafletMap.externalLayers.karelianMaps')]: karelianMaps,
      [intl.get('leafletMap.externalLayers.senateAtlas')]: senateAtlas
    }
    this.createOpacitySlider(opacityLayers)
esikkala's avatar
esikkala committed
    this.initMapEventListeners()
  }

  populateOverlay = layerObj => {
    /*
      The baseLayers and overlays parameters are object literals with layer names as keys
      and Layer objects as values
    */
    const leafletOverlay = this.overlayLayers[intl.get(`leafletMap.externalLayers.${layerObj.layerID}`)]
    const leafletGeoJSONLayer = L.geoJSON(layerObj.geoJSON, {
      // style for GeoJSON Points
      pointToLayer: (feature, latlng) => {
        return L.circleMarker(latlng, leafletOverlay.options.geojsonMarkerOptions)
      },
      // style for GeoJSON Polygons
      style: leafletOverlay.options.geojsonMPolygonOptions,
      // add popups
      onEachFeature: (feature, layer) => {
        layer.bindPopup(this.createPopUpContentGeoJSON(feature.properties))
      }
    })
    leafletGeoJSONLayer.addTo(leafletOverlay).addTo(this.leafletMap)
  }

  clearOverlay = id => {
    const leafletOverlay = this.overlayLayers[intl.get(`leafletMap.externalLayers.${id}`)]
    leafletOverlay.clearLayers()
  addMapModeControl = () => {
esikkala's avatar
esikkala committed
    L.Control.Mapmode = L.Control.extend({
      onAdd: map => {
        const container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded')
        const markersInputContainer = L.DomUtil.create('div', 'leaflet-control-mapmode-input-container', container)
        const heatmapInputContainer = L.DomUtil.create('div', 'leaflet-control-mapmode-input-container', container)
        const radioMarkers = L.DomUtil.create('input', 'leaflet-control-mapmode-input', markersInputContainer)
        const radioHeatmap = L.DomUtil.create('input', 'leaflet-control-mapmode-input', heatmapInputContainer)
        const markersLabel = L.DomUtil.create('label', 'leaflet-control-mapmode-label', markersInputContainer)
        const heatmapLabel = L.DomUtil.create('label', 'leaflet-control-mapmode-label', heatmapInputContainer)
        radioMarkers.id = 'leaflet-control-mapmode-markers'
        radioHeatmap.id = 'leaflet-control-mapmode-heatmap'
        radioMarkers.type = 'radio'
        radioHeatmap.type = 'radio'
        radioMarkers.checked = this.state.mapMode === 'cluster'
        radioHeatmap.checked = this.state.mapMode === 'heatmap'
        radioMarkers.name = 'mapmode'
        radioHeatmap.name = 'mapmode'
        radioMarkers.value = 'cluster'
        radioHeatmap.value = 'heatmap'
        markersLabel.for = 'leaflet-control-mapmode-markers'
esikkala's avatar
esikkala committed
        markersLabel.textContent = intl.get('leafletMap.mapModeButtons.markers')
        heatmapLabel.for = 'leaflet-control-mapmode-heatmap'
esikkala's avatar
esikkala committed
        heatmapLabel.textContent = intl.get('leafletMap.mapModeButtons.heatmap')
        L.DomEvent.on(radioMarkers, 'click', event => this.setState({ mapMode: event.target.value }))
        L.DomEvent.on(radioHeatmap, 'click', event => this.setState({ mapMode: event.target.value }))
        return container
      },
      onRemove: map => {
esikkala's avatar
esikkala committed
        // TODO: remove DOM events?
esikkala's avatar
esikkala committed
    L.control.mapmode = opts => {
      return new L.Control.Mapmode(opts)
esikkala's avatar
esikkala committed
    L.control.mapmode({ position: 'topleft' }).addTo(this.leafletMap)
  addDrawButtons = () => {
    this.drawnItems = new L.FeatureGroup()
    this.leafletMap.addLayer(this.drawnItems)

    // https://github.com/Leaflet/Leaflet.draw/issues/315
    this.drawControlFull = new L.Control.Draw({
      draw: {
        polyline: false,
        rectangle: {
          shapeOptions: {
            color: '#bada55'
          }
        },
        circle: false,
        polygon: false,
        marker: false,
        circlemarker: false
      },
      edit: {
        featureGroup: this.drawnItems
      }
    })

    this.drawControlEditOnly = new L.Control.Draw({
      draw: false,
      edit: {
        featureGroup: this.drawnItems

    if (this.props.facet.spatialFilter !== null) {
      this.drawnItems.addLayer(this.props.facet.spatialFilter)
      this.leafletMap.addControl(this.drawControlEditOnly)
      this.leafletMap.addControl(this.drawControlFull)
    }

    this.leafletMap.on(L.Draw.Event.CREATED, e => {
      this.drawnItems.addLayer(e.layer)
      this.leafletMap.removeControl(this.drawControlFull)
      this.leafletMap.addControl(this.drawControlEditOnly)
      this.props.updateFacetOption({
        facetClass: this.props.facetClass,
        facetID: this.props.facetID,
        option: 'spatialFilter',
        value: e.layer

    this.leafletMap.on(L.Draw.Event.EDITED, e => {
      e.layers.eachLayer(layer => {
        this.props.updateFacetOption({
          facetClass: this.props.facetClass,
          facetID: this.props.facetID,
          option: 'spatialFilter',
          value: layer

    this.leafletMap.on(L.Draw.Event.DELETED, () => {
      this.leafletMap.removeControl(this.drawControlEditOnly)
      this.leafletMap.addControl(this.drawControlFull)
      this.props.updateFacetOption({
        facetClass: this.props.facetClass,
        facetID: this.props.facetID,
        option: 'spatialFilter',
        value: null
  }

  removeDrawButtons = () => {
    this.leafletMap.removeLayer(this.drawnItems)
    this.leafletMap.removeControl(this.drawControlFull)
    this.leafletMap.removeControl(this.drawControlEditOnly)
Esko Ikkala's avatar
Esko Ikkala committed
  }

esikkala's avatar
esikkala committed
  // drawHeatmap = latLngs => {

  // }
  updateMarkers = results => {
    this.resultMarkerLayer.clearLayers()
    this.markers = {}
    Object.values(results).forEach(value => {
      const marker = this.createMarker(value)
      if (marker !== null) {
        this.markers[value.id] = marker
        marker.addTo(this.resultMarkerLayer)
      }
    })
  updateMarkersAndCluster = results => {
    this.resultMarkerLayer.clearLayers()
    this.markers = {}
    let clusterer = null
    clusterer = new L.MarkerClusterGroup({
      iconCreateFunction: (cluster) => {
        let childCount = 0
        if (this.props.showInstanceCountInClusters) {
esikkala's avatar
esikkala committed
          cluster.getAllChildMarkers().forEach(marker => {
            childCount += parseInt(marker.options.instanceCount)
          })
          childCount = cluster.getChildCount()
        let c = ' marker-cluster-'
        if (childCount < 10) {
          c += 'small'
        } else if (childCount < 100) {
          c += 'medium'
          c += 'large'
esikkala's avatar
esikkala committed
        return new L.DivIcon({
          html: '<div><span>' + childCount + '</span></div>',
          className: 'marker-cluster' + c,
          iconSize: new L.Point(40, 40)
        })
    for (const result of results) {
      const marker = this.createMarker(result)
      if (marker !== null) {
        this.markers[result.id] = marker
        clusterer.addLayer(marker)
    clusterer.addTo(this.resultMarkerLayer)
  createMarker = result => {
    if (!has(result, 'lat') ||
      !has(result, 'long') ||
      result.lat === 'Undefined' ||
      result.long === 'Undefined'
esikkala's avatar
esikkala committed
    ) {
      return null
    } else {
      const lat = Array.isArray(result.lat) ? result.lat[0] : result.lat
      const long = Array.isArray(result.long) ? result.long[0] : result.long
      const latLng = [+lat, +long]
      let marker = null
      if (this.props.showInstanceCountInClusters) {
        // https://github.com/coryasilva/Leaflet.ExtraMarkers
        const icon = L.ExtraMarkers.icon({
          icon: 'fa-number',
          number: result.instanceCount,
          markerColor: 'blue',
          shape: 'circle',
          prefix: 'fa'
        marker = L.marker(latLng, {
          icon: icon,
          instanceCount: result.instanceCount ? result.instanceCount : null,
          id: result.id,
          prefLabel: result.prefLabel ? result.prefLabel : null,
          events: result.events ? result.events : null
        const color = result.markerColor || 'green'
        let markerIcon = ''
        switch (color) {
            markerIcon = markerIconViolet
            break
            markerIcon = markerIconGreen
            break
            markerIcon = markerIconRed
            break
            markerIcon = markerIconOrange
            break
          case 'yellow':
            markerIcon = markerIconYellow
            break
          default:
            markerIcon = markerIconGreen
        }
        marker = L.marker(latLng, {
          icon: new ColorIcon({ iconUrl: markerIcon }),
          id: result.id,
          prefLabel: result.prefLabel ? result.prefLabel : null,
          events: result.events ? result.events : null
      }
      if (this.props.pageType === 'facetResults') {
        marker.on('click', this.markerOnClickFacetResults)
      }
      if (this.props.pageType === 'instancePage') {
        marker.bindPopup(this.createPopUpContent(marker.options))
      if (this.props.pageType === 'clientFSResults') {
        marker.bindPopup(this.createPopUpContentNameSampo(result))
      }
      return marker
  markerOnClickFacetResults = event => {
esikkala's avatar
esikkala committed
    this.props.fetchByURI({
      resultClass: this.props.resultClass,
      facetClass: this.props.facetClass,
      uri: event.target.options.id
esikkala's avatar
esikkala committed
  // TODO: add click events instead of a tags:
  // https://stackoverflow.com/questions/54744762/click-event-on-leaflet-popup-content
  createPopUpContent = result => {
    let popUpTemplate = ''
    if (Array.isArray(result.prefLabel)) {
      result.prefLabel = result.prefLabel[0]
esikkala's avatar
esikkala committed
    if (has(result.prefLabel, 'dataProviderUrl')) {
      popUpTemplate += `<a href=${result.prefLabel.dataProviderUrl}><h3>${result.prefLabel.prefLabel}</h3></a>`
esikkala's avatar
esikkala committed
    } else {
      popUpTemplate += `<h3>${result.prefLabel.prefLabel}</h3>`
esikkala's avatar
esikkala committed
    }
      popUpTemplate += `<p>Place authority: <a target="_blank" rel="noopener noreferrer" href=${result.sameAs}>${result.sameAs}</a></p>`
    if (this.props.resultClass === 'placesMsProduced') {
      popUpTemplate += '<p>Manuscripts produced here:</p>'
      popUpTemplate += this.createInstanceListing(result.related)
    if (this.props.resultClass === 'lastKnownLocations') {
      popUpTemplate += '<p>Last known location of:</p>'
      popUpTemplate += this.createInstanceListing(result.related)
    if (this.props.resultClass === 'placesActors') {
      popUpTemplate += '<p>Actors:</p>'
      popUpTemplate += this.createInstanceListing(result.related)
    if (this.props.resultClass === 'instanceEvents') {
      popUpTemplate += '<p>Events:</p>'
      popUpTemplate += this.createInstanceListing(result.events)
esikkala's avatar
esikkala committed
    // console.log(popUpTemplate)
    return popUpTemplate
  createPopUpContentNameSampo = data => {
    const { perspectiveID } = this.props
    let popUpTemplate = ''
    popUpTemplate += `<a href=${data.id} target='_blank'><h3>${data.prefLabel}</h3></a>`
    if (has(data, 'broaderTypeLabel')) {
      popUpTemplate += `
        <p><b>${intl.get(`perspectives.${perspectiveID}.properties.broaderTypeLabel.label`)}</b>: ${data.broaderTypeLabel}</p>`
    }
    if (has(data, 'broaderAreaLabel')) {
      popUpTemplate += `
        <p><b>${intl.get(`perspectives.${perspectiveID}.properties.broaderAreaLabel.label`)}</b>: ${data.broaderAreaLabel}</p>`
    }
    if (has(data, 'modifier')) {
      popUpTemplate += `
        <p><b>${intl.get(`perspectives.${perspectiveID}.properties.modifier.label`)}</b>: ${data.modifier}</p>`
    }
    if (has(data, 'basicElement')) {
      popUpTemplate += `
        <p><b>${intl.get(`perspectives.${perspectiveID}.properties.basicElement.label`)}</b>: ${data.basicElement}</p>`
    }
    if (has(data, 'collectionYear')) {
      popUpTemplate += `
        <p><b>${intl.get(`perspectives.${perspectiveID}.properties.collectionYear.label`)}</b>: ${data.collectionYear}</p>`
    }
    if (has(data, 'source')) {
esikkala's avatar
esikkala committed
      if (data.namesArchiveLink !== '-') {
        popUpTemplate += `
        <p><b>${intl.get(`perspectives.${perspectiveID}.properties.source.label`)}</b>: 
          <a href="${data.namesArchiveLink}" target="_blank">${data.source}</a></p>`
      } else {
        popUpTemplate += `
        <p><b>${intl.get(`perspectives.${perspectiveID}.properties.source.label`)}</b>: ${data.source}</p>`
      }
    }

    // console.log(popUpTemplate)
    return popUpTemplate
  }

  createPopUpContentGeoJSONFHA = properties => {
esikkala's avatar
esikkala committed
    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 html = ''
    if (Array.isArray(instances)) {
      instances = orderBy(instances, 'prefLabel')
      html += '<ul>'
      instances.forEach(i => {
        html += '<li><a href=' + i.dataProviderUrl + '>' + i.prefLabel + '</a></li>'
      })
      html += '</ul>'
    } else {
      html += '<p><a href=' + instances.dataProviderUrl + '>' + instances.prefLabel + '</a></p>'
  createOpacitySlider = overlayLayers => {
    L.control.opacity(
      overlayLayers,
      {
        label: null,
        collapsed: true
    ).addTo(this.leafletMap)
  render = () => {
esikkala's avatar
esikkala committed
    return (
        <div className={this.props.classes[`leafletContainer${this.props.pageType}`]}>
          <div id={this.props.container ? this.props.container : 'map'} className={this.props.classes.mapElement} />
esikkala's avatar
esikkala committed
          {(this.props.fetching ||
            (this.props.showExternalLayers && this.props.layers.fetching)) &&
              <div className={this.props.classes.spinnerContainer}>
                <CircularProgress style={{ color: purple[500] }} thickness={5} />
              </div>}
Esko Ikkala's avatar
Esko Ikkala committed
  }
}

LeafletMap.propTypes = {
  classes: PropTypes.object.isRequired,
  pageType: PropTypes.string.isRequired,
  results: PropTypes.array,
  layers: PropTypes.object,
  facetID: PropTypes.string,
  facet: PropTypes.object,
esikkala's avatar
esikkala committed
  instance: PropTypes.object,
  facetUpdateID: PropTypes.number,
  fetchGeoJSONLayers: PropTypes.func,
  resultClass: PropTypes.string,
  facetClass: PropTypes.string,
  fetchByURI: PropTypes.func,
  fetching: PropTypes.bool.isRequired,
  mapMode: PropTypes.string,
  showInstanceCountInClusters: PropTypes.bool,
esikkala's avatar
esikkala committed
  showExternalLayers: PropTypes.bool,
  updateFacetOption: PropTypes.func,
  facetedSearchMode: PropTypes.string,
  container: PropTypes.string
export default withStyles(styles)(LeafletMap)