UNPKG

kepler.gl

Version:

kepler.gl is a webgl based application to visualize large scale location data in the browser

202 lines (177 loc) 6.38 kB
// Copyright (c) 2021 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import memoize from 'lodash.memoize'; import clondDeep from 'lodash.clonedeep'; import {DEFAULT_LAYER_GROUPS, DEFAULT_MAPBOX_API_URL} from 'constants/default-settings'; const mapUrlRg = /^mapbox:\/\/styles\/[-a-z0-9]{2,256}\/[-a-z0-9]{2,256}/; const httpRg = /^(?=(http:|https:))/; export function getDefaultLayerGroupVisibility({layerGroups = []}) { return layerGroups.reduce( (accu, layer) => ({ ...accu, [layer.slug]: layer.defaultVisibility }), {} ); } const resolver = ({id, mapStyle, visibleLayerGroups = {}}) => `${id}:${Object.keys(visibleLayerGroups) .filter(d => visibleLayerGroups[d]) .sort() .join('-')}`; /** * Edit preset map style to keep only visible layers * * @param {Object} mapStyle - preset map style * @param {Object} visibleLayerGroups - visible layers of top map * @returns {Object} top map style */ export const editTopMapStyle = memoize(({id, mapStyle, visibleLayerGroups}) => { const visibleFilters = (mapStyle.layerGroups || []) .filter(lg => visibleLayerGroups[lg.slug]) .map(lg => lg.filter); // if top map // keep only visible layers const filteredLayers = mapStyle.style.layers.filter(layer => visibleFilters.some(match => match(layer)) ); return { ...mapStyle.style, layers: filteredLayers }; }, resolver); /** * Edit preset map style to filter out invisible layers * * @param {Object} mapStyle - preset map style * @param {Object} visibleLayerGroups - visible layers of bottom map * @returns {Object} bottom map style */ export const editBottomMapStyle = memoize(({id, mapStyle, visibleLayerGroups}) => { const invisibleFilters = (mapStyle.layerGroups || []) .filter(lg => !visibleLayerGroups[lg.slug]) .map(lg => lg.filter); // if bottom map // filter out invisible layers const filteredLayers = mapStyle.style.layers.filter(layer => invisibleFilters.every(match => !match(layer)) ); return { ...mapStyle.style, layers: filteredLayers }; }, resolver); // valid style url // mapbox://styles/uberdata/cjfyl03kp1tul2smf5v2tbdd4 // lowercase letters, numbers and dashes only. export function isValidStyleUrl(url) { return typeof url === 'string' && Boolean(url.match(mapUrlRg) || url.match(httpRg)); } export function getStyleDownloadUrl(styleUrl, accessToken, mapboxApiUrl) { if (styleUrl.startsWith('http')) { return styleUrl; } // mapbox://styles/jckr/cjhcl0lxv13di2rpfoytdbdyj if (styleUrl.startsWith('mapbox://styles')) { const styleId = styleUrl.replace('mapbox://styles/', ''); // https://api.mapbox.com/styles/v1/heshan0131/cjg1bfumo1cwm2rlrjxkinfgw?pluginName=Keplergl&access_token=<token> return `${mapboxApiUrl || DEFAULT_MAPBOX_API_URL}/styles/v1/${styleId}?pluginName=Keplergl&access_token=${accessToken}`; } // style url not recognized return null; } /** * Generate static map image from style Url to be used as icon * @param {Object} param * @param {string} param.styleUrl * @param {string} param.mapboxApiAccessToken * @param {string} param.mapboxApiUrl * @param {Object} param.mapState * @param {number} param.mapW * @param {number} param.mapH */ export function getStyleImageIcon({ styleUrl, mapboxApiAccessToken, mapboxApiUrl = DEFAULT_MAPBOX_API_URL, mapState = { longitude: -122.3391, latitude: 37.7922, zoom: 9 }, mapW = 400, mapH = 300 }) { const styleId = styleUrl.replace('mapbox://styles/', ''); return ( `${mapboxApiUrl}/styles/v1/${styleId}/static/` + `${mapState.longitude},${mapState.latitude},${mapState.zoom},0,0/` + `${mapW}x${mapH}` + `?access_token=${mapboxApiAccessToken}&logo=false&attribution=false` ); } export function scaleMapStyleByResolution(mapboxStyle, scale) { if (scale !== 1 && mapboxStyle) { const labelLayerGroup = DEFAULT_LAYER_GROUPS.find(lg => lg.slug === 'label'); // @ts-ignore const {filter: labelLayerFilter} = labelLayerGroup; const zoomOffset = Math.log2(scale); const copyStyle = clondDeep(mapboxStyle); (copyStyle.layers || []).forEach(d => { // edit minzoom and maxzoom if (d.maxzoom) { d.maxzoom = Math.max(d.maxzoom + zoomOffset, 1); } if (d.minzoom) { d.minzoom = Math.max(d.minzoom + zoomOffset, 1); } // edit text size if (labelLayerFilter(d)) { if (d.layout && d.layout['text-size'] && Array.isArray(d.layout['text-size'].stops)) { d.layout['text-size'].stops.forEach(stop => { // zoom stop[0] = Math.max(stop[0] + zoomOffset, 1); // size stop[1] *= scale; }); } } }); return copyStyle; } return mapboxStyle; } /** * When switch to a new style, try to keep current layer group visibility * by merging default and current * @param {Object} defaultLayerGroup * @param {Object} currentLayerGroup * @return {Object} mergedLayerGroups */ export function mergeLayerGroupVisibility(defaultLayerGroup, currentLayerGroup) { return Object.keys(defaultLayerGroup).reduce( (accu, key) => ({ ...accu, ...(currentLayerGroup.hasOwnProperty(key) ? {[key]: currentLayerGroup[key]} : {}) }), defaultLayerGroup ); }