2024-05-06 18:07:29 +02:00
|
|
|
|
import {
|
2022-02-08 12:49:51 +01:00
|
|
|
|
useState,
|
|
|
|
|
useContext,
|
|
|
|
|
useRef,
|
|
|
|
|
useEffect,
|
|
|
|
|
useMemo,
|
|
|
|
|
ReactNode,
|
2022-02-22 13:14:11 +01:00
|
|
|
|
createContext,
|
|
|
|
|
useCallback
|
2022-02-08 12:49:51 +01:00
|
|
|
|
} from 'react';
|
2024-09-20 18:22:49 +02:00
|
|
|
|
import { createPortal } from 'react-dom';
|
|
|
|
|
import { Map, NavigationControl } from 'maplibre-gl';
|
|
|
|
|
import type { StyleSpecification, IControl } from 'maplibre-gl';
|
|
|
|
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
2022-02-08 12:49:51 +01:00
|
|
|
|
|
|
|
|
|
import invariant from 'tiny-invariant';
|
|
|
|
|
|
2023-08-31 18:16:14 +02:00
|
|
|
|
import { useStyle, useElementVisible } from './hooks';
|
2024-09-20 18:22:49 +02:00
|
|
|
|
import { StyleSwitch } from './StyleControl';
|
2022-02-08 12:49:51 +01:00
|
|
|
|
|
|
|
|
|
const Context = createContext<{ map?: Map | null }>({});
|
|
|
|
|
|
|
|
|
|
type MapLibreProps = {
|
|
|
|
|
layers: string[];
|
|
|
|
|
children: ReactNode;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function useMapLibre() {
|
|
|
|
|
const context = useContext(Context);
|
|
|
|
|
invariant(context.map, 'Maplibre not initialized');
|
|
|
|
|
return context.map;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-24 17:20:17 +01:00
|
|
|
|
export function MapLibre({ children, layers }: MapLibreProps) {
|
2024-09-20 18:22:49 +02:00
|
|
|
|
const isSupported = useMemo(() => isWebglSupported(), []);
|
2022-02-08 12:49:51 +01:00
|
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
2023-08-31 18:16:14 +02:00
|
|
|
|
const visible = useElementVisible(containerRef);
|
2022-02-08 12:49:51 +01:00
|
|
|
|
const [map, setMap] = useState<Map | null>();
|
2024-09-20 18:22:49 +02:00
|
|
|
|
const [styleControlElement, setStyleControlElement] =
|
|
|
|
|
useState<HTMLElement | null>(null);
|
2022-02-08 12:49:51 +01:00
|
|
|
|
|
2022-02-22 13:14:11 +01:00
|
|
|
|
const onStyleChange = useCallback(
|
2024-09-20 18:22:49 +02:00
|
|
|
|
(style: StyleSpecification) => {
|
2022-02-22 13:14:11 +01:00
|
|
|
|
if (map) {
|
|
|
|
|
map.setStyle(style);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[map]
|
|
|
|
|
);
|
2022-02-08 12:49:51 +01:00
|
|
|
|
const { style, ...mapStyleProps } = useStyle(layers, onStyleChange);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2023-08-31 18:16:14 +02:00
|
|
|
|
if (isSupported && visible && !map) {
|
2022-02-08 12:49:51 +01:00
|
|
|
|
invariant(containerRef.current, 'Map container not found');
|
|
|
|
|
const map = new Map({
|
|
|
|
|
container: containerRef.current,
|
|
|
|
|
style
|
|
|
|
|
});
|
|
|
|
|
map.addControl(new NavigationControl({}), 'top-right');
|
2024-09-20 18:22:49 +02:00
|
|
|
|
const styleControl = new ReactControl();
|
|
|
|
|
map.addControl(styleControl, 'bottom-left');
|
2022-02-08 12:49:51 +01:00
|
|
|
|
map.on('load', () => {
|
|
|
|
|
setMap(map);
|
2024-09-20 18:22:49 +02:00
|
|
|
|
setStyleControlElement(styleControl.container);
|
2022-02-08 12:49:51 +01:00
|
|
|
|
});
|
|
|
|
|
}
|
2023-08-31 18:16:14 +02:00
|
|
|
|
}, [map, style, visible, isSupported]);
|
2022-02-08 12:49:51 +01:00
|
|
|
|
|
|
|
|
|
if (!isSupported) {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
style={{ marginBottom: '20px' }}
|
|
|
|
|
className="outdated-browser-banner site-banner"
|
|
|
|
|
>
|
|
|
|
|
<div className="container">
|
|
|
|
|
<div className="site-banner-icon">⚠️</div>
|
|
|
|
|
<div className="site-banner-text">
|
2023-12-22 11:39:09 +01:00
|
|
|
|
Nous ne pouvons pas afficher la carte car elle est incompatible avec
|
2022-02-08 12:49:51 +01:00
|
|
|
|
votre navigateur. Nous vous conseillons de le mettre à jour ou
|
|
|
|
|
d’utiliser{' '}
|
|
|
|
|
<a
|
|
|
|
|
href="https://browser-update.org/fr/update.html"
|
|
|
|
|
target="_blank"
|
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
|
>
|
|
|
|
|
un navigateur plus récent
|
|
|
|
|
</a>
|
|
|
|
|
.
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Context.Provider value={{ map }}>
|
|
|
|
|
<div ref={containerRef} style={{ height: '500px' }}>
|
2024-09-20 18:22:49 +02:00
|
|
|
|
{styleControlElement != null
|
|
|
|
|
? createPortal(
|
|
|
|
|
<StyleSwitch styleId={style.id} {...mapStyleProps} />,
|
|
|
|
|
styleControlElement
|
|
|
|
|
)
|
|
|
|
|
: null}
|
2022-02-08 12:49:51 +01:00
|
|
|
|
{map ? children : null}
|
|
|
|
|
</div>
|
|
|
|
|
</Context.Provider>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 18:22:49 +02:00
|
|
|
|
function isWebglSupported() {
|
|
|
|
|
if (window.WebGLRenderingContext) {
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
|
try {
|
|
|
|
|
// Note that { failIfMajorPerformanceCaveat: true } can be passed as a second argument
|
|
|
|
|
// to canvas.getContext(), causing the check to fail if hardware rendering is not available. See
|
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext
|
|
|
|
|
// for more details.
|
|
|
|
|
const context = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
|
|
|
|
if (context && typeof context.getParameter == 'function') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// WebGL is supported, but disabled
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// WebGL not supported
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class ReactControl implements IControl {
|
|
|
|
|
#container: HTMLElement | null = null;
|
|
|
|
|
|
|
|
|
|
get container(): HTMLElement | null {
|
|
|
|
|
return this.#container;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onAdd(): HTMLElement {
|
|
|
|
|
this.#container = document.createElement('div');
|
|
|
|
|
this.#container.className = 'maplibregl-ctrl maplibregl-ctrl-group ds-ctrl';
|
|
|
|
|
return this.#container;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onRemove(): void {
|
|
|
|
|
this.#container?.remove();
|
|
|
|
|
this.#container = null;
|
|
|
|
|
}
|
2022-02-08 12:49:51 +01:00
|
|
|
|
}
|