Skip to content

Commit 429c3cd

Browse files
committed
smart outline calculation for features that are part of multiple tiles
Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>
1 parent 18d4da7 commit 429c3cd

2 files changed

Lines changed: 101 additions & 1 deletion

File tree

src/layers/src/vector-tile/common-tile/tile-dataset.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ export default class TileDataset<T, I extends Iterable<any> = T extends Iterable
7676
this.tileSet = new IterableTileSet(tiles.map(getIterable), getRowCount);
7777
}
7878

79+
/**
80+
* Return current tiles
81+
*/
82+
getTiles(): readonly T[] {
83+
return this.tiles;
84+
}
85+
7986
/**
8087
* Get the min/max domain of a field
8188
*/

src/layers/src/vector-tile/vector-tile-layer.ts

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {GeoJsonLayer, PathLayer} from '@deck.gl/layers/typed';
99
import {MVTSource, MVTTileSource} from '@loaders.gl/mvt';
1010
import {PMTilesSource, PMTilesTileSource} from '@loaders.gl/pmtiles';
1111
import GL from '@luma.gl/constants';
12+
import {ClipExtension} from '@deck.gl/extensions/typed';
1213

1314
import {notNullorUndefined} from '@kepler.gl/common-utils';
1415
import {
@@ -566,6 +567,12 @@ export default class VectorTileLayer extends AbstractTileLayer<VectorTile, Featu
566567
: (hoveredObject as any).id;
567568
}
568569

570+
// Build per-tile clipped overlay to draw only the outer stroke of highlighted feature per tile
571+
const perTileOverlays = this._getPerTileOverlays(hoveredObject, {
572+
defaultLayerProps,
573+
visConfig
574+
});
575+
569576
const layers = [
570577
new CustomMVTLayer({
571578
...defaultLayerProps,
@@ -663,12 +670,98 @@ export default class VectorTileLayer extends AbstractTileLayer<VectorTile, Featu
663670
loadOptions: {
664671
mvt: getLoaderOptions().mvt
665672
}
666-
})
673+
}),
674+
// render hover layer for features with no unique id property and no highlighted feature id
675+
...(hoveredObject && !uniqueIdProperty && !highlightedFeatureId
676+
? [
677+
new GeoJsonLayer({
678+
// @ts-expect-error props not typed?
679+
...objectHovered.sourceLayer?.props,
680+
...(this.getDefaultHoverLayerProps() as any),
681+
visible: true,
682+
wrapLongitude: false,
683+
data: [hoveredObject],
684+
getLineColor: DEFAULT_HIGHLIGHT_STROKE_COLOR,
685+
getFillColor: DEFAULT_HIGHLIGHT_FILL_COLOR,
686+
getLineWidth: visConfig.strokeWidth + 1,
687+
lineWidthUnits: 'pixels',
688+
stroked: true,
689+
filled: true
690+
})
691+
]
692+
: []),
693+
...perTileOverlays
667694
// ...tileLayerBoundsLayer(defaultLayerProps.id, data),
668695
];
669696

670697
return layers;
671698
}
672699
return [];
673700
}
701+
702+
/**
703+
* Build per-tile clipped overlay to draw only the outer stroke of highlighted feature per tile
704+
* @param hoveredObject
705+
*/
706+
_getPerTileOverlays(
707+
hoveredObject: Feature,
708+
options: {defaultLayerProps: any; visConfig: any}
709+
): DeckLayer[] {
710+
let perTileOverlays: DeckLayer[] = [];
711+
if (hoveredObject) {
712+
try {
713+
const tiles = this.tileDataset?.getTiles?.() || [];
714+
// Derive hovered id from hoveredObject
715+
const hoveredPid = UUID_CANDIDATES.find(
716+
k => hoveredObject?.properties && k in hoveredObject.properties
717+
);
718+
const hoveredId = hoveredPid
719+
? String(hoveredObject?.properties?.[hoveredPid])
720+
: String((hoveredObject as any)?.id);
721+
722+
// Group matched fragments by tile id
723+
const byTile: Record<string, Feature[]> = {};
724+
for (const tile of tiles) {
725+
const content = (tile as any)?.content;
726+
const features = content?.shape === 'geojson-table' ? content.features : content;
727+
if (!Array.isArray(features)) continue;
728+
const tileId = (tile as any).id;
729+
for (const f of features) {
730+
const pid = UUID_CANDIDATES.find(k => f.properties && k in f.properties);
731+
const fid = pid ? f.properties?.[pid] : (f as any).id;
732+
if (fid !== undefined && String(fid) === hoveredId) {
733+
(byTile[tileId] = byTile[tileId] || []).push(f as Feature);
734+
}
735+
}
736+
}
737+
738+
perTileOverlays = Object.entries(byTile).map(([tileId, feats]) => {
739+
const tile = tiles.find((t: any) => String(t.id) === String(tileId));
740+
const bounds = tile?.boundingBox
741+
? [...tile.boundingBox[0], ...tile.boundingBox[1]]
742+
: undefined;
743+
return new GeoJsonLayer({
744+
...(this.getDefaultHoverLayerProps() as any),
745+
id: `${options.defaultLayerProps.id}-hover-outline-${tileId}`,
746+
visible: true,
747+
wrapLongitude: false,
748+
data: feats,
749+
getLineColor: DEFAULT_HIGHLIGHT_STROKE_COLOR,
750+
getFillColor: [0, 0, 0, 0],
751+
getLineWidth: options.visConfig.strokeWidth + 1,
752+
lineWidthUnits: 'pixels',
753+
lineJointRounded: true,
754+
lineCapRounded: true,
755+
stroked: true,
756+
filled: false,
757+
clipBounds: bounds,
758+
extensions: bounds ? [new ClipExtension()] : []
759+
});
760+
});
761+
} catch {
762+
perTileOverlays = [];
763+
}
764+
}
765+
return perTileOverlays;
766+
}
674767
}

0 commit comments

Comments
 (0)