@@ -9,6 +9,7 @@ import {GeoJsonLayer, PathLayer} from '@deck.gl/layers/typed';
99import { MVTSource , MVTTileSource } from '@loaders.gl/mvt' ;
1010import { PMTilesSource , PMTilesTileSource } from '@loaders.gl/pmtiles' ;
1111import GL from '@luma.gl/constants' ;
12+ import { ClipExtension } from '@deck.gl/extensions/typed' ;
1213
1314import { notNullorUndefined } from '@kepler.gl/common-utils' ;
1415import {
@@ -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