Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CARTO: Support gzip compression in RasterLayer #9352

Merged
merged 9 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion modules/carto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"prepublishOnly": "npm run build-bundle && npm run build-bundle -- --env=dev"
},
"dependencies": {
"@carto/api-client": "^0.4.0",
"@carto/api-client": "^0.4.4",
"@loaders.gl/gis": "^4.2.0",
"@loaders.gl/loader-utils": "^4.2.0",
"@loaders.gl/mvt": "^4.2.0",
Expand Down
7 changes: 5 additions & 2 deletions modules/carto/src/layers/raster-tile-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default class RasterTileLayer<
const tileJSON = this.props.data as TilejsonResult;
if (!tileJSON) return null;

const {tiles: data, minzoom: minZoom, maxzoom: maxZoom} = tileJSON;
const {tiles: data, minzoom: minZoom, maxzoom: maxZoom, raster_metadata: metadata} = tileJSON;
const SubLayerClass = this.getSubLayerClass('tile', PostProcessTileLayer);
return new SubLayerClass(this.props, {
id: `raster-tile-layer-${this.props.id}`,
Expand All @@ -78,7 +78,10 @@ export default class RasterTileLayer<
renderSubLayers,
minZoom,
maxZoom,
loadOptions: this.getLoadOptions()
loadOptions: {
cartoRasterTile: {metadata},
...this.getLoadOptions()
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const CartoPropertiesTileLoader: LoaderWithParser = {
extensions: ['pbf'],
mimeTypes: ['application/vnd.carto-properties-tile'],
category: 'geometry',
worker: false,
worker: true,
parse: async (arrayBuffer, options) => parseCartoPropertiesTile(arrayBuffer, options),
parseSync: parseCartoPropertiesTile,
options: {}
Expand Down
11 changes: 9 additions & 2 deletions modules/carto/src/layers/schema/carto-raster-tile-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Copyright (c) vis.gl contributors

import {LoaderOptions, LoaderWithParser} from '@loaders.gl/loader-utils';
import type {RasterMetadata} from '@carto/api-client';

import {TileReader} from './carto-raster-tile';
import {parsePbf} from './tile-loader-utils';
Expand All @@ -14,12 +15,14 @@ const id = 'cartoRasterTile';

type CartoRasterTileLoaderOptions = LoaderOptions & {
cartoRasterTile?: {
metadata: RasterMetadata | null;
workerUrl: string;
};
};

const DEFAULT_OPTIONS: CartoRasterTileLoaderOptions = {
cartoRasterTile: {
metadata: null,
workerUrl: getWorkerUrl(id, VERSION)
}
};
Expand Down Expand Up @@ -52,8 +55,12 @@ function parseCartoRasterTile(
arrayBuffer: ArrayBuffer,
options?: CartoRasterTileLoaderOptions
): Raster | null {
if (!arrayBuffer) return null;
const {bands, blockSize} = parsePbf(arrayBuffer, TileReader);
const metadata = options?.cartoRasterTile?.metadata;
if (!arrayBuffer || !metadata) return null;
// @ts-expect-error Upstream type needs to be updated
TileReader.compression = metadata.compression;
const out = parsePbf(arrayBuffer, TileReader);
const {bands, blockSize} = out;

const numericProps = {};
for (let i = 0; i < bands.length; i++) {
Expand Down
4 changes: 3 additions & 1 deletion modules/carto/src/layers/schema/carto-raster-tile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ export class BandReader {
throw Error(`Invalid data type: ${obj.type}`);
}
obj.data = {};
readPackedTypedArray(TypedArray, pbf, obj.data);
const {compression} = TileReader;
readPackedTypedArray(TypedArray, pbf, obj.data, {compression});
}
}
}

export class TileReader {
public static compression: null | 'gzip';
static read(pbf, end) {
return pbf.readFields(TileReader._readField, {blockSize: 0, bands: []}, end);
}
Expand Down
19 changes: 17 additions & 2 deletions modules/carto/src/layers/schema/fast-pbf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,25 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {GZipCompression} from '@loaders.gl/compression';

type ReadPackedOptions = {
compression: null | 'gzip';
};

// Optimized (100X speed improvement) reading function for binary data
export function readPackedTypedArray(TypedArray, pbf, obj) {
export function readPackedTypedArray(TypedArray, pbf, obj, options?: ReadPackedOptions) {
const end = pbf.type === 2 ? pbf.readVarint() + pbf.pos : pbf.pos + 1;
obj.value = new TypedArray(pbf.buf.buffer.slice(pbf.pos, end));
const data = pbf.buf.buffer.slice(pbf.pos, end);

if (options?.compression === 'gzip') {
const compression = new GZipCompression();
const decompressedData = compression.decompressSync(data);
obj.value = new TypedArray(decompressedData);
} else {
obj.value = new TypedArray(data);
}

pbf.pos = end;
return obj.value;
}
7 changes: 6 additions & 1 deletion test/modules/carto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import './api/fetch-map.spec';
import './api/layer-map.spec';
import './api/parse-map.spec';
import './utils.spec';
import './layers/carto-vector-tile.spec';
import './layers/h3-tile-layer.spec';
import './layers/h3-tileset-2d.spec';
import './layers/raster.spec';
Expand All @@ -18,6 +17,12 @@ import './layers/quadbin-layer.spec';
import './layers/quadbin-tile-layer.spec';
import './layers/quadbin-tileset-2d.spec';
import './layers/vector-tile-layer.spec';
import './layers/schema/carto-properties-tile-loader.spec';
import './layers/schema/carto-raster-tile-loader.spec';
import './layers/schema/carto-raster-tile.spec';
import './layers/schema/carto-spatial-tile-loader.spec';
import './layers/schema/carto-vector-tile-loader.spec';
import './layers/schema/carto-vector-tile.spec';
import './style/carto-color-bins.spec';
import './style/carto-color-categories.spec';
import './style/carto-color-continuous.spec';
Expand Down
4 changes: 3 additions & 1 deletion test/modules/carto/layers/raster.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import binaryRasterTileData from '../data/binaryRasterTile.json';
const BINARY_RASTER_TILE = new Uint8Array(binaryRasterTileData).buffer;

test('Parse Carto Raster Tile', async t => {
const converted = CartoRasterTileLoader.parseSync(BINARY_RASTER_TILE, {});
const converted = CartoRasterTileLoader.parseSync(BINARY_RASTER_TILE, {
cartoRasterTile: {metadata: {}}
});
const {numericProps} = converted.cells;

const {band_1} = numericProps;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import test from 'tape-promise/tape';
import CartoPropertiesTileLoader from '@deck.gl/carto/layers/schema/carto-properties-tile-loader';
import type {LoaderWithParser} from '@loaders.gl/loader-utils';

test('CartoPropertiesTileLoader', t => {
const loader = CartoPropertiesTileLoader as LoaderWithParser;

t.ok(loader, 'CartoPropertiesTileLoader should be defined');
t.equals(loader.name, 'CARTO Properties Tile', 'Should have correct name');
t.equals(typeof loader.parse, 'function', 'Should have parse method');
t.equals(typeof loader.parseSync, 'function', 'Should have parseSync method');
t.equals(loader.worker, true, 'worker property should be true');
t.end();
});
39 changes: 39 additions & 0 deletions test/modules/carto/layers/schema/carto-raster-tile-loader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import test from 'tape-promise/tape';
import CartoRasterTileLoader from '@deck.gl/carto/layers/schema/carto-raster-tile-loader';
import type {LoaderWithParser} from '@loaders.gl/loader-utils';
import {BAND, COMPRESSED_BAND, TEST_DATA} from './carto-raster-tile.spec';

test('CartoRasterTileLoader', t => {
const loader = CartoRasterTileLoader as LoaderWithParser;

t.ok(loader, 'CartoRasterTileLoader should be defined');
t.equals(loader.name, 'CARTO Raster Tile', 'Should have correct name');
t.equals(typeof loader.parse, 'function', 'Should have parse method');
t.equals(typeof loader.parseSync, 'function', 'Should have parseSync method');
t.equals(CartoRasterTileLoader.worker, true, 'worker property should be true');

const result = loader.parseSync!(TEST_DATA, {cartoRasterTile: {metadata: {compression: null}}});
t.equals(result.blockSize, 256, 'Should return correct blockSize');
t.ok(result.cells, 'Should return cells');
t.ok(result.cells.numericProps, 'Should return numericProps');
t.deepEqual(
result.cells.numericProps.band1.value,
COMPRESSED_BAND,
'Should return compressed band'
);

// Repeat with compressed data
const result2 = loader.parseSync!(TEST_DATA, {
cartoRasterTile: {metadata: {compression: 'gzip'}}
});
t.equals(result2.blockSize, 256, 'Should return correct blockSize');
t.ok(result2.cells, 'Should return cells');
t.ok(result2.cells.numericProps, 'Should return numericProps');
t.deepEqual(result2.cells.numericProps.band1.value, BAND, 'Should return uncompressed band');

t.end();
});
55 changes: 55 additions & 0 deletions test/modules/carto/layers/schema/carto-raster-tile.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import test from 'tape-promise/tape';
import {TileReader} from '@deck.gl/carto/layers/schema/carto-raster-tile';
import Pbf from 'pbf';

// GZIP compressed data for [1, 2, 3, 4]
export const BAND = [1, 2, 3, 4];
export const COMPRESSED_BAND = [
31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 99, 100, 98, 102, 1, 0, 205, 251, 60, 182, 4, 0, 0, 0
];
const buffer = new Pbf();
buffer.writeVarintField(1, 256); // blockSize
buffer.writeMessage(2, (_, pbf) => {
// bands
pbf.writeStringField(1, 'band1');
pbf.writeStringField(2, 'uint8');
pbf.writeBytesField(3, new Uint8Array(COMPRESSED_BAND));
});
export const TEST_DATA = buffer.finish();

/**
* syntax = "proto3";
* package carto;
*
* message Band {
* string name = 1;
* string type = 2;
* bytes data = 3;
* }
*
* message Tile {
* uint32 blockSize = 1;
* repeated Band bands = 2;
* }
*/
test('TileReader', t => {
const tile = TileReader.read(new Pbf(TEST_DATA), TEST_DATA.byteLength);
t.equals(tile.blockSize, 256, 'Should read blockSize correctly');
t.equals(tile.bands.length, 1, 'Should have one band');
t.equals(tile.bands[0].name, 'band1', 'Band should have correct name');
t.deepEqual(tile.bands[0].data.value, COMPRESSED_BAND, 'Band should have compressed data');

// Repeat with compressed data
TileReader.compression = 'gzip';
const tile2 = TileReader.read(new Pbf(TEST_DATA), TEST_DATA.byteLength);
t.equals(tile.blockSize, 256, 'Should read blockSize correctly');
t.equals(tile.bands.length, 1, 'Should have one band');
t.equals(tile.bands[0].name, 'band1', 'Band should have correct name');
t.deepEqual(tile2.bands[0].data.value, BAND, 'Band should have decompressed data');

t.end();
});
18 changes: 18 additions & 0 deletions test/modules/carto/layers/schema/carto-spatial-tile-loader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import test from 'tape-promise/tape';
import CartoSpatialTileLoader from '@deck.gl/carto/layers/schema/carto-spatial-tile-loader';
import type {LoaderWithParser} from '@loaders.gl/loader-utils';

test('CartoSpatialTileLoader', t => {
const loader = CartoSpatialTileLoader as LoaderWithParser;

t.ok(loader, 'CartoSpatialTileLoader should be defined');
t.equals(loader.name, 'CARTO Spatial Tile', 'Should have correct name');
t.equals(typeof loader.parse, 'function', 'Should have parse method');
t.equals(typeof loader.parseSync, 'function', 'Should have parseSync method');
t.equals(loader.worker, true, 'worker property should be true');
t.end();
});
18 changes: 18 additions & 0 deletions test/modules/carto/layers/schema/carto-vector-tile-loader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import test from 'tape-promise/tape';
import CartoVectorTileLoader from '@deck.gl/carto/layers/schema/carto-vector-tile-loader';
import type {LoaderWithParser} from '@loaders.gl/loader-utils';

test('CartoVectorTileLoader', t => {
const loader = CartoVectorTileLoader as LoaderWithParser;

t.ok(loader, 'CartoVectorTileLoader should be defined');
t.equals(loader.name, 'CARTO Vector Tile', 'Should have correct name');
t.equals(typeof loader.parse, 'function', 'Should have parse method');
t.equals(typeof loader.parseSync, 'function', 'Should have parseSync method');
t.equals(loader.worker, true, 'worker property should be true');
t.end();
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import test from 'tape-promise/tape';
import CartoVectoTileLoader from '@deck.gl/carto/layers/schema/carto-vector-tile-loader';

// See test/modules/carto/responseToJson for details for creating test data
import binaryVectorTileData from '../data/binaryTilePolygon.json';
import binaryNoTrianglesTileData from '../data/binaryTilePolygonNoTri.json';
import binaryVectorTileData from '../../data/binaryTilePolygon.json';
import binaryNoTrianglesTileData from '../../data/binaryTilePolygonNoTri.json';
const BINARY_VECTOR_TILE = new Uint8Array(binaryVectorTileData).buffer;
const BINARY_VECTOR_TILE_NOTRI = new Uint8Array(binaryNoTrianglesTileData).buffer;

Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==

"@carto/api-client@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@carto/api-client/-/api-client-0.4.0.tgz#853469e70dcfa5ce14083f6ad9348911d08046fa"
integrity sha512-zFn4/cBBVNvpSqS9di72XuRUl5qrWHEW2KsuWK3fzR5Sng8jXoTFESf6poY0nyhxlAutkMNZIAhpHm+wdc7y/w==
"@carto/api-client@^0.4.4":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@carto/api-client/-/api-client-0.4.4.tgz#53e4c5886db642f2b79968e02a5efa07e49caa58"
integrity sha512-XeKYgmBIUIBHW+KiRylHvb5pY4VBaRst5ZaqS2q+BPBLY9Dtse3q8kPLcpEIYVEQoYcIz9Z5rPHOuGs4pPOYKg==
dependencies:
"@turf/bbox-clip" "^7.1.0"
"@turf/bbox-polygon" "^7.1.0"
Expand Down
Loading