@vizzuality/layer-manager-plugin-mapboxgl
Version:
## Requirements Install the next peer dependencies: ```sh # with Yarn yarn add deck.gl@7.3.6 luma.gl@7.3.2
348 lines • 13.5 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable no-underscore-dangle */
const raster_layer_mapbox_gl_1 = __importDefault(require("./raster-layer-mapbox-gl"));
const vector_layer_mapbox_gl_1 = __importDefault(require("./vector-layer-mapbox-gl"));
const geojson_layer_mapbox_gl_1 = __importDefault(require("./geojson-layer-mapbox-gl"));
const video_layer_mapbox_gl_1 = __importDefault(require("./video-layer-mapbox-gl"));
const deck_layer_mapbox_gl_1 = __importDefault(require("./deck-layer-mapbox-gl"));
class PluginMapboxGL {
constructor(map, options = {}) {
this.type = {
raster: raster_layer_mapbox_gl_1.default,
vector: vector_layer_mapbox_gl_1.default,
geojson: geojson_layer_mapbox_gl_1.default,
video: video_layer_mapbox_gl_1.default,
deck: deck_layer_mapbox_gl_1.default,
};
this.map = map;
this.options = options;
// You can change map styles and all the layers will be repositioned
this.map.on('style.load', () => {
const layers = this.getLayers();
layers.forEach((layer) => {
this.add(layer);
});
});
}
setOptions(options = {}) {
this.options = options;
}
/**
* Add a layer
* @param {Object} layerModel
*/
add(layerModel) {
const { images, mapLayer, opacity, visibility, } = layerModel;
const allLayers = this.getLayers();
if (Array.isArray(images)) {
images.forEach(({ id, src, options }) => {
if (!this.map.hasImage(id)) {
const img = new Image();
img.src = src;
img.onload = () => {
this.map.addImage(id, img, options);
};
}
});
}
// add source if it has one
if (this.map
&& mapLayer
&& mapLayer.source
&& mapLayer.id
&& !this.map.getSource(mapLayer.id)) {
this.map.addSource(mapLayer.id, mapLayer.source);
}
// add layers
if (mapLayer && mapLayer.layers) {
mapLayer.layers.forEach((l) => {
const { metadata = {} } = l;
const nextLayerId = metadata.position === 'top' ? null : this.getNextLayerId(layerModel);
if (this.map && !this.map.getLayer(l.id)) {
this.map.addLayer(l, nextLayerId);
}
allLayers.forEach(() => {
this.setZIndex();
});
});
this.setOpacity(layerModel, opacity);
this.setVisibility(layerModel, visibility);
}
}
/**
* Remove a layer
* @param {Object} layerModel
*/
remove(layerModel = {}) {
const { mapLayer } = layerModel;
if (mapLayer && mapLayer.layers && this.map && this.map.style) {
mapLayer.layers.forEach((l) => {
if (this.map.getLayer(l.id)) {
this.map.removeLayer(l.id);
}
});
}
if (mapLayer && !!this.map && this.map.getSource(mapLayer.id)) {
this.map.removeSource(mapLayer.id);
}
}
getMap() {
return this.map;
}
/**
* Get method by type
* @param {String} type
*/
getLayerByType(type) {
return this.type[type];
}
/**
* Get all mapbox layers
*/
getLayersOnMap() {
return this.map.getStyle().layers;
}
getLayer(layerModel) {
const { mapLayer } = layerModel;
if (this.map) {
return this.map.getSource(mapLayer.id);
}
return null;
}
/**
* Get all layers passed to layer manager
*/
getLayers() {
const { getLayers } = this.options;
const layers = getLayers();
return layers.sort((a, b) => {
if (Object.prototype.hasOwnProperty.call(a, 'decodeFunction')
&& !Object.prototype.hasOwnProperty.call(b, 'decodeFunction'))
return -1;
return 1;
});
}
/**
* Get the layer above the given z-index
* @param {Object} layerModel
*/
getNextLayerId(layerModel) {
const { zIndex } = layerModel;
const allLayers = this.getLayers();
const layersOnMap = this.getLayersOnMap();
// find the top layer for placing data layers below
const customLayer = layersOnMap
&& layersOnMap.length
&& layersOnMap.find((l) => l.id.includes('custom-layers')
|| l.id.includes('label')
|| l.id.includes('place')
|| l.id.includes('poi'));
// make sure layers are sorted by zIndex
const sortedLayers = allLayers.sort((a, b) => {
if (a.zIndex > b.zIndex)
return 1;
return -1;
});
// get the layer with zIndex greater than current layer from all layers
const nextLayer = sortedLayers.find((l) => l.zIndex > zIndex
&& (!l.mapLayer
|| !l.mapLayer.layers
|| !l.mapLayer.layers[0]
|| !l.mapLayer.layers[0].metadata
|| !l.mapLayer.layers[0].metadata.position));
// if no layer above it then use the custom layer
if (!nextLayer || (!!nextLayer && !nextLayer.mapLayer)) {
return customLayer.id;
}
// get the first layer of the next layer's array
const nextLayerMapLayers = nextLayer.mapLayer.layers;
const nextLayerMapLayer = nextLayerMapLayers[0];
// Filter layers with custom metadata position
const { id } = nextLayerMapLayer;
// if it has a layer above it, check if that layer has been added to the map and get its id
const isNextLayerOnMap = !!layersOnMap.find((l) => id === l.id);
// if next layer is on map return the id, else return the custom layer to add below
return isNextLayerOnMap ? id : customLayer.id;
}
/**
* A namespace to set z-index
* @param {Object} layerModel
*/
setZIndex() {
const allLayers = this.getLayers();
const layersOnMap = this.getLayersOnMap();
// set zIndex for all layers currently on map
layersOnMap.forEach((l) => {
const { id, metadata = {} } = l;
const layerModel = allLayers.find((ly) => id.includes(ly.id));
if (layerModel) {
const nextLayerId = metadata.position === 'top' ? null : this.getNextLayerId(layerModel);
this.map.moveLayer(id, nextLayerId);
}
});
// set for all decode layers that don't exist inside mapStyle()
const deckLayers = allLayers.filter((l) => l.type === 'deck');
if (deckLayers && this.map) {
deckLayers.forEach((layerModel) => {
const { mapLayer } = layerModel;
if (mapLayer) {
const { layers } = mapLayer;
const [parentLayer, ...childLayers] = layers;
const parentLayerOnMap = layersOnMap.find((ly) => ly.id === parentLayer.id);
childLayers.forEach((childLayer) => {
const childLayerOnMap = this.map.getLayer(childLayer.id);
if (parentLayerOnMap && childLayerOnMap) {
this.map.moveLayer(childLayer.id, parentLayer.id);
}
});
}
});
}
return true;
}
/**
* Given a desired value to give to a paint property of a layer, this function will return the
* correct value to set, by taking into account other factors such as the opacity of the layer
* @param {object} layerModel LayerModel instance
* @param {string} layerId ID of the layer that contains the property
* @param {string} propertyName Name of the property to set
* @param {any} desiredValue Value to give the property
*/
// eslint-disable-next-line class-methods-use-this
computePaintPropertyValue(layerModel, layerId, propertyName, desiredValue) {
const { opacity, render: { layers = [] }, mapLayer, } = layerModel;
const isOpacityProperty = /-opacity$/.test(propertyName);
// The opacity of the layer isn't relevant for this property
if (!isOpacityProperty) {
return desiredValue;
}
const { paint = {} } = layers.find((layer, index) => {
if (layer.id === undefined || layer.id === null) {
return `${mapLayer.id}-${layer.type}-${index}` === layerId;
}
return layer.id === layerId;
}) || {};
const currentValue = paint[propertyName];
let res = 0.99 * opacity;
if (currentValue !== undefined && currentValue !== null) {
if (typeof currentValue === 'number') {
res = currentValue * opacity * 0.99;
}
else if (Array.isArray(currentValue)) {
res = currentValue.map((element) => (typeof element === 'number' ? element * opacity * 0.99 : element));
}
}
return res;
}
/**
* A namespace to set opacity
* @param {Object} layerModel
* @param {Number} opacity
*/
setOpacity(layerModel, opacity) {
const PAINT_STYLE_NAMES = {
symbol: ['icon', 'text'],
circle: ['circle', 'circle-stroke'],
};
const { type, mapLayer } = layerModel;
if (mapLayer.layers && type !== 'deck') {
mapLayer.layers.forEach((l) => {
// Select the style to change depending on the type of layer
const paintStyleNames = PAINT_STYLE_NAMES[l.type] || [l.type];
// Loop each style name and check if there is an opacity in the original layer
paintStyleNames.forEach((name) => {
const propertyName = `${name}-opacity`;
const propertyValue = this.computePaintPropertyValue(layerModel, l.id, propertyName, opacity);
this.map.setPaintProperty(l.id, propertyName, propertyValue);
});
});
}
if (type === 'deck') {
const layer = mapLayer.layers[1];
if (layer && typeof layer.setProps === 'function') {
layer.setProps({ opacity });
}
}
}
/**
* A namespace to hide or show a selected layer
* @param {Object} layerModel
* @param {Boolean} visibility
*/
setVisibility(layerModel, visibility) {
const { mapLayer } = layerModel;
if (mapLayer && mapLayer.layers && this.map && this.map.style) {
mapLayer.layers.forEach((l) => {
this.map.setLayoutProperty(l.id, 'visibility', visibility ? 'visible' : 'none');
});
}
}
setSource(layerModel) {
const { id, source } = layerModel;
const { type, data } = source;
if (this.map && type === 'geojson' && !!data && typeof data !== 'string') {
const src = this.map.getSource(id);
src.setData(data);
}
return this;
}
setRender(layerModel) {
var _a;
const { mapLayer, render } = layerModel;
const { layers: renderLayers = [] } = render;
if (!mapLayer || !renderLayers.length) {
return this;
}
try {
(_a = mapLayer.layers) === null || _a === void 0 ? void 0 : _a.forEach((layer, i) => {
const { id } = layer;
// take the style for each layer or use the first one for all of them
const rl = renderLayers[i] || renderLayers[0] || {};
const { paint = {}, layout = {}, filter = null } = rl;
this.map.setFilter(id, filter);
Object.keys(paint).forEach((p) => {
this.map.setPaintProperty(id, p, this.computePaintPropertyValue(layerModel, id, p, paint[p]));
});
Object.keys(layout).forEach((l) => {
this.map.setLayoutProperty(id, l, layout[l]);
});
});
}
catch (error) {
console.error(error);
}
return this;
}
setParams() {
return this;
}
setSQLParams() {
return this;
}
setDeck(layerModel) {
var _a;
const { mapLayer, deck } = layerModel;
(_a = mapLayer === null || mapLayer === void 0 ? void 0 : mapLayer.layers) === null || _a === void 0 ? void 0 : _a.forEach((layer, i) => {
if (layer && layer.setProps && typeof layer.setProps === 'function') {
const dl = deck[i - 1];
if (dl) {
const { props: deckProps } = dl;
layer.setProps(deckProps);
}
else {
console.warn('No deck layer found for layer', layer);
}
}
});
return this;
}
unmount() {
this.map = null;
}
}
exports.default = PluginMapboxGL;
//# sourceMappingURL=index.js.map