Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ajout des tuiles vectorielles et des events lors du clique sur un collège #15

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
182 changes: 5 additions & 177 deletions components/map/index.js
Original file line number Diff line number Diff line change
@@ -1,180 +1,8 @@
import {useEffect, useRef, useCallback, useMemo} from 'react'
import PropTypes from 'prop-types'
import maplibregl from 'maplibre-gl'
import dynamic from 'next/dynamic'

import {sources} from '@/components/map/sources.js'
import {layers} from '@/components/map/layers.js'
import colors from '@/styles/colors.js'

const itineraireLayer = {
id: 'itineraire-line',
type: 'line',
source: 'itineraire',
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': colors.darkGrey,
'line-width': 4
}
}

const Map = ({selectedAdresse, collegeFeature, collegeItineraire, isMobileDevice}) => {
const mapContainer = useRef(null)
const adresseMarker = useRef(null)
const adressePopup = useRef(null)
const collegeMarker = useRef(null)
const collegePopup = useRef(null)
const sourcesLoaded = useRef(false)
const layersLoaded = useRef(false)
const map = useRef(null)

useEffect(() => {
const maplibre = new maplibregl.Map({
container: mapContainer.current,
style: 'https://openmaptiles.geo.data.gouv.fr/styles/osm-bright/style.json',
center: [1.7, 46.9],
zoom: 4,
attributionControl: false
})
.addControl(new maplibregl.AttributionControl({
compact: false
}))

maplibre.once('load', () => {
maplibre.addControl(new maplibregl.NavigationControl({showCompass: false}))

for (const source of sources) {
maplibre.addSource(source.id, source.options)
}

for (const layer of layers) {
maplibre.addLayer(layer)
}
})

map.current = maplibre

return () => {
maplibre.remove()
}
}, [])

useEffect(() => {
if (selectedAdresse && collegeFeature && map?.current) {
const adressePosition = selectedAdresse.geometry.coordinates

const adresseMarkerElement = document.createElement('div') // eslint-disable-line no-undef
const collegeMarkerElement = document.createElement('div') // eslint-disable-line no-undef

const currentAdressePopup = new maplibregl.Popup({offset: 25, closeOnClick: false, closeButton: false})
.setLngLat(adressePosition)
.setText(selectedAdresse.properties.label)
.addTo(map.current)

const currentAdresseMarker = new maplibregl.Marker(adresseMarkerElement)
.setLngLat(adressePosition)
.addTo(map.current)

const currentCollegePopup = new maplibregl.Popup({offset: 25, closeOnClick: false, closeButton: false})
.setLngLat(collegeFeature.geometry.coordinates)
.setText(collegeFeature.properties.nom)
.addTo(map.current)

const currentCollegeMarker = new maplibregl.Marker(collegeMarkerElement)
.setLngLat(collegeFeature.geometry.coordinates)
.addTo(map.current)

currentAdresseMarker.getElement().innerHTML = `<img width="${isMobileDevice ? '400px' : ''}" src="/images/map/home.svg">`
currentCollegeMarker.getElement().innerHTML = `<img width="${isMobileDevice ? '400px' : ''}" src="/images/map/school.svg">`

adresseMarker.current = currentAdresseMarker
adressePopup.current = currentAdressePopup
collegeMarker.current = currentCollegeMarker
collegePopup.current = currentCollegePopup

map.current.fitBounds([
adressePosition,
collegeFeature.geometry.coordinates
], {padding: isMobileDevice ? 50 : 200})
}

return () => {
if (adresseMarker.current) {
adresseMarker.current.remove()
}

if (adressePopup.current) {
adressePopup.current.remove()
}

if (collegeMarker.current) {
collegeMarker.current.remove()
}

if (collegePopup.current) {
collegePopup.current.remove()
}
}
}, [selectedAdresse, collegeFeature, map, isMobileDevice])

const createOrChangeSource = useCallback(() => {
if (collegeItineraire) {
if (!sourcesLoaded.current) {
map.current.addSource('itineraire', {
type: 'geojson',
data: {
type: 'Feature',
geometry: collegeItineraire.geometry
}
})

sourcesLoaded.current = true
}

if (!layersLoaded.current) {
map.current.addLayer(itineraireLayer)

layersLoaded.current = true
}

map.current.getSource('itineraire').setData({
type: 'Feature',
geometry: collegeItineraire?.geometry
})
}
}, [collegeItineraire])

const memorizedChangingSource = useMemo(() => createOrChangeSource, [createOrChangeSource])

useEffect(() => {
if (map.current) {
map.current.on('sourcedata', e => {
if (e.isSourceLoaded) {
memorizedChangingSource()
}
})
}
}, [map, memorizedChangingSource])

return (
<div ref={mapContainer} style={{width: '100%', height: '100%'}} />
)
}

Map.propTypes = {
selectedAdresse: PropTypes.object,
collegeFeature: PropTypes.object,
collegeItineraire: PropTypes.object,
isMobileDevice: PropTypes.bool
}

Map.defaultProps = {
selectedAdresse: null,
collegeFeature: null,
collegeItineraire: null,
isMobileDevice: false
}
const Map = dynamic(
() => import('./map.js'),
{ssr: false}
)
Comment on lines +3 to +6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il n'est pas utile d'importer dynamiquement ce composant. La création de la carte se fait dans un componentDidMount, le code sera donc bien exécuté côté client. Ici, c'est le fait que les sources soient calculées à l'aide de window qui elles sont calculées côté serveur qui pose un problème.


export default Map
48 changes: 48 additions & 0 deletions components/map/layers.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,50 @@
import colors from '@/styles/colors.js'

export const layers = [
{
id: 'secteurs-lines',
source: 'secteurs',
'source-layer': 'secteurs',
type: 'line',
paint: {
'line-color': colors.darkGrey,
'line-width': 1
},
layout: {
visibility: 'none'
}
},
{
id: 'secteurs-fill',
source: 'secteurs',
'source-layer': 'secteurs',
type: 'fill',
paint: {
'fill-color': colors.blue,
'fill-opacity': 0.1
},
layout: {
visibility: 'none'
}
},
{
id: 'colleges',
source: 'secteurs',
'source-layer': 'colleges',
type: 'circle',
paint: {
'circle-color': [
'case',
['boolean', ['feature-state', 'selected'], false],
colors.blue,
colors.green
],
'circle-radius': 10
},
filter: [
'all',
['==', 'secteur', 'Public'],
['!has', 'erreur']
]
}
]
87 changes: 87 additions & 0 deletions components/map/legend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {useState} from 'react'
import {uniqueId} from 'lodash-es'

import colors from '@/styles/colors.js'

const legend = [
{
circle: true,
color: colors.green,
libelle: 'Autre collège'
},
{
circle: true,
color: colors.blue,
libelle: 'Autre collège sélectionné'
},
{
circle: false,
color: 'rgba(0, 83, 179, 0.1)',
libelle: 'Zones de rattachement'
},
{
circle: false,
color: 'rgba(0, 83, 179, 0.4)',
libelle: 'Zones de rattachement du collège sélectionné'
}
]

const Legend = () => {
const [isWrap, setIsWrap] = useState(true)

return (
<div className='legend'
onMouseEnter={() => setIsWrap(false)}
onMouseLeave={() => setIsWrap(true)}
>
<div>Légende</div>

{!isWrap && (
<div className='legend-wrapper'>
{legend.map(({libelle, color, circle}) => (
<div key={uniqueId()}>
<div key={libelle} className='legend-container'>
<div className='legend-color' style={{backgroundColor: `${color}`, borderRadius: `${circle ? '25px' : ''}`}} />
<div>{libelle}</div>
</div>
</div>
))}
</div>
)}

<style jsx>{`
.legend {
position: absolute;
margin-top: 1em;
margin-left: 1em;
box-shadow: none;
border: 2px solid #dcd8d5;
border-radius: 4px;
z-index: 1;
padding: 0.5em;
background-color: rgba(255, 255, 255, 0.8);
max-width: 1000px;
}

.legend-wrapper {
margin-top: 1em;
}

.legend-container {
margin: .5em 0;
display: flex;
align-items: center;
}

.legend-color {
width: 15px;
height: 15px;
margin-right: 0.5em;
}
`}</style>
</div>

)
}

export default Legend
Loading