-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(export): add France interactive map (#31)
* wip * wip 2 * hover : select department * feat: add a centroid tooltip * rework all * feat: create final hook to compute departments * fix import
- Loading branch information
1 parent
08b4db2
commit faa651e
Showing
11 changed files
with
414 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { useEffect } from "react"; | ||
import { makeStyles } from "tss-react/dsfr"; | ||
import { useDepartments } from "../hooks/useDepartments"; | ||
import { useMap } from "../hooks/useMap"; | ||
import { useToolTipMap } from "../hooks/useTooltipMap"; | ||
import { LoaderOrErrorContainer } from "./ui/LoaderOrErrorContainer"; | ||
|
||
const useStyles = makeStyles()(theme => ({ | ||
container: { | ||
display: "grid", | ||
gridTemplateRow: "1fr", | ||
gridTemplateColumn: "1fr", | ||
/* width: "100%", | ||
height: "100%", */ | ||
maxWidth: 800, | ||
minHeight: 350, | ||
maxHeight: 800, | ||
}, | ||
mapContainer: { | ||
height: "100%", | ||
width: "100%", | ||
borderWidth: 1, | ||
borderStyle: "solid", | ||
borderColor: theme.decisions.artwork.motif.grey.default, | ||
gridColumn: 1, | ||
gridRow: 1, | ||
}, | ||
loadingContainer: { | ||
gridColumn: 1, | ||
gridRow: 1, | ||
zIndex: 2, | ||
display: "flex", | ||
alignSelf: "center", | ||
justifyContent: "center", | ||
}, | ||
})); | ||
|
||
type Props = { | ||
className?: string; | ||
}; | ||
|
||
export const FrenchMap = ({ className }: Props) => { | ||
const { classes, cx } = useStyles(); | ||
const { isLoading, isError, refetch, departmentLayer, departmentsExtent } = useDepartments(); | ||
const { map } = useMap("map"); | ||
useToolTipMap({ map, layer: departmentLayer }); | ||
|
||
useEffect(() => { | ||
if (map === undefined) return; | ||
if (departmentLayer === undefined) return; | ||
|
||
map.addLayer(departmentLayer); | ||
|
||
return () => { | ||
map.removeLayer(departmentLayer); | ||
}; | ||
}, [departmentLayer]); | ||
|
||
useEffect(() => { | ||
if (map === undefined || departmentsExtent === undefined) return; | ||
map.getView().fit(departmentsExtent, { padding: [10, 10, 10, 10] }); | ||
}, [map, departmentsExtent]); | ||
|
||
return ( | ||
<div className={cx(classes.container, className)}> | ||
{(isLoading || isError) && ( | ||
<div className={cx(classes.mapContainer, classes.loadingContainer)}> | ||
<LoaderOrErrorContainer isLoading={isLoading} isError={isError} refetch={refetch} /> | ||
</div> | ||
)} | ||
<div id="map" className={classes.mapContainer}></div> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import Button from "@codegouvfr/react-dsfr/Button"; | ||
import { CircularProgress } from "@mui/material"; | ||
import { useMemo } from "react"; | ||
import { makeStyles } from "tss-react/dsfr"; | ||
|
||
type Props = { | ||
isLoading: boolean; | ||
isError: boolean; | ||
refetch(): void; | ||
}; | ||
|
||
const useStyles = makeStyles()({ | ||
loaderOrErrorContainer: { | ||
width: "100%", | ||
flex: 1, | ||
display: "flex", | ||
alignItems: "center", | ||
justifyContent: "center", | ||
}, | ||
errorContainer: { | ||
alignItems: "center", | ||
display: "flex", | ||
flexDirection: "column", | ||
}, | ||
}); | ||
|
||
export const LoaderOrErrorContainer = ({ isLoading, isError, refetch }: Props) => { | ||
const { classes } = useStyles(); | ||
console.error(isError); | ||
|
||
const content = useMemo(() => { | ||
if (isLoading) return <CircularProgress />; | ||
if (isError) | ||
return ( | ||
<div className={classes.errorContainer}> | ||
<p>Un problème est survenu.</p> | ||
<Button onClick={refetch}>Réessayer</Button> | ||
</div> | ||
); | ||
return null; | ||
}, [isLoading, isError, refetch]); | ||
|
||
return <div className={classes.loaderOrErrorContainer}>{content}</div>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import Feature from "ol/Feature"; | ||
import GeoJSON from "ol/format/GeoJSON"; | ||
import { Geometry } from "ol/geom"; | ||
import VectorLayer from "ol/layer/Vector"; | ||
import VectorSource from "ol/source/Vector"; | ||
import Fill from "ol/style/Fill"; | ||
import Stroke from "ol/style/Stroke"; | ||
import Style, { StyleFunction } from "ol/style/Style"; | ||
import { useCallback, useEffect, useMemo, useState } from "react"; | ||
import { useQuery } from "react-query"; | ||
import { getAllDepartments } from "../api/cosiaApi"; | ||
import { | ||
computeDepartmentFeature, | ||
getAllDepartmentsExtent, | ||
getFillColorFromStatus, | ||
} from "../utils/departments"; | ||
|
||
export const useDepartments = () => { | ||
const [departementFeatures, setdepartementFeatures] = useState<Feature<Geometry>[]>([]); | ||
const [departmentLayer, setDepartmentLayer] = useState<VectorLayer<VectorSource> | undefined>( | ||
undefined, | ||
); | ||
|
||
const { | ||
isLoading, | ||
isError, | ||
data: departments, | ||
refetch, | ||
} = useQuery({ | ||
queryKey: ["department"], | ||
queryFn: () => getAllDepartments(), | ||
staleTime: 60_000, | ||
}); | ||
|
||
const departmentsExtent = useMemo( | ||
() => getAllDepartmentsExtent(departementFeatures), | ||
[departementFeatures], | ||
); | ||
|
||
useEffect(() => { | ||
if (departments === undefined || departments.length === 0) return; | ||
|
||
const format = new GeoJSON(); | ||
const features: Feature<Geometry>[] = departments.map(dep => computeDepartmentFeature(dep, format)); | ||
|
||
setdepartementFeatures(features); | ||
}, [departments]); | ||
|
||
const setStyle: StyleFunction = useCallback(feature => { | ||
const status = feature.get("status"); | ||
const fillColor = getFillColorFromStatus(status); | ||
return new Style({ | ||
stroke: new Stroke({ | ||
color: "#FFF", | ||
width: 1, | ||
}), | ||
fill: new Fill({ | ||
color: fillColor, | ||
}), | ||
}); | ||
}, []); | ||
|
||
useEffect(() => { | ||
if (departementFeatures.length === 0) return; | ||
|
||
const newSource = new VectorSource({ | ||
features: departementFeatures, | ||
}); | ||
|
||
const departmentLayer = new VectorLayer({ source: newSource }); | ||
departmentLayer.setStyle(setStyle); | ||
|
||
setDepartmentLayer(departmentLayer); | ||
}, [departementFeatures]); | ||
|
||
return { isLoading, isError, refetch, departmentLayer, departmentsExtent }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { Map, View } from "ol"; | ||
import { fromLonLat } from "ol/proj"; | ||
import { useEffect, useMemo, useState } from "react"; | ||
|
||
const MIN_ZOOM = 4; | ||
const MAX_ZOOM = 12; | ||
const MAX_EXTENT = [...fromLonLat([-3.2, 45.8]), ...fromLonLat([15.8, 55.8])]; | ||
|
||
export const useMap = (target: string, minZoom = MIN_ZOOM, maxZoom = MAX_ZOOM, extent = MAX_EXTENT) => { | ||
const [map, setMap] = useState<Map | undefined>(undefined); | ||
|
||
const view = useMemo( | ||
() => | ||
new View({ | ||
minZoom: minZoom, | ||
maxZoom: maxZoom, | ||
extent: extent, | ||
}), | ||
[], | ||
); | ||
|
||
useEffect(() => { | ||
const map = new Map({ | ||
target, | ||
view, | ||
}); | ||
|
||
setMap(map); | ||
|
||
return () => map.setTarget(undefined); | ||
}, []); | ||
|
||
return { map }; | ||
}; |
Oops, something went wrong.