react-bkoi-gl
Version:
React components for Barikoi GL JS
387 lines • 11.1 kB
JavaScript
import { transformToViewState, applyViewStateToTransform } from "../utils/transform.js";
import { normalizeStyle } from "../utils/style-utils.js";
import { deepEqual } from "../utils/deep-equal.js";
const DEFAULT_STYLE = {
version: 8,
sources: {},
layers: []
};
const pointerEvents = {
mousedown: "onMouseDown",
mouseup: "onMouseUp",
mouseover: "onMouseOver",
mousemove: "onMouseMove",
click: "onClick",
dblclick: "onDblClick",
mouseenter: "onMouseEnter",
mouseleave: "onMouseLeave",
mouseout: "onMouseOut",
contextmenu: "onContextMenu",
touchstart: "onTouchStart",
touchend: "onTouchEnd",
touchmove: "onTouchMove",
touchcancel: "onTouchCancel"
};
const cameraEvents = {
movestart: "onMoveStart",
move: "onMove",
moveend: "onMoveEnd",
dragstart: "onDragStart",
drag: "onDrag",
dragend: "onDragEnd",
zoomstart: "onZoomStart",
zoom: "onZoom",
zoomend: "onZoomEnd",
rotatestart: "onRotateStart",
rotate: "onRotate",
rotateend: "onRotateEnd",
pitchstart: "onPitchStart",
pitch: "onPitch",
pitchend: "onPitchEnd"
};
const otherEvents = {
wheel: "onWheel",
boxzoomstart: "onBoxZoomStart",
boxzoomend: "onBoxZoomEnd",
boxzoomcancel: "onBoxZoomCancel",
resize: "onResize",
load: "onLoad",
render: "onRender",
idle: "onIdle",
remove: "onRemove",
data: "onData",
styledata: "onStyleData",
sourcedata: "onSourceData",
error: "onError"
};
const settingNames = ["minZoom", "maxZoom", "minPitch", "maxPitch", "maxBounds", "projection", "renderWorldCopies"];
const handlerNames = ["scrollZoom", "boxZoom", "dragRotate", "dragPan", "keyboard", "doubleClickZoom", "touchZoomRotate", "touchPitch"];
export default class Maplibre {
_map = null;
_internalUpdate = false;
_hoveredFeatures = null;
_propsedCameraUpdate = null;
_styleComponents = {};
static savedMaps = [];
constructor(MapClass, props, container) {
this._MapClass = MapClass;
this.props = props;
this._initialize(container);
}
get map() {
return this._map;
}
setProps(props) {
const oldProps = this.props;
this.props = props;
const settingsChanged = this._updateSettings(props, oldProps);
const sizeChanged = this._updateSize(props);
const viewStateChanged = this._updateViewState(props);
this._updateStyle(props, oldProps);
this._updateStyleComponents(props);
this._updateHandlers(props, oldProps);
if (settingsChanged || sizeChanged || viewStateChanged && !this._map.isMoving()) {
this.redraw();
}
}
static reuse(props, container) {
const that = Maplibre.savedMaps.pop();
if (!that) {
return null;
}
const map = that.map;
const oldContainer = map.getContainer();
container.className = oldContainer.className;
while (oldContainer.childNodes.length > 0) {
container.appendChild(oldContainer.childNodes[0]);
}
map._container = container;
const resizeObserver = map._resizeObserver;
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver.observe(container);
}
that.setProps({
...props,
styleDiffing: false
});
map.resize();
const {
initialViewState
} = props;
if (initialViewState) {
if (initialViewState.bounds) {
map.fitBounds(initialViewState.bounds, {
...initialViewState.fitBoundsOptions,
duration: 0
});
} else {
that._updateViewState(initialViewState);
}
}
if (map.isStyleLoaded()) {
map.fire("load");
} else {
map.once("style.load", () => map.fire("load"));
}
map._update();
return that;
}
_initialize(container) {
const {
props
} = this;
const {
mapStyle = DEFAULT_STYLE
} = props;
const mapOptions = {
...props,
...props.initialViewState,
container,
style: normalizeStyle(mapStyle)
};
const viewState = mapOptions.initialViewState || mapOptions.viewState || mapOptions;
Object.assign(mapOptions, {
center: [viewState.longitude || 0, viewState.latitude || 0],
zoom: viewState.zoom || 0,
pitch: viewState.pitch || 0,
bearing: viewState.bearing || 0
});
if (props.gl) {
const getContext = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = () => {
HTMLCanvasElement.prototype.getContext = getContext;
return props.gl;
};
}
const map = new this._MapClass(mapOptions);
if (viewState.padding) {
map.setPadding(viewState.padding);
}
if (props.cursor) {
map.getCanvas().style.cursor = props.cursor;
}
map.transformCameraUpdate = this._onCameraUpdate;
map.on("style.load", () => {
this._styleComponents = {
light: map.getLight(),
sky: map.getSky(),
projection: map.getProjection?.(),
terrain: map.getTerrain()
};
this._updateStyleComponents(this.props);
});
map.on("sourcedata", () => {
this._updateStyleComponents(this.props);
});
for (const eventName in pointerEvents) {
map.on(eventName, this._onPointerEvent);
}
for (const eventName in cameraEvents) {
map.on(eventName, this._onCameraEvent);
}
for (const eventName in otherEvents) {
map.on(eventName, this._onEvent);
}
this._map = map;
}
recycle() {
const container = this.map.getContainer();
const children = container.querySelector("[mapboxgl-children]");
children?.remove();
Maplibre.savedMaps.push(this);
}
destroy() {
this._map.remove();
}
redraw() {
const map = this._map;
if (map.style) {
if (map._frame) {
map._frame.cancel();
map._frame = null;
}
map._render();
}
}
_updateSize(nextProps) {
const {
viewState
} = nextProps;
if (viewState) {
const map = this._map;
if (viewState.width !== map.transform.width || viewState.height !== map.transform.height) {
map.resize();
return true;
}
}
return false;
}
_updateViewState(nextProps) {
const map = this._map;
const tr = map.transform;
const isMoving = map.isMoving();
if (!isMoving) {
const changes = applyViewStateToTransform(tr, nextProps);
if (Object.keys(changes).length > 0) {
this._internalUpdate = true;
map.jumpTo(changes);
this._internalUpdate = false;
return true;
}
}
return false;
}
_updateSettings(nextProps, currProps) {
const map = this._map;
let changed = false;
for (const propName of settingNames) {
if (propName in nextProps && !deepEqual(nextProps[propName], currProps[propName])) {
changed = true;
const setter = map[`set${propName[0].toUpperCase()}${propName.slice(1)}`];
setter?.call(map, nextProps[propName]);
}
}
return changed;
}
_updateStyle(nextProps, currProps) {
if (nextProps.cursor !== currProps.cursor) {
this._map.getCanvas().style.cursor = nextProps.cursor || "";
}
if (nextProps.mapStyle !== currProps.mapStyle) {
const {
mapStyle = DEFAULT_STYLE,
styleDiffing = true
} = nextProps;
const options = {
diff: styleDiffing
};
if ("localIdeographFontFamily" in nextProps) {
options.localIdeographFontFamily = nextProps.localIdeographFontFamily;
}
this._map.setStyle(normalizeStyle(mapStyle), options);
}
}
_updateStyleComponents(_ref) {
let {
light,
projection,
sky,
terrain
} = _ref;
const map = this._map;
const currProps = this._styleComponents;
if (map.style._loaded) {
if (light && !deepEqual(light, currProps.light)) {
currProps.light = light;
map.setLight(light);
}
if (projection && !deepEqual(projection, currProps.projection) && projection !== currProps.projection?.type) {
currProps.projection = typeof projection === "string" ? {
type: projection
} : projection;
map.setProjection?.(currProps.projection);
}
if (sky && !deepEqual(sky, currProps.sky)) {
currProps.sky = sky;
map.setSky(sky);
}
if (terrain !== undefined && !deepEqual(terrain, currProps.terrain)) {
if (!terrain || map.getSource(terrain.source)) {
currProps.terrain = terrain;
map.setTerrain(terrain);
}
}
}
}
_updateHandlers(nextProps, currProps) {
const map = this._map;
for (const propName of handlerNames) {
const newValue = nextProps[propName] ?? true;
const oldValue = currProps[propName] ?? true;
if (!deepEqual(newValue, oldValue)) {
if (newValue) {
map[propName].enable(newValue);
} else {
map[propName].disable();
}
}
}
}
_onEvent = e => {
const cb = this.props[otherEvents[e.type]];
if (cb) {
cb(e);
} else if (e.type === "error") {
console.error(e.error);
}
};
_onCameraEvent = e => {
if (this._internalUpdate) {
return;
}
e.viewState = this._propsedCameraUpdate || transformToViewState(this._map.transform);
const cb = this.props[cameraEvents[e.type]];
if (cb) {
cb(e);
}
};
_onCameraUpdate = tr => {
if (this._internalUpdate) {
return tr;
}
this._propsedCameraUpdate = transformToViewState(tr);
return applyViewStateToTransform(tr, this.props);
};
_queryRenderedFeatures(point) {
const map = this._map;
const {
interactiveLayerIds = []
} = this.props;
try {
return map.queryRenderedFeatures(point, {
layers: interactiveLayerIds.filter(map.getLayer.bind(map))
});
} catch {
return [];
}
}
_updateHover(e) {
const {
props
} = this;
const shouldTrackHoveredFeatures = props.interactiveLayerIds && (props.onMouseMove || props.onMouseEnter || props.onMouseLeave);
if (shouldTrackHoveredFeatures) {
const eventType = e.type;
const wasHovering = this._hoveredFeatures?.length > 0;
const features = this._queryRenderedFeatures(e.point);
const isHovering = features.length > 0;
if (!isHovering && wasHovering) {
e.type = "mouseleave";
this._onPointerEvent(e);
}
this._hoveredFeatures = features;
if (isHovering && !wasHovering) {
e.type = "mouseenter";
this._onPointerEvent(e);
}
e.type = eventType;
} else {
this._hoveredFeatures = null;
}
}
_onPointerEvent = e => {
if (e.type === "mousemove" || e.type === "mouseout") {
this._updateHover(e);
}
const cb = this.props[pointerEvents[e.type]];
if (cb) {
if (this.props.interactiveLayerIds && e.type !== "mouseover" && e.type !== "mouseout") {
e.features = this._hoveredFeatures || this._queryRenderedFeatures(e.point);
}
cb(e);
delete e.features;
}
};
}
//# sourceMappingURL=maplibre.js.map