Add usePointLayer hook.
This commit is contained in:
@@ -7,8 +7,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function MapContainer({ children }: Props) {
|
export function MapContainer({ children }: Props) {
|
||||||
const mapDivRef = useRef<HTMLDivElement>(null);
|
|
||||||
const { initializeMap } = useMapApi();
|
const { initializeMap } = useMapApi();
|
||||||
|
const mapDivRef = useRef<HTMLDivElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!mapDivRef.current) return;
|
if (!mapDivRef.current) return;
|
||||||
const map = new Map({
|
const map = new Map({
|
||||||
|
|||||||
20
src/hooks/layers/useLayerMount.ts
Normal file
20
src/hooks/layers/useLayerMount.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import Layer from "ol/layer/Layer";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useMapApi } from "../mapApi";
|
||||||
|
|
||||||
|
/** Wait for map to be ready, and then add the given named layer to it.
|
||||||
|
* @returns if layer has been mounted and is ready to use
|
||||||
|
*/
|
||||||
|
export function useLayerMount(layerName: string, layer: Layer) {
|
||||||
|
const { isMapReady, addLayer } = useMapApi();
|
||||||
|
const [isLayerReady, setIsLayerReady] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMapReady) {
|
||||||
|
addLayer(layerName, layer);
|
||||||
|
setIsLayerReady(true);
|
||||||
|
}
|
||||||
|
}, [isMapReady]);
|
||||||
|
|
||||||
|
return isLayerReady;
|
||||||
|
}
|
||||||
68
src/hooks/layers/usePointLayer.ts
Normal file
68
src/hooks/layers/usePointLayer.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { useVectorLayerHook } from "./vectorLayerApi";
|
||||||
|
import { useMapApi } from "../mapApi";
|
||||||
|
import VectorLayer from "ol/layer/Vector";
|
||||||
|
import VectorSource from "ol/source/Vector";
|
||||||
|
import { useLayerMount } from "./useLayerMount";
|
||||||
|
import { Feature } from "ol";
|
||||||
|
import { Point } from "ol/geom";
|
||||||
|
|
||||||
|
interface PointFeature {
|
||||||
|
id?: string;
|
||||||
|
lat: number;
|
||||||
|
lon: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePointLayer: useVectorLayerHook<PointFeature> = (layerName: string) => {
|
||||||
|
const { getLayerSource } = useMapApi();
|
||||||
|
const isLayerReady = useLayerMount(
|
||||||
|
layerName,
|
||||||
|
new VectorLayer({
|
||||||
|
source: new VectorSource()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const add = (points: PointFeature[]) => {
|
||||||
|
if (isLayerReady) {
|
||||||
|
const source = getLayerSource(layerName) as VectorSource;
|
||||||
|
const features = points.map((point, i) => new Feature({
|
||||||
|
id: point.id ?? i.toString(),
|
||||||
|
geometry: new Point([point.lon, point.lat]),
|
||||||
|
}));
|
||||||
|
source.addFeatures(features);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = (ids: string[]) => {
|
||||||
|
if (isLayerReady) {
|
||||||
|
const source = getLayerSource(layerName) as VectorSource;
|
||||||
|
for (const id of ids) {
|
||||||
|
const feature = source.getFeatureById(id);
|
||||||
|
if (feature) {
|
||||||
|
source.removeFeature(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
if (isLayerReady) {
|
||||||
|
const source = getLayerSource(layerName) as VectorSource;
|
||||||
|
source.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const get = (id: string) => {
|
||||||
|
if (isLayerReady) {
|
||||||
|
const source = getLayerSource(layerName) as VectorSource;
|
||||||
|
return source.getFeatureById(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLayerReady,
|
||||||
|
add,
|
||||||
|
remove,
|
||||||
|
clear,
|
||||||
|
get,
|
||||||
|
};
|
||||||
|
};
|
||||||
11
src/hooks/layers/vectorLayerApi.ts
Normal file
11
src/hooks/layers/vectorLayerApi.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Feature } from "ol";
|
||||||
|
|
||||||
|
interface VectorLayerApi<T> {
|
||||||
|
isLayerReady: boolean;
|
||||||
|
add: (features: T[]) => void;
|
||||||
|
remove: (featureIds: string[]) => void;
|
||||||
|
clear: () => void;
|
||||||
|
get: (id: string) => Feature | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type useVectorLayerHook<T> = (layerName: string) => VectorLayerApi<T>;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Map } from "ol";
|
import { Map } from "ol";
|
||||||
import Layer from "ol/layer/Layer";
|
import Layer from "ol/layer/Layer";
|
||||||
|
import { Source } from "ol/source";
|
||||||
import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
|
import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,6 +34,15 @@ interface MapAPI {
|
|||||||
*/
|
*/
|
||||||
getLayer: (name: string) => Layer | undefined;
|
getLayer: (name: string) => Layer | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a layer's source
|
||||||
|
* @returns The Open Layers Source associated with the layer with the given name
|
||||||
|
* @throws NullMapError if map is not initialized
|
||||||
|
* @throws Error if no layer is found with the given name
|
||||||
|
* @throws Error if the layer with the given name doesn't have a Source defined.
|
||||||
|
*/
|
||||||
|
getLayerSource: (name: string) => Source;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a layer to the map
|
* Add a layer to the map
|
||||||
* @param name The name that will be associated with the Layer
|
* @param name The name that will be associated with the Layer
|
||||||
@@ -66,35 +76,47 @@ interface Props {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MapAPIProvider({ children }: Props) {
|
export function MapAPIProvider({ children }: Props) {
|
||||||
const mapRef = useRef<Map | null>(null);
|
const mapRef = useRef<Map | null>(null);
|
||||||
const [layers, setLayers] = useState<Record<string, Layer>>({});
|
const [layers, setLayers] = useState<Record<string, Layer>>({});
|
||||||
|
const [isMapReady, setIsMapReady] = useState(false);
|
||||||
|
|
||||||
const initializeMap = useCallback((map: Map) => {
|
const initializeMap = useCallback((map: Map) => {
|
||||||
if (mapRef.current) {
|
if (mapRef.current) {
|
||||||
throw new Error('Map has already been initialized!')
|
throw new Error('Map has already been initialized!')
|
||||||
}
|
}
|
||||||
mapRef.current = map;
|
mapRef.current = map;
|
||||||
}, [mapRef.current]);
|
setIsMapReady(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const getMap = useCallback(() => {
|
const getMap = useCallback(() => {
|
||||||
if (!mapRef.current) {
|
if (!mapRef.current) {
|
||||||
throw new NullMapError();
|
throw new NullMapError();
|
||||||
}
|
}
|
||||||
return mapRef.current;
|
return mapRef.current;
|
||||||
}, [mapRef.current]);
|
}, []);
|
||||||
|
|
||||||
const isMapReady = useMemo(
|
|
||||||
() => mapRef.current === null,
|
|
||||||
[mapRef.current]
|
|
||||||
);
|
|
||||||
|
|
||||||
const getLayer = useCallback((name: string) => {
|
const getLayer = useCallback((name: string) => {
|
||||||
if (!mapRef.current) {
|
if (!mapRef.current) {
|
||||||
throw new NullMapError();
|
throw new NullMapError();
|
||||||
}
|
}
|
||||||
return layers[name];
|
return layers[name];
|
||||||
}, [mapRef.current, layers]);
|
}, [layers]);
|
||||||
|
|
||||||
|
const getLayerSource = useCallback((name: string) => {
|
||||||
|
if (!mapRef.current) {
|
||||||
|
throw new NullMapError();
|
||||||
|
}
|
||||||
|
const layer = layers[name];
|
||||||
|
if (!layer) {
|
||||||
|
throw new Error(`Layer "${name}" does not exist!`);
|
||||||
|
}
|
||||||
|
const source = layer.getSource();
|
||||||
|
if (!source) {
|
||||||
|
throw new Error(`Layer "${name}" has no Source!`);
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}, [layers]);
|
||||||
|
|
||||||
const addLayer = useCallback((name: string, layer: Layer) => {
|
const addLayer = useCallback((name: string, layer: Layer) => {
|
||||||
if (!mapRef.current) {
|
if (!mapRef.current) {
|
||||||
@@ -105,7 +127,7 @@ function MapAPIProvider({ children }: Props) {
|
|||||||
...prev,
|
...prev,
|
||||||
[name]: layer,
|
[name]: layer,
|
||||||
}));
|
}));
|
||||||
}, [mapRef.current]);
|
}, [setLayers]);
|
||||||
|
|
||||||
const removeLayer = useCallback((name: string) => {
|
const removeLayer = useCallback((name: string) => {
|
||||||
if (!mapRef.current) {
|
if (!mapRef.current) {
|
||||||
@@ -122,6 +144,7 @@ function MapAPIProvider({ children }: Props) {
|
|||||||
getMap,
|
getMap,
|
||||||
isMapReady,
|
isMapReady,
|
||||||
getLayer,
|
getLayer,
|
||||||
|
getLayerSource,
|
||||||
addLayer,
|
addLayer,
|
||||||
removeLayer,
|
removeLayer,
|
||||||
}}>
|
}}>
|
||||||
|
|||||||
Reference in New Issue
Block a user