Skip to content

Commit

Permalink
[fix] fixes for vector-tile layer (#3013)
Browse files Browse the repository at this point in the history
Signed-off-by: Ihor Dykhta <[email protected]>
  • Loading branch information
igorDykhta authored Mar 6, 2025
1 parent b4b979d commit 7107e41
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 44 deletions.
52 changes: 27 additions & 25 deletions src/components/src/hooks/use-fetch-vector-tile-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
// SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project

import {useCallback, useEffect, useState} from 'react';
import {useEffect, useState} from 'react';

import {/* MVTSource,*/ TileJSON} from '@loaders.gl/mvt';
import {TileJSON} from '@loaders.gl/mvt';
import {PMTilesSource, PMTilesMetadata} from '@loaders.gl/pmtiles';

import {RemoteTileFormat} from '@kepler.gl/constants';
import {getMVTMetadata, VectorTileMetadata} from '@kepler.gl/table';
import {getMVTMetadata, VectorTileMetadata, getFieldsFromTile} from '@kepler.gl/table';

type FetchVectorTileMetadataProps = {
url: string | null;
metadataUrl: string | null;
tilesetUrl: string | null;
remoteTileFormat: RemoteTileFormat;
process?: (json: PMTilesMetadata | TileJSON) => VectorTileMetadata | Error | null;
};
Expand All @@ -36,39 +37,25 @@ type FetchVectorTileMetadataReturn = {
/** Hook to fetch and return mvt or pmtiles metadata. */
export default function useFetchVectorTileMetadata({
remoteTileFormat,
url,
tilesetUrl,
metadataUrl,
process = DEFAULT_PROCESS_FUNCTION
}: FetchVectorTileMetadataProps): FetchVectorTileMetadataReturn {
const [error, setError] = useState<Error | null>(null);
const [data, setData] = useState<VectorTileMetadata | null>(null);
const [loading, setLoading] = useState<boolean>(false);

const setProcessedData = useCallback(
(value: PMTilesMetadata | TileJSON | null) => {
if (!value) {
return setError(value);
}
const processedData = process(value);
if (processedData instanceof Error) {
setError(processedData);
} else {
setData(processedData);
}
},
[setError, setData, process]
);

useEffect(() => {
const getAndProcessMetadata = async () => {
setError(null);
setData(null);
if (url) {
if (metadataUrl) {
setLoading(true);

try {
let metadata: PMTilesMetadata | TileJSON | null = null;
if (remoteTileFormat === RemoteTileFormat.MVT) {
metadata = await getMVTMetadata(url);
metadata = await getMVTMetadata(metadataUrl);

// MVTSource returns messy partial metadata
// MVTSource.createDataSource('', {
Expand All @@ -77,15 +64,30 @@ export default function useFetchVectorTileMetadata({
// }
// })
} else {
const tileSource = PMTilesSource.createDataSource(url, {});
const tileSource = PMTilesSource.createDataSource(metadataUrl, {});
metadata = await tileSource.metadata;
}

// Since we switched to Source.createDataSource detailed response errors aren't available here...
if (!metadata) {
throw new Error('Failed to fetch metadata');
}
setProcessedData(metadata);

const processedMetadata = process(metadata);
if (processedMetadata instanceof Error) {
setError(processedMetadata);
} else {
setError(null);

await getFieldsFromTile({
remoteTileFormat,
tilesetUrl,
metadataUrl,
metadata: processedMetadata
});

setData(processedMetadata);
}
} catch (metadataError) {
setError(metadataError as any);
}
Expand All @@ -94,7 +96,7 @@ export default function useFetchVectorTileMetadata({
};

getAndProcessMetadata();
}, [url, remoteTileFormat, setProcessedData]);
}, [metadataUrl, tilesetUrl, remoteTileFormat, setError, setData, process]);

return {data, loading, error};
}
3 changes: 2 additions & 1 deletion src/components/src/modal-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ export default function ModalContainerFactory(
disableDataOperation: true
},
{
autoCreateLayers: true
autoCreateLayers: true,
centerMap: true
}
);
this._closeModal();
Expand Down
15 changes: 12 additions & 3 deletions src/components/src/modals/tilesets-modals/tileset-vector-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,13 @@ const TilesetVectorForm: React.FC<TilesetVectorFormProps> = ({setResponse}) => {
event.preventDefault();
const newTileUrl = event.target.value;
setTileUrl(newTileUrl);
const potentialMetadataUrl = isPMTilesUrl(newTileUrl) ? newTileUrl : getMetaUrl(newTileUrl);

const usePMTiles = isPMTilesUrl(newTileUrl);
const potentialMetadataUrl = usePMTiles ? newTileUrl : getMetaUrl(newTileUrl);
if (!metadataUrl && potentialMetadataUrl) {
// check if URL exists before setting it as the metadata URL
const resp = await fetch(potentialMetadataUrl);
// Note: The {method: HEAD} request often fails, likely due to individual storage settings.
const resp = usePMTiles ? ({ok: true} as Response) : await fetch(potentialMetadataUrl);
if (resp.ok) {
setInitialFetchError(null);
setMetadataUrl(potentialMetadataUrl);
Expand Down Expand Up @@ -127,11 +130,17 @@ const TilesetVectorForm: React.FC<TilesetVectorFormProps> = ({setResponse}) => {
loading,
error: metaError
} = useFetchVectorTileMetadata({
url: metadataUrl,
metadataUrl,
tilesetUrl: tileUrl,
remoteTileFormat: isPMTilesUrl(metadataUrl) ? RemoteTileFormat.PMTILES : RemoteTileFormat.MVT,
process
});

// reset initial fetch error if the metadata is available
if (metadata && initialFetchError) {
setInitialFetchError(null);
}

useEffect(() => {
if (tileName && tileUrl) {
const dataset = getDatasetAttributesFromVectorTile({
Expand Down
11 changes: 9 additions & 2 deletions src/layers/src/vector-tile/abstract-tile-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export type AbstractTileLayerVisConfigSettings = {
export type LayerData = {
minZoom?: number;
maxZoom?: number;
bounds?: number[];
getPointRadius?: () => number;
};

Expand Down Expand Up @@ -408,10 +409,16 @@ export default abstract class AbstractTileLayer<
});

const metadata = dataset?.metadata as LayerData | undefined;
const metadataToData = metadata
? {
minZoom: metadata.minZoom,
maxZoom: metadata.maxZoom,
bounds: metadata.bounds
}
: {};

return {
...(metadata ? {minZoom: metadata.minZoom} : {}),
...(metadata ? {maxZoom: metadata.maxZoom} : {}),
...metadataToData,
...accessors
};
}
Expand Down
32 changes: 31 additions & 1 deletion src/layers/src/vector-tile/vector-tile-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {FeatureCollection, Feature} from 'geojson';

import {Layer as DeckLayer} from '@deck.gl/core/typed';
import {_Tile2DHeader as Tile2DHeader} from '@deck.gl/geo-layers/typed';
import {GeoJsonLayer} from '@deck.gl/layers/typed';
import {GeoJsonLayer, PathLayer} from '@deck.gl/layers/typed';
import {MVTSource, MVTTileSource} from '@loaders.gl/mvt';
import {PMTilesSource, PMTilesTileSource} from '@loaders.gl/pmtiles';
import GL from '@luma.gl/constants';
Expand Down Expand Up @@ -175,6 +175,35 @@ export type VectorTileLayerVisConfigSettings = Merge<
}
>;

export function tileLayerBoundsLayer(id: string, props: {bounds?: number[]}): DeckLayer[] {
const {bounds} = props;
if (bounds?.length !== 4) return [];

const data = [
{
path: [
[bounds[0], bounds[1]],
[bounds[2], bounds[1]],
[bounds[2], bounds[3]],
[bounds[0], bounds[3]],
[bounds[0], bounds[1]]
]
}
];

const layer = new PathLayer({
id: `${id}-vector-tile-bounds`,
data,
getPath: d => d.path,
getColor: [128, 128, 128, 255],
getWidth: 1,
widthUnits: 'pixels',
pickable: false
});

return [layer];
}

export default class VectorTileLayer extends AbstractTileLayer<VectorTile, Feature[]> {
declare config: VectorTileLayerConfig;
declare visConfigSettings: VectorTileLayerVisConfigSettings;
Expand Down Expand Up @@ -625,6 +654,7 @@ export default class VectorTileLayer extends AbstractTileLayer<VectorTile, Featu
})
]
: [])
// ...tileLayerBoundsLayer(defaultLayerProps.id, data),
];

return layers;
Expand Down
18 changes: 14 additions & 4 deletions src/table/src/dataset-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {PMTilesSource, PMTilesMetadata} from '@loaders.gl/pmtiles';
import {/* MVTSource,*/ TileJSON} from '@loaders.gl/mvt';

import {getMVTMetadata} from './tileset/tileset-utils';
import {parseVectorMetadata} from './tileset/vector-tile-utils';
import {parseVectorMetadata, getFieldsFromTile} from './tileset/vector-tile-utils';

// apply a color for each dataset
// to use as label colors
Expand Down Expand Up @@ -137,12 +137,13 @@ async function refreshRemoteData(datasetInfo: CreateTableProps) {
return null;
}

const {remoteTileFormat, tilesetMetadataUrl} =
const {remoteTileFormat, tilesetMetadataUrl, tilesetDataUrl} =
(datasetInfo.opts.metadata as VectorTileDatasetMetadata) || {};

if (
!(remoteTileFormat === RemoteTileFormat.PMTILES || remoteTileFormat === RemoteTileFormat.MVT) ||
typeof tilesetMetadataUrl !== 'string'
typeof tilesetMetadataUrl !== 'string' ||
typeof tilesetDataUrl !== 'string'
) {
return null;
}
Expand All @@ -157,7 +158,16 @@ async function refreshRemoteData(datasetInfo: CreateTableProps) {
}

if (rawMetadata) {
return parseVectorMetadata(rawMetadata);
const metadata = parseVectorMetadata(rawMetadata);

await getFieldsFromTile({
remoteTileFormat,
tilesetUrl: tilesetDataUrl,
metadataUrl: tilesetMetadataUrl,
metadata
});

return metadata;
}
} catch (err) {
// ignore for now, and use old metadata?
Expand Down
Loading

0 comments on commit 7107e41

Please sign in to comment.