af83/chouette-core

View on GitHub
app/packs/src/components/MapWrapper.jsx

Summary

Maintainability
A
0 mins
Test Coverage
import React, { useMemo, useEffect, useCallback, useState } from 'react'
import { PropTypes } from 'prop-types'
import { Map, View } from 'ol'
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
import { OSM, Vector as VectorSource } from 'ol/source'
import { ScaleLine, Zoom, ZoomSlider } from 'ol/control'
import { toStringXY } from 'ol/coordinate'

import { toWgs84 } from '@turf/turf'

import { usePrevious } from '../helpers/hooks'

function MapWrapper({ features, onInit, style, height, width }) {
  const [ selectedCoord , setSelectedCoord ] = useState()
  const previousFeatures = usePrevious(features)

  const featuresLayer = useMemo(
    () => new VectorLayer({ source: new VectorSource(), style }),
    []
  )

  const map = useMemo(
    () => new Map({
      layers: [
        // OSM Topo
        new TileLayer({ source: new OSM({ attributions: '© OpenStreetMap contributors' }) }),
        featuresLayer
      ],
      view: new View({ center: [0, 0], zoom: 2 }),
      controls: [new ScaleLine(), new Zoom(), new ZoomSlider()]
    }),
    []
  )

  // pull refs
  const mapRef = useCallback(node => { node !== null && map.setTarget(node) }, [])

  useEffect(() => {
    map.on('singleclick', e => { setSelectedCoord(toWgs84(e.coordinate)) })
    onInit(map)
  },[])

  // update map if features prop changes - logic formerly put into componentDidUpdate
  useEffect( () => {
    if (features && !previousFeatures) { // we just want to execute this block once

      // set features to map
      featuresLayer.setSource(new VectorSource({ features }))

      // Workaround to prevent openlayer rendering bugs within modal
      map.updateSize()

      // Fit map to feature extent (with 100px of padding)
      map.getView().fit(featuresLayer.getSource().getExtent(), {
        padding: [100,100,100,100],
        maxZoom: 18
      })
    }
  },[features])

  // render component
  return (
    <div>
      <div ref={mapRef} className="map-container" style={{ width, height }}></div>
      <div className="clicked-coord-label">
        <p>{ (selectedCoord) ? toStringXY(selectedCoord, 5) : '' }</p>
      </div>
    </div>
  )
}

MapWrapper.defaultProps = {
  onInit: _map => {},
  height: 370,
  width: '100%'
}

MapWrapper.propTypes = {
  fetchFeatures: PropTypes.array,
  onInit: PropTypes.func,
  style: PropTypes.object,
  height: PropTypes.number,
  width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
}

export default MapWrapper