UNPKG

@pirireis/react-carto-map-gl

Version:

396 lines (344 loc) 8.64 kB
import {PureComponent} from 'react'; import PropTypes from 'prop-types'; import carto from '@carto/carto-vl'; import MapContext from '../MapContext/MapContext'; import {isSourcemetadataEqual} from '../../utils/vizEquality'; import {isEqual} from 'lodash-es'; async function asyncForEach(array, callback) { for (let index = 0; index < array.length; index++) { await callback(array[index], index, array); } } class Source extends PureComponent { static propTypes = { /** * Source id. */ id: PropTypes.string.isRequired, /** * Source type. */ type: PropTypes.oneOf(['mapbox', 'carto-vl']).isRequired, /** * Source data type. */ dataType: PropTypes.oneOf(['geojson', 'vector', 'raster']).isRequired, /** * <em>Required if dataType of source is <b>vector</b>.</em> */ vectorTileUrl: PropTypes.string, /** * <em>Required if dataType of source is <b>raster</b>.</em> */ rasterTileUrl: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), /** * <em>Required if dataType of source is <b>raster</b>.</em> */ tileSize: PropTypes.number, /** * carto-vl MVT metadata. <br /> * <em>Required if source is a <b>carto-vl</b> source with dataType <b>vector</b>.</em> */ metadata: PropTypes.object, /** * Geojson feature collection. <br /> * <em>Required if source is a <b>geojson</b> source</em> */ data: PropTypes.object, /** * Callback function that will trigger when <b>carto-vl</b> source is created. <br /> * @param cartoSource created carto source */ onCartoSourceReady: PropTypes.func, /** * Mapbox geojson cluster. */ cluster: PropTypes.bool, /** * Mapbox geojson cluster max zoom. */ clusterMaxZoom: PropTypes.number, /** * Mapbox geojson cluster radius. */ clusterRadius: PropTypes.number, }; static defaultProps = { cluster: false, clusterMaxZoom: 14, clusterRadius: 50, }; state = {ready: true}; componentDidMount() { this.createSource(); } componentDidUpdate(prevProps, prevState, snapshot) { const { id, vectorTileUrl, rasterTileUrl, dataType, data, metadata, } = this.props; const { id: prevId, vectorTileUrl: prevVectorTileUrl, rasterTileUrl: prevRasterTileUrl, dataType: prevDataType, data: prevData, metadata: prevMetadata, } = prevProps; if ( id !== prevId || vectorTileUrl !== prevVectorTileUrl || !isEqual(rasterTileUrl, prevRasterTileUrl) || !isSourcemetadataEqual(metadata, prevMetadata) ) { this.removeSource().then(() => { this.createSource(); }); return; } if (dataType === 'geojson' && prevDataType === 'geojson') { this.updateGeoJSONSource(prevData, data); } } componentWillUnmount() { this.removeSource(); } static contextType = MapContext; removeSource = () => { return new Promise((resolve, reject) => { const {id, type} = this.props; const {compare, map, cartoLayers} = this.context; const source = this.context.sources[id].mapSource; if (type === 'carto-vl') { const layers = Object.keys(cartoLayers) .filter(key => cartoLayers[key].sourceId === id) .map(key => cartoLayers[key]); layers.forEach(l => { this.context.removeCartoLayer(l.id); l.remove(); }); source.free(); this.context.removeSource(id); resolve(); } else if (type === 'mapbox') { if (compare) { asyncForEach(['map', 'afterMap'], async target => { const map = this.context[target]; if (map.getSource(id)) { const {layers} = map.getStyle(); if (layers) { layers.forEach(layer => { if (layer.source === id) { map.removeLayer(layer.id); } }); } await this.context.removeSource(id); map.removeSource(id); } }); resolve(); } else { if (map.getSource(id)) { const {layers} = map.getStyle(); if (layers) { layers.forEach(layer => { if (layer.source === id) { map.removeLayer(layer.id); } }); } this.context.removeSource(id); map.removeSource(id); resolve(); } } } }); }; async updateGeoJSONSource(prevData, data) { if (!isEqual(data, prevData)) { const {compare, map, afterMap, cartoLayers} = this.context; const {id, type, dataType} = this.props; if (type === 'carto-vl') { const layers = Object.keys(cartoLayers) .filter(key => cartoLayers[key].sourceId === id) .map(key => cartoLayers[key]); const mapSource = new carto.source.GeoJSON(data); // await this.context.removeSource(id); this.context.addSource(id, { type, dataType, mapSource, }); layers.forEach(l => { l.update(mapSource); }); } else if (type === 'mapbox') { if (compare) { const source = map.getSource(id); const afterSource = afterMap.getSource(id); if (source !== undefined) { source.setData(data); } if (afterSource !== undefined) { afterSource.setData(data); } } else { const source = map.getSource(id); if (source !== undefined) { source.setData(data); } } } } } createSource() { const {dataType, type} = this.props; if (type === 'mapbox' && dataType === 'geojson') { this._createMapBoxGeoJSONSource(); } else if (type === 'mapbox' && dataType === 'vector') { this._createMapBoxVectorSource(); } else if (type === 'mapbox' && dataType === 'raster') { this._createMapBoxRasterSource(); } else if (type === 'carto-vl' && dataType === 'geojson') { this._createCartoGeoJSONSource(); } else if (type === 'carto-vl' && dataType === 'vector') { this._createCartoVectorSource(); } } _createMapBoxGeoJSONSource() { const { data, id, dataType, type, cluster, clusterMaxZoom, clusterRadius, } = this.props; const {compare, map, afterMap} = this.context; if (compare) { map.addSource(id, { type: 'geojson', data, cluster, clusterMaxZoom, clusterRadius, }); afterMap.addSource(id, { type: 'geojson', data, cluster, clusterMaxZoom, clusterRadius, }); } else { map.addSource(id, { type: 'geojson', data, cluster, clusterMaxZoom, clusterRadius, }); } const mapSource = map.getSource(id); this.context.addSource(id, { mapSource, dataType, type, }); } _createMapBoxVectorSource() { const {vectorTileUrl, id, dataType, type} = this.props; const {compare, map, afterMap} = this.context; if (compare) { const waiting = () => { if (!afterMap.isStyleLoaded()) { setTimeout(waiting, 200); } else { map.addSource(id, { type: 'vector', tiles: [vectorTileUrl], }); afterMap.addSource(id, { type: 'vector', tiles: [vectorTileUrl], }); const mapSource = map.getSource(id); this.context.addSource(id, { mapSource, dataType, type, }); } }; waiting(); } else { map.addSource(id, { type: 'vector', tiles: [vectorTileUrl], }); const mapSource = map.getSource(id); this.context.addSource(id, { mapSource, dataType, type, }); } } async _createMapBoxRasterSource() { const {rasterTileUrl, tileSize = 256, id, dataType, type} = this.props; const {compare, map} = this.context; if (compare) { await asyncForEach(['map', 'afterMap'], async target => { const map = this.context[target]; map.addSource(id, { type: 'raster', tiles: Array.isArray(rasterTileUrl) ? rasterTileUrl : [rasterTileUrl], tileSize: tileSize, }); }); } else { map.addSource(id, { type: 'raster', tiles: Array.isArray(rasterTileUrl) ? rasterTileUrl : [rasterTileUrl], tileSize: tileSize, }); } const mapSource = map.getSource(id); this.context.addSource(id, { mapSource, dataType, type, }); } _createCartoGeoJSONSource() { const {data, id, dataType, type} = this.props; const mapSource = new carto.source.GeoJSON(data); this.props.onCartoSourceReady && this.props.onCartoSourceReady(mapSource); this.context.addSource(id, { mapSource, dataType, type, }); } _createCartoVectorSource() { const {vectorTileUrl, metadata, id, dataType, type} = this.props; const mapSource = new carto.source.MVT([vectorTileUrl], metadata); this.props.onCartoSourceReady && this.props.onCartoSourceReady(mapSource); this.context.addSource(id, { mapSource, dataType, type, }); } render() { return null; } } export default Source;