@eightshift/frontend-libs
Version:
A collection of useful frontend utility modules. powered by Eightshift
403 lines (344 loc) • 8.27 kB
JavaScript
import React, { createContext, useRef, useState, useEffect, useContext } from 'react';
import { Map as OLMap, View } from 'ol/index.js';
import { OSM, Vector as VectorSource, VectorTile as VectorTileSource, XYZ, TileJSON, } from 'ol/source';
import { Tile as OLTileLayer, Vector as OLVectorLayer, VectorTile as OLVectorTile } from 'ol/layer';
import {MapboxVectorLayer} from 'ol-mapbox-style';
import OLVectorTileLayer from 'ol/layer/VectorTile';
import { useGeographic } from 'ol/proj';
import { MVT, GeoJSON } from 'ol/format';
import { defaults as OLDefaultInteractions } from 'ol/interaction/defaults';
import { defaults as OLDefaultControls } from 'ol/control/defaults';
import OLAttribution from 'ol/control/Attribution.js';
import OLFullScreen from 'ol/control/FullScreen.js';
import OLMousePosition from 'ol/control/MousePosition.js';
import OLOverviewMap from 'ol/control/OverviewMap.js';
import OLRotate from 'ol/control/Rotate.js';
import OLScaleLine from 'ol/control/ScaleLine.js';
import OLZoomSlider from 'ol/control/ZoomSlider.js';
import OLZoomToExtent from 'ol/control/ZoomToExtent.js';
import OLZoom from 'ol/control/Zoom.js';
import { applyStyle as OLMBStyleApply } from 'ol-mapbox-style';
import { Style, Fill, Stroke, Icon } from 'ol/style';
import manifest from '../manifest.json';
export const MapContext = new createContext();
export const OpenLayersMap = ({ children, zoom, center, blockClass }) => {
const mapRef = useRef();
const [map, setMap] = useState(null);
// Map init.
useEffect(() => {
useGeographic();
let options = {
view: new View({ zoom, center }),
layers: [],
interactions: [],
controls: [],
};
const mapObject = new OLMap(options);
mapObject.setTarget(mapRef.current);
setMap(mapObject);
return () => mapObject.setTarget(undefined);
}, []);
// Default zoom level change.
useEffect(() => {
if (!map) {
return;
}
map.getView().setZoom(zoom);
}, [zoom]);
// Default center point change.
useEffect(() => {
if (!map) {
return;
}
map.getView().setCenter(center);
}, [center]);
return (
<MapContext.Provider value={{ map }}>
<div ref={mapRef} className={blockClass}>
{children}
</div>
</MapContext.Provider>
);
};
export const Layers = ({ children }) => children;
export const Interactions = ({ children }) => children;
export const Overlays = ({ children }) => children;
export const Controls = ({ children }) => children;
export const MapLayer = (props) => {
const {
type = 'tile',
source,
accessToken,
styleUrl,
style,
} = props;
const { map } = useContext(MapContext);
useEffect(() => {
if (!map) {
return;
}
let tileLayer;
switch (type) {
case 'mapboxVector':
tileLayer = new MapboxVectorLayer({
styleUrl,
accessToken,
});
break;
case 'vectorJson':
tileLayer = new OLVectorTileLayer({ declutter: true });
break;
case 'vectorTile':
tileLayer = new OLVectorTile({
source,
});
break;
case 'vector':
tileLayer = new OLVectorLayer({
source,
style,
});
break;
default:
tileLayer = new OLTileLayer({
source,
});
break;
}
if (type === 'vectorJson') {
OLMBStyleApply(tileLayer, styleUrl, { accessToken: accessToken });
}
map.addLayer(tileLayer);
return () => {
if (map) {
map.removeLayer(tileLayer);
}
};
}, [map]);
return null;
};
export const MapInteraction = (props) => {
const {
type = 'defaults',
options = {},
} = props;
const { map } = useContext(MapContext);
useEffect(() => {
if (!map) {
return;
}
let interaction;
switch (type) {
default:
interaction = OLDefaultInteractions(options).getArray();
break;
}
if (Array.isArray(interaction)) {
interaction.forEach((i) => map.addInteraction(i));
} else {
map.addInteraction(interaction);
}
return () => {
if (map) {
if (Array.isArray(interaction)) {
interaction.forEach((i) => map.removeInteraction(i));
} else {
map.removeInteraction(interaction);
}
}
};
}, [map]);
return null;
};
export const MapControl = (props) => {
const {
type = 'defaults',
options = {},
} = props;
const { map } = useContext(MapContext);
useEffect(() => {
if (!map) {
return;
}
let control;
switch (type) {
case 'attribution':
control = new OLAttribution(options);
break;
case 'fullScreen':
control = new OLFullScreen(options);
break;
case 'mousePosition':
control = new OLMousePosition(options);
break;
case 'overviewMap':
control = new OLOverviewMap(Object.keys(options).length > 0 ? options : {
layers: [
new OLTileLayer({ source: new OSM() }),
],
mapOptions: {
maxResolution: 0.0015,
numZoomLevels: 2,
}
});
break;
case 'rotate':
control = new OLRotate(options);
break;
case 'scaleLine':
control = new OLScaleLine(options);
break;
case 'zoomSlider':
control = new OLZoomSlider(options);
break;
case 'zoomToExtent':
control = new OLZoomToExtent(options);
break;
case 'zoom':
control = new OLZoom(options);
break;
default:
control = OLDefaultControls(options).getArray();
break;
}
if (Array.isArray(control)) {
control.forEach((i) => map.addControl(i));
} else {
map.addControl(control);
}
return () => {
if (map) {
if (Array.isArray(control)) {
control.forEach((i) => map.removeControl(i));
} else {
map.removeControl(control);
}
}
};
}, [map]);
return null;
};
export const processMapLayer = (layer) => {
if (layer?.hidden) {
return null;
}
switch (layer?.type) {
case 'openStreetMap':
return (
<MapLayer source={new OSM()} />
);
case 'mapBoxVector':
if (!layer?.apiKey || layer?.styleUrl?.length < 1) {
return null;
}
return (
<MapLayer
key={layer?.id ?? Math.random()}
type='mapboxVector'
styleUrl={layer?.styleUrl}
accessToken={layer?.apiKey}
/>
);
case 'mapBoxRaster':
if (!layer?.apiKey || layer?.styleUrl?.length < 1) {
return null;
}
return (
<MapLayer
key={layer?.id ?? Math.random()}
source={new XYZ({
url: layer?.styleUrl,
})}
/>
);
case 'mapTilerVector':
if (!layer?.apiKey || layer?.styleUrl?.length < 1) {
return null;
}
return (
<MapLayer
key={layer?.id ?? Math.random()}
type='vectorTile'
source={new VectorTileSource({
format: new MVT(),
url: layer?.styleUrl,
})}
/>
);
case 'vectorJson':
if (!layer?.apiKey || layer?.styleUrl?.length < 1) {
return null;
}
return (
<MapLayer
key={layer?.id ?? Math.random()}
type='vectorJson'
styleUrl={layer?.styleUrl}
accessToken={layer?.apiKey}
/>
);
case 'mapTilerRasterXyz':
if (!layer?.apiKey || layer?.styleUrl?.length < 1) {
return null;
}
return (
<MapLayer
key={layer?.id ?? Math.random()}
source={new XYZ({
url: layer?.styleUrl,
})}
/>
);
case 'mapTilerRasterJson':
if (!layer?.apiKey) {
return null;
}
return (
<MapLayer
key={layer?.id ?? Math.random()}
source={new TileJSON({
url: layer?.styleUrl,
})}
/>
);
case 'geoJson':
if (!layer?.geoJsonUrl) {
return null;
}
return (
<MapLayer
key={layer?.id ?? Math.random()}
type='vector'
source={new VectorSource({
format: new GeoJSON(),
url: layer?.geoJsonUrl,
})}
// Stylize GeoJSON features based on type.
style={(feature, resolution) => {
const name = feature.getGeometry().getType();
if (name === 'Point') {
return new Style({
image: new Icon({
src: manifest.resources.markerIcon,
scale: 2 / Math.pow(resolution, 1 / 4),
displacement: [0, 15 / Math.pow(resolution, 1 / 4)],
})
});
}
return new Style({
fill: new Fill({
color: 'rgb(58 102 168 / 0.25)',
}),
stroke: new Stroke({
color: '#3A66A8',
lineJoin: 'round',
lineCap: 'round',
width: 2.5,
}),
});
}}
/>
);
}
return null;
};