@deck.gl/carto
Version:
CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.
260 lines • 11.1 kB
JavaScript
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { log } from '@deck.gl/core';
import { AGGREGATION, getLayer, getColorAccessor, getColorValueAccessor, getSizeAccessor, getTextAccessor, OPACITY_MAP, opacityToAlpha, getIconUrlAccessor, negateAccessor, getMaxMarkerSize } from "./layer-map.js";
import { assert } from "../utils.js";
export function parseMap(json) {
const { keplerMapConfig, datasets, token } = json;
assert(keplerMapConfig.version === 'v1', 'Only support Kepler v1');
const { mapState, mapStyle } = keplerMapConfig.config;
const { layers, layerBlending, interactionConfig } = keplerMapConfig.config.visState;
return {
id: json.id,
title: json.title,
description: json.description,
createdAt: json.createdAt,
updatedAt: json.updatedAt,
initialViewState: mapState,
/** @deprecated Use `basemap`. */
mapStyle,
token,
layers: layers.reverse().map(({ id, type, config, visualChannels }) => {
try {
const { dataId } = config;
const dataset = datasets.find(d => d.id === dataId);
assert(dataset, `No dataset matching dataId: ${dataId}`);
const { data } = dataset;
assert(data, `No data loaded for dataId: ${dataId}`);
const { Layer, propMap, defaultProps } = getLayer(type, config, dataset);
const styleProps = createStyleProps(config, propMap);
return new Layer({
id,
data,
...defaultProps,
...createInteractionProps(interactionConfig),
...styleProps,
...createChannelProps(id, type, config, visualChannels, data), // Must come after style
...createParametersProp(layerBlending, styleProps.parameters || {}), // Must come after style
...createLoadOptions(token)
});
}
catch (e) {
log.error(e.message)();
return undefined;
}
})
};
}
function createParametersProp(layerBlending, parameters) {
if (layerBlending === 'additive') {
parameters.blendColorSrcFactor = parameters.blendAlphaSrcFactor = 'src-alpha';
parameters.blendColorDstFactor = parameters.blendAlphaDstFactor = 'dst-alpha';
parameters.blendColorOperation = parameters.blendAlphaOperation = 'add';
}
else if (layerBlending === 'subtractive') {
parameters.blendColorSrcFactor = 'one';
parameters.blendColorDstFactor = 'one-minus-dst';
parameters.blendAlphaSrcFactor = 'src-alpha';
parameters.blendAlphaDstFactor = 'dst-alpha';
parameters.blendColorOperation = 'subtract';
parameters.blendAlphaOperation = 'add';
}
return Object.keys(parameters).length ? { parameters } : {};
}
function createInteractionProps(interactionConfig) {
const pickable = interactionConfig && interactionConfig.tooltip.enabled;
return {
autoHighlight: pickable,
pickable
};
}
function mapProps(source, target, mapping) {
for (const sourceKey in mapping) {
const sourceValue = source[sourceKey];
const targetKey = mapping[sourceKey];
if (sourceValue === undefined) {
// eslint-disable-next-line no-continue
continue;
}
if (typeof targetKey === 'string') {
target[targetKey] = sourceValue;
}
else if (typeof targetKey === 'function') {
const [key, value] = Object.entries(targetKey(sourceValue))[0];
target[key] = value;
}
else if (typeof targetKey === 'object') {
// Nested definition, recurse down one level (also handles arrays)
mapProps(sourceValue, target, targetKey);
}
}
}
function createStyleProps(config, mapping) {
const result = {};
mapProps(config, result, mapping);
// Kepler format sometimes omits strokeColor. TODO: remove once we can rely on
// `strokeColor` always being set when `stroke: true`.
if (result.stroked && !result.getLineColor) {
result.getLineColor = result.getFillColor;
}
for (const colorAccessor in OPACITY_MAP) {
if (Array.isArray(result[colorAccessor])) {
const color = [...result[colorAccessor]];
const opacityKey = OPACITY_MAP[colorAccessor];
const opacity = config.visConfig[opacityKey];
color[3] = opacityToAlpha(opacity);
result[colorAccessor] = color;
}
}
result.highlightColor = config.visConfig.enable3d ? [255, 255, 255, 60] : [252, 242, 26, 255];
return result;
}
/* eslint-disable complexity, max-statements */
function createChannelProps(id, type, config, visualChannels, data) {
const { colorField, colorScale, radiusField, radiusScale, sizeField, sizeScale, strokeColorField, strokeColorScale, weightField } = visualChannels;
let { heightField, heightScale } = visualChannels;
if (type === 'hexagonId') {
heightField = sizeField;
heightScale = sizeScale;
}
const { textLabel, visConfig } = config;
const result = {};
if (type === 'grid' || type === 'hexagon') {
result.colorScaleType = colorScale;
if (colorField) {
const { colorAggregation } = config.visConfig;
if (!AGGREGATION[colorAggregation]) {
result.getColorValue = getColorValueAccessor(colorField, colorAggregation, data);
}
else {
result.getColorWeight = d => d[colorField.name];
}
}
}
else if (colorField) {
const { colorAggregation: aggregation, colorRange: range } = visConfig;
result.getFillColor = getColorAccessor(colorField,
// @ts-ignore
colorScale, { aggregation, range }, visConfig.opacity, data);
}
if (type === 'point') {
const altitude = config.columns?.altitude;
if (altitude) {
result.dataTransform = data => {
data.features.forEach(({ geometry, properties }) => {
const { type, coordinates } = geometry;
if (type === 'Point') {
coordinates[2] = properties[altitude];
}
});
return data;
};
}
}
if (radiusField || sizeField) {
result.getPointRadius = getSizeAccessor(
// @ts-ignore
radiusField || sizeField,
// @ts-ignore
radiusScale || sizeScale, visConfig.sizeAggregation, visConfig.radiusRange || visConfig.sizeRange, data);
}
if (strokeColorField) {
const fallbackOpacity = type === 'point' ? visConfig.opacity : 1;
const opacity = visConfig.strokeOpacity !== undefined ? visConfig.strokeOpacity : fallbackOpacity;
const { strokeColorAggregation: aggregation, strokeColorRange: range } = visConfig;
result.getLineColor = getColorAccessor(strokeColorField,
// @ts-ignore
strokeColorScale,
// @ts-ignore
{ aggregation, range }, opacity, data);
}
if (heightField && visConfig.enable3d) {
result.getElevation = getSizeAccessor(heightField,
// @ts-ignore
heightScale, visConfig.heightAggregation, visConfig.heightRange || visConfig.sizeRange, data);
}
if (weightField) {
result.getWeight = getSizeAccessor(weightField, undefined, visConfig.weightAggregation, undefined, data);
}
if (visConfig.customMarkers) {
const maxIconSize = getMaxMarkerSize(visConfig, visualChannels);
const { getPointRadius, getFillColor } = result;
const { customMarkersUrl, customMarkersRange, filled: useMaskedIcons } = visConfig;
result.pointType = 'icon';
result.getIcon = getIconUrlAccessor(visualChannels.customMarkersField, customMarkersRange, { fallbackUrl: customMarkersUrl, maxIconSize, useMaskedIcons }, data);
result._subLayerProps = {
'points-icon': {
loadOptions: {
image: {
type: 'imagebitmap'
},
imagebitmap: {
resizeWidth: maxIconSize,
resizeHeight: maxIconSize,
resizeQuality: 'high'
}
}
}
};
if (getFillColor && useMaskedIcons) {
result.getIconColor = getFillColor;
}
if (getPointRadius) {
result.getIconSize = getPointRadius;
}
if (visualChannels.rotationField) {
result.getIconAngle = negateAccessor(getSizeAccessor(visualChannels.rotationField, undefined, null, undefined, data));
}
}
else if (type === 'point' || type === 'tileset') {
result.pointType = 'circle';
}
if (textLabel && textLabel.length && textLabel[0].field) {
const [mainLabel, secondaryLabel] = textLabel;
const collisionGroup = id;
({
alignment: result.getTextAlignmentBaseline,
anchor: result.getTextAnchor,
color: result.getTextColor,
outlineColor: result.textOutlineColor,
size: result.textSizeScale
} = mainLabel);
const { color: getSecondaryColor, field: secondaryField, outlineColor: secondaryOutlineColor, size: secondarySizeScale } = secondaryLabel || {};
result.getText = mainLabel.field && getTextAccessor(mainLabel.field, data);
const getSecondaryText = secondaryField && getTextAccessor(secondaryField, data);
result.pointType = `${result.pointType}+text`;
result.textCharacterSet = 'auto';
result.textFontFamily = 'Inter, sans';
result.textFontSettings = { sdf: true };
result.textFontWeight = 600;
result.textOutlineWidth = 3;
result._subLayerProps = {
...result._subLayerProps,
'points-text': {
// The following props are injected by default by VectorTileLayer:
// type: PointLabelLayer,
// extensions: [new CollisionFilterExtension()],
collisionEnabled: true,
collisionGroup,
// getPointRadius already has radiusScale baked in, so only pass one or the other
...(result.getPointRadius
? { getRadius: result.getPointRadius }
: { radiusScale: visConfig.radius }),
...(secondaryField && {
getSecondaryText,
getSecondaryColor,
secondarySizeScale,
secondaryOutlineColor
})
}
};
}
return result;
}
function createLoadOptions(accessToken) {
return {
loadOptions: { fetch: { headers: { Authorization: `Bearer ${accessToken}` } } }
};
}
//# sourceMappingURL=parse-map.js.map