Skip to content

Commit

Permalink
Smart fitbound
Browse files Browse the repository at this point in the history
  • Loading branch information
tmerlier committed May 29, 2020
1 parent 1fd9e03 commit deb20f5
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 49 deletions.
1 change: 1 addition & 0 deletions components/layouts/prelevements/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ const DesktopPrelevements = () => {
<div className='menu'>
<Scrollable>
<div style={{padding: '1em'}}>
<h4>Saisissez une adresse pour retrouver tous les points de prélèvement des tests virologiques (RT-PCR) à proximité !</h4>
<SearchAddress />
<PlacesList />
</div>
Expand Down
32 changes: 3 additions & 29 deletions components/layouts/prelevements/places-list.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,16 @@
import React, {useContext} from 'react'

import Counter from '../../counter'

import {PrelevementsContext} from '.'

import Place from './place'

const PlacesList = () => {
const {address, prelevementsSites, places, selectedPlace, hoveredPlace} = useContext(PrelevementsContext)
const {places, selectedPlace, hoveredPlace} = useContext(PrelevementsContext)

return (
<div className='places-list-container'>
{address ? (
places.length > 0 ? (
<div><b>{places.length}</b> site{places.length > 1 ? 's' : ''} de prélèvement à proximité</div>
) : (
<div><b>Aucun</b> site de prélèvement n’est diponible à proximité de cette adresse</div>
)
) : (
<div>
<h3>Sites de prélèvements du COVID-19</h3>
<div className='counters'>
<Counter
value={prelevementsSites.features.filter(({properties}) => properties.isPublic).length}
label='Tout public'
color='green'
details='Site de prélèvement de COVID-19 ouvert à tout public'
isBig
/>
<Counter
value={prelevementsSites.features.filter(({properties}) => !properties.isPublic).length}
label='Accès limité'
color='orange'
details='Site de prélèvement de COVID-19 ouvert uniquement sous certaines conditions'
isBig
/>
</div>
</div >
{places.length > 0 && (
<div><b>{places.length}</b> site{places.length > 1 ? 's' : ''} de prélèvement à proximité</div>
)}

{places.map(place => (
Expand Down
124 changes: 104 additions & 20 deletions components/layouts/prelevements/prelevement-map.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, {useState, useContext, useEffect, useRef} from 'react'
import ReactMapGL, {Source, Layer, Popup} from 'react-map-gl'
import React, {useState, useContext, useEffect, useRef, useCallback} from 'react'
import ReactMapGL, {Source, Layer, Popup, Marker, WebMercatorViewport} from 'react-map-gl'
import nearestPoint from '@turf/nearest-point'
import bbox from '@turf/bbox'

import colors from '../../../styles/colors'

Expand Down Expand Up @@ -108,35 +110,93 @@ const PrelevementsMap = () => {
setHovered(null)
}

const fitBounds = (a, b) => {
const bounds = bbox({type: 'FeatureCollection', features: [a, b]}).map(n => Number(n))
setViewport(viewport => {
return new WebMercatorViewport(viewport)
.fitBounds([[bounds[0], bounds[1]], [bounds[2], bounds[3]]], {padding: 100})
})
}

const getVisiblePlaces = useCallback(() => {
if (mapRef && mapRef.current) {
const features = mapRef.current.queryRenderedFeatures(null, {layers: ['public-place', 'limited-access']})
const visiblePlaces = features.map(f => f.properties.id)
return prelevementsSites.features.filter(({properties}) => visiblePlaces.includes(properties.id)).map(({properties}) => properties)
}

return []
}, [mapRef, prelevementsSites])

useEffect(() => {
if (selectedPlace) {
const {longitude, latitude} = selectedPlace
setViewport({
longitude: Number(longitude),
latitude: Number(latitude),
zoom: 15
})
if (address) {
const selectedPlaceFeature = {
type: 'Feature',
properties: selectedPlace,
geometry: {
type: 'Point',
coordinates: [
longitude,
latitude
]
}
}
fitBounds(address, selectedPlaceFeature)
} else {
setViewport(viewport => {
return {
...viewport,
longitude: Number(longitude),
latitude: Number(latitude),
zoom: 15
}
})
}
}
}, [selectedPlace])
}, [address, selectedPlace])

useEffect(() => {
if (address) {
const [longitude, latitude] = address.geometry.coordinates
setViewport({
longitude,
latitude,
zoom: zoomLevel[address.properties.type]
})
const {type, population} = address.properties
if (type === 'housenumber' || type === 'street' || population < 10000) {
const nearest = nearestPoint(address, prelevementsSites)
fitBounds(address, nearest)
} else {
const [longitude, latitude] = address.geometry.coordinates
setViewport(viewport => {
return {
...viewport,
longitude,
latitude,
zoom: zoomLevel[type]
}
})
}
}
}, [address])
}, [address, prelevementsSites, getVisiblePlaces, setPlaces])

useEffect(() => {
if (mapRef && mapRef.current && viewport.zoom >= 10) {
const features = mapRef.current.queryRenderedFeatures(null, {layers: ['public-place', 'limited-access']})
const visiblePlaces = features.map(f => f.properties.id)
setPlaces(prelevementsSites.features.filter(({properties}) => visiblePlaces.includes(properties.id)).map(({properties}) => properties))
if (viewport.zoom >= 9) {
const places = getVisiblePlaces()
setPlaces(places)
}
}, [mapRef, viewport, setPlaces, prelevementsSites])
}, [address, viewport, setPlaces, prelevementsSites, getVisiblePlaces])

const addressLayer = {
type: 'symbol',
paint: {
'text-halo-color': '#fff',
'text-halo-width': 4
},
layout: {
'text-field': '{name}',
'text-size': 16,
'text-offset': [0, -3],
'text-anchor': 'top'
}
}

return (
<ReactMapGL
Expand All @@ -153,8 +213,26 @@ const PrelevementsMap = () => {
<Source data={prelevementsSites} type='geojson'>
<Layer {...publicPlaceLayer} />
<Layer {...limitedAccessPlaceLayer} />
<Layer id='place-name' {...addressLayer} filter={['==', ['get', 'id'], selectedPlaceId]} />
</Source>

{address && (
<>
<Source type='geojson' data={address}>
<Layer id='address-name' {...addressLayer} />
</Source>

<Marker
latitude={address.geometry.coordinates[1]}
longitude={address.geometry.coordinates[0]}
offsetLeft={-20}
offsetTop={-20}
>
<img src='./icons/Map_pin_icon.svg' width='20' />
</Marker>
</>
)}

{hovered && (
<Popup
longitude={hovered.longitude}
Expand All @@ -167,6 +245,12 @@ const PrelevementsMap = () => {
<PlaceSumup {...hovered.feature.properties} />
</Popup>
)}

<style jsx>{`
.marker {
text-align: center;
}
`}</style>
</ReactMapGL>
)
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"dependencies": {
"@etalab/decoupage-administratif": "^0.8.0",
"@turf/bbox": "^6.0.1",
"@turf/nearest-point": "^6.0.1",
"chart.js": "^2.9.3",
"csv-parser": "^2.3.2",
"deck.gl": "^8.1.1",
Expand Down
129 changes: 129 additions & 0 deletions public/icons/Map_pin_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit deb20f5

Please sign in to comment.