Skip to content

Commit

Permalink
Merge branch 'master' into chr/fix-widget-events
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisgervang authored May 21, 2024
2 parents c604274 + eebcdd4 commit be7b990
Show file tree
Hide file tree
Showing 22 changed files with 892 additions and 84 deletions.
19 changes: 16 additions & 3 deletions docs/api-reference/carto/basemap.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,34 @@ To use pre-bundled scripts:
<script src="https://unpkg.com/@deck.gl/carto@^9.0.0/dist.min.js"></script>

<!-- or -->
<script src="https://unpkg.com/@deck.gl/core@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/core/@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/layers@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/mesh-layers@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/geo-layers@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/extensions@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/carto@^9.0.0/dist.min.js"></script>

<!-- basemap provider -->
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
```

```js
const deckgl = new deck.DeckGL({
const map = new maplibregl.Map({
container: 'map',
mapStyle: deck.carto.BASEMAP.POSITRON,
style: deck.carto.BASEMAP.POSITRON,
interactive: false
})
const deckgl = new deck.DeckGL({
canvas: 'deck-canvas',
initialViewState: {
latitude: 0,
longitude: 0,
zoom: 1
},
onViewStateChange: ({viewState}) => {
const {longitude, latitude, ...rest} = viewState;
map.jumpTo({center: [longitude, latitude], ...rest});
}
controller: true
});
```
Expand Down
52 changes: 38 additions & 14 deletions docs/api-reference/carto/fetch-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,21 @@ fetchMap({cartoMapId}).then(map => new Deck(map));

### Integration with CARTO basemaps


```js
fetchMap({cartoMapId}).then(({initialViewState, mapStyle, layers}) => {
const deckgl = new deck.DeckGL({
container: 'container',
controller: true,
// (Optional) Include a basemap.
mapStyle: `https://basemaps.cartocdn.com/gl/${mapStyle.styleType}-gl-style/style.json`,
initialViewState,
layers
});
});
import { fetchMap } from '@deck.gl/carto';
import { MapboxOverlay } from '@deck.gl/mapbox';
import maplibregl from 'maplibre-gl';

fetchMap({ cartoMapId }).then(({ basemap, layers }) => {
const map = new maplibregl.Map({
container: '...',
...basemap?.props, // basemap.props contain all props required to setup basemap
interactive: true
})
const overlay = new MapboxOverlay({layers: result.layers});
map.addControl(overlay);
})
```

## Parameters
Expand Down Expand Up @@ -90,14 +94,34 @@ When the map was last updated.

The [view state](../../developer-guide/views.md#view-state).

#### `mapStyle` (string) {#mapstyle}

An identifier describing the [basemap](../../api-reference/carto/basemap.md#supported-basemaps) configured in CARTO Builder.

#### `layers` (Layer[]) {#layers}

A collection of deck.gl [layers](../core/layer.md).

#### `basemap` (object) {#basemap}

An object describing the [basemap](../../api-reference/carto/basemap.md#supported-basemaps) configured in CARTO Builder.

Properties:
* `type` **(string)** - type of basemap: `'maplibre'` or `'google-maps'`
* `props` **(string or object)** - props that should be passed to basemap implementation
* if `type` is `'maplibre'` then it contains
* `style` **(string or object)** - URL of basemap style or style object if custom basemap is configured
* `center` **([number, number])** - center of map as `[latitude, longitude]`
* `zoom` **(number)** - zoom level
* `pitch` **(number)**
* `bearing` **(number)**
* if `type` is `'google-maps'`, then it contains those props
* `mapTypeId` **(string)** - type id of map
* `mapId` **(string, optional)** - map id
* `center` **(object)** - center of map as `{lat: number; lng: number}`
* `zoom`: **(number)** - zoom level (note, it has +1 offset applied versus deck.gl zoom)
* `tilt`: **(number)** - tilt, same as `pitch` in deck.gl API
* `heading`: **(number)** - heading, same as `bearing` in deck.gl API
* `rawStyle` **(string or object)** - for `maplibre` basemaps, original `style` before applying layer filtering
* `visibleLayerGroups` **(object, optional)** - layer groups to be displayed in the basemap.
* `attribution` **(string, optional)** - custom attribution HTML for this basemap

#### `stopAutoRefresh` (Function) {#stopautorefresh}

A function to invoke to stop auto-refreshing. Only present if `autoRefresh` option was provided to `fetchMap`.
Expand Down
1 change: 1 addition & 0 deletions docs/api-reference/carto/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ The CARTO module contains a number of custom layers which can be used to visuali

- [H3TileLayer](./h3-tile-layer.md)
- [QuadbinTileLayer](./quadbin-tile-layer.md)
- [QuadbinHeatmapTileLayer](./quadbin-heatmap-tile-layer.md)
- [RasterTileLayer](./raster-tile-layer.md)
- [VectorTileLayer](./vector-tile-layer.md)

Expand Down
104 changes: 104 additions & 0 deletions docs/api-reference/carto/quadbin-heatmap-tile-layer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# QuadbinHeatmapTileLayer (Experimental)

`QuadbinHeatmapTileLayer` is a layer for visualizing tiled data indexed with a [Quadbin Spatial Index](https://docs.carto.com/data-and-analysis/analytics-toolbox-for-bigquery/key-concepts/spatial-indexes#quadbin) using a heatmap.

## Usage

```tsx
import DeckGL from '@deck.gl/react';
import {QuadbinHeatmapTileLayer, quadbinTableSource} from '@deck.gl/carto';

function App({viewState}) {
const data = quadbinTableSource({
accessToken: 'XXX',
connectionName: 'carto_dw',
tableName: 'carto-demo-data.demo_tables.quadbin'
});

const layer = new QuadbinHeatmapTileLayer({
data,
getWeight: d => d.properties.count
})

return <DeckGL viewState={viewState} layers={[layer]} />;
}
```

## Installation

To install the dependencies from NPM:

```bash
npm install deck.gl
# or
npm install @deck.gl/core @deck.gl/layers @deck.gl/carto
```

```js
import {QuadbinHeatmapTileLayer} from '@deck.gl/carto';
new QuadbinHeatmapTileLayer({});
```

To use pre-bundled scripts:

```html
<script src="https://unpkg.com/deck.gl@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/carto@^9.0.0/dist.min.js"></script>

<!-- or -->
<script src="https://unpkg.com/@deck.gl/core@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/layers@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/geo-layers@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/carto@^9.0.0/dist.min.js"></script>
```

```js
new deck.carto.QuadbinHeatmapTileLayer({});
```

## Properties

Inherits from all [Base Layer](../core/layer.md) and [CompositeLayer](../core/composite-layer.md) properties.

#### `data` (TilejsonResult) {#data}

Required. A valid `TilejsonResult` object.

Use one of the following [Data Sources](./data-sources.md) to fetch this from the CARTO API:

- [quadbinTableSource](./data-sources#quadbintablesource)
- [quadbinQuerySource](./data-sources#quadbinquerysource)
- [quadbinTilesetSource](./data-sources#quadbintilesetsource)

### Render Options

#### `radiusPixels` (number, optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#radiuspixels}

* Default: `20`

Radius of the heatmap blur in pixels, to which the weight of a cell is distributed.

#### `colorDomain` (number[2], optional) ![transition-enabled](https://img.shields.io/badge/transition-enabled-green.svg?style=flat-square") {#colordomain}

* Default: `[0, 1]`

Controls how weight values are mapped to the `colorRange`, as an array of two numbers [`minValue`, `maxValue`].

When `colorDomain` is specified, a pixel with `minValue` is assigned the first color in `colorRange`, a pixel with `maxValue` is assigned the last color in `colorRange`, and any value in between is interpolated. Pixels in the bottom 10% of the range defined by `colorDomain` are gradually faded out by reducing alpha, until 100% transparency at `minValue`. Pixels with weight more than `maxValue` are capped to the last color in `colorRange`.

#### `colorRange` (Color[], optional) {#colorrange}

* Default: [colorbrewer](http://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6) `6-class YlOrRd` <img src="https://deck.gl/images/colorbrewer_YlOrRd_6.png"/>

The color palette used in the heatmap, as an array of colors [color1, color2, ...]. Each color is in the format of `[r, g, b]`. Each channel is a number between 0-255.


### Data Accessors

#### `getWeight` ([Accessor&lt;number&gt;](../../developer-guide/using-layers.md#accessors), optional) {#getweight}

* Default: `1`

Method called to retrieve weight of each quadbin cell. By default each cell will use a weight of `1`.


1 change: 1 addition & 0 deletions docs/table-of-contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@
"api-reference/carto/data-sources",
"api-reference/carto/h3-tile-layer",
"api-reference/carto/quadbin-tile-layer",
"api-reference/carto/quadbin-heatmap-tile-layer",
"api-reference/carto/raster-tile-layer",
"api-reference/carto/vector-tile-layer",
"api-reference/carto/styles"
Expand Down
2 changes: 1 addition & 1 deletion modules/carto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"@types/d3-array": "^3.0.2",
"@types/d3-color": "^1.4.2",
"@types/d3-scale": "^3.0.0",
"cartocolor": "^4.0.2",
"cartocolor": "^5.0.0",
"d3-array": "^3.2.0",
"d3-color": "^3.1.0",
"d3-format": "^3.1.0",
Expand Down
100 changes: 100 additions & 0 deletions modules/carto/src/api/basemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {MapViewState} from '@deck.gl/core';
import {
GOOGLE_BASEMAPS,
CARTO_MAP_STYLES,
applyLayerGroupFilters,
fetchStyle,
getStyleUrl,
someLayerGroupsDisabled
} from '../basemap';
import {APIErrorContext, Basemap, KeplerMapConfig, MapLibreBasemapProps} from './types';

const CUSTOM_STYLE_ID_PREFIX = 'custom:';
const DEFAULT_CARTO_STYLE = 'positron';

function mapLibreViewpros(config: KeplerMapConfig): Omit<MapLibreBasemapProps, 'style'> {
const {longitude, latitude, ...rest} = config.mapState as MapViewState;
return {
center: [longitude, latitude],
...rest
};
}

/**
* Get basemap properties for Carto map.
*
* For maplibre-based basemaps it returns style or style URL that can be used with `maplibregl.Map` compatible component.
* * style url is returned for non-filtered standard Carto basemaps or if user used style URL directly in configuration
* * filtered style object returned for Carto basemaps with layer groups filtered
*
* For Google-maps base maps, it returns options that can be used with `google.maps.Map` constructor.
*/
export async function fetchBasemapProps({
config,
errorContext,

applyLayerFilters = true
}: {
config: KeplerMapConfig;

/** By default `fetchBasemapProps` applies layers filters to style. Set this to `false` to disable it. */
applyLayerFilters?: boolean;
errorContext?: APIErrorContext;
}): Promise<Basemap | null> {
const {mapStyle} = config;
const styleType = mapStyle.styleType || DEFAULT_CARTO_STYLE;
if (styleType.startsWith(CUSTOM_STYLE_ID_PREFIX)) {
const currentCustomStyle = config.customBaseMaps?.customStyle;
if (currentCustomStyle) {
return {
type: 'maplibre',
props: {
style: currentCustomStyle.style || currentCustomStyle.url,
...mapLibreViewpros(config)
},
attribution: currentCustomStyle.customAttribution
};
}
}

if (CARTO_MAP_STYLES.includes(styleType)) {
const {visibleLayerGroups} = mapStyle;
const styleUrl = getStyleUrl(styleType);
let style = styleUrl;
let rawStyle = styleUrl;
if (applyLayerFilters && visibleLayerGroups && someLayerGroupsDisabled(visibleLayerGroups)) {
rawStyle = await fetchStyle({styleUrl, errorContext});
style = applyLayerGroupFilters(rawStyle, visibleLayerGroups);
}
return {
type: 'maplibre',
props: {
style,
...mapLibreViewpros(config)
},
visibleLayerGroups,
rawStyle
};
}
const googleBasemapDef = GOOGLE_BASEMAPS[styleType];
if (googleBasemapDef) {
const {mapState} = config;
return {
type: 'google-maps',
props: {
...googleBasemapDef,
center: {lat: mapState.latitude, lng: mapState.longitude},
zoom: mapState.zoom + 1,
tilt: mapState.pitch,
heading: mapState.bearing
}
};
}
return {
type: 'maplibre',
props: {
style: getStyleUrl(DEFAULT_CARTO_STYLE),
...mapLibreViewpros(config)
}
};
}
26 changes: 20 additions & 6 deletions modules/carto/src/api/fetch-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import {
vectorTableSource,
vectorTilesetSource
} from '../sources/index';
import {parseMap} from './parse-map';
import {ParseMapResult, parseMap} from './parse-map';
import {requestWithParameters} from './request-with-parameters';
import {assert} from '../utils';
import type {APIErrorContext, Format, MapType, QueryParameters} from './types';
import type {APIErrorContext, Basemap, Format, MapType, QueryParameters} from './types';
import {fetchBasemapProps} from './basemap';

type Dataset = {
id: string;
Expand Down Expand Up @@ -213,6 +214,14 @@ export type FetchMapOptions = {
onNewData?: (map: any) => void;
};

export type FetchMapResult = ParseMapResult & {
/**
* Basemap properties.
*/
basemap: Basemap | null;
stopAutoRefresh?: () => void;
};

/* eslint-disable max-statements */
export async function fetchMap({
apiBaseUrl = DEFAULT_API_BASE_URL,
Expand All @@ -221,7 +230,7 @@ export async function fetchMap({
headers = {},
autoRefresh,
onNewData
}: FetchMapOptions) {
}: FetchMapOptions): Promise<FetchMapResult> {
assert(cartoMapId, 'Must define CARTO map id: fetchMap({cartoMapId: "XXXX-XXXX-XXXX"})');
assert(apiBaseUrl, 'Must define apiBaseUrl');

Expand Down Expand Up @@ -272,12 +281,17 @@ export async function fetchMap({
}
});

// Mutates map.datasets so that dataset.data contains data
await fillInMapDatasets(map, clientId, apiBaseUrl, headers);
const [basemap] = await Promise.all([
fetchBasemapProps({config: map.keplerMapConfig.config, errorContext}),

// Mutates map.datasets so that dataset.data contains data
fillInMapDatasets(map, clientId, apiBaseUrl, headers)
]);

// Mutates attributes in visualChannels to contain tile stats
await fillInTileStats(map, apiBaseUrl);
const out = {...parseMap(map), ...{stopAutoRefresh}};

const out = {...parseMap(map), basemap, ...{stopAutoRefresh}};

const textLayers = out.layers.filter(layer => {
const pointType = layer.props.pointType || '';
Expand Down
Loading

0 comments on commit be7b990

Please sign in to comment.