From ff202e824aac169189a3d17fbb196a714ec8b986 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 14:15:21 +0900 Subject: [PATCH 01/12] Track visible regions --- .../plugins/images/ImageOverlayPlugin.js | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/three/plugins/images/ImageOverlayPlugin.js b/src/three/plugins/images/ImageOverlayPlugin.js index caae9a92a..ad9feba95 100644 --- a/src/three/plugins/images/ImageOverlayPlugin.js +++ b/src/three/plugins/images/ImageOverlayPlugin.js @@ -312,6 +312,23 @@ export class ImageOverlayPlugin { } + setTileVisible( tile, visible ) { + + this.overlayInfo.forEach( ( { tileInfo }, overlay ) => { + + const info = tileInfo.get( tile ); + if ( ! info || ! info.range ) { + + return; + + } + + overlay.setRegionVisible( info.range, visible ); + + } ); + + } + calculateBytesUsed( tile ) { const { overlayInfo } = this; @@ -1306,6 +1323,7 @@ export class ImageOverlay { this._whenReady = null; this.isReady = false; this.isInitialized = false; + this._visibleRegionCounts = new Map(); } @@ -1383,6 +1401,43 @@ export class ImageOverlay { } + setRegionVisible( range, visible ) { + + const { _visibleRegionCounts } = this; + const key = range.join( '_' ); + let count = _visibleRegionCounts.get( key ) || 0; + if ( visible ) { + + count ++; + + } else { + + count --; + + } + + if ( count < 0 ) { + + throw new Error(); + + } else if ( count === 0 ) { + + _visibleRegionCounts.delete( key ); + + } else { + + _visibleRegionCounts.set( key, count ); + + } + + } + + isRegionVisible( range ) { + + return this._visibleRegionCounts.has( range.join( '_' ) ); + + } + } /** From 70d6daa39a81fc0d08018231b3f9de1193c2dd91 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 16:23:31 +0900 Subject: [PATCH 02/12] Fix tracking --- .../plugins/images/ImageOverlayPlugin.js | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/three/plugins/images/ImageOverlayPlugin.js b/src/three/plugins/images/ImageOverlayPlugin.js index ad9feba95..5c111ced2 100644 --- a/src/three/plugins/images/ImageOverlayPlugin.js +++ b/src/three/plugins/images/ImageOverlayPlugin.js @@ -87,6 +87,7 @@ export class ImageOverlayPlugin { this.processQueue = null; this._onUpdateAfter = null; this._onTileDownloadStart = null; + this._onTileVisibilityChange = null; this._virtualChildResetId = 0; this._bytesUsed = new WeakMap(); @@ -222,8 +223,22 @@ export class ImageOverlayPlugin { }; + this._onTileVisibilityChange = ( { tile, visible } ) => { + + this.overlayInfo.forEach( ( { tileInfo }, overlay ) => { + + const info = tileInfo.get( tile ); + if ( ! info || ! info.range ) return; + + overlay.setRegionVisible( info.range, visible ); + + } ); + + }; + tiles.addEventListener( 'update-after', this._onUpdateAfter ); tiles.addEventListener( 'tile-download-start', this._onTileDownloadStart ); + tiles.addEventListener( 'tile-visibility-change', this._onTileVisibilityChange ); this.overlays.forEach( overlay => { @@ -292,6 +307,7 @@ export class ImageOverlayPlugin { if ( range !== null ) { + overlay.setRegionVisible( range, false ); overlay.releaseTexture( range ); } @@ -312,22 +328,6 @@ export class ImageOverlayPlugin { } - setTileVisible( tile, visible ) { - - this.overlayInfo.forEach( ( { tileInfo }, overlay ) => { - - const info = tileInfo.get( tile ); - if ( ! info || ! info.range ) { - - return; - - } - - overlay.setRegionVisible( info.range, visible ); - - } ); - - } calculateBytesUsed( tile ) { @@ -420,6 +420,8 @@ export class ImageOverlayPlugin { } ); tiles.removeEventListener( 'update-after', this._onUpdateAfter ); + tiles.removeEventListener( 'tile-download-start', this._onTileDownloadStart ); + tiles.removeEventListener( 'tile-visibility-change', this._onTileVisibilityChange ); this.resetVirtualChildren( true ); @@ -1032,6 +1034,7 @@ export class ImageOverlayPlugin { info.range = range; overlay.lockTexture( range ); + overlay.setRegionVisible( range, true ); } @@ -1124,6 +1127,7 @@ export class ImageOverlayPlugin { info.range = range; overlay.lockTexture( range ); + overlay.setRegionVisible( range, true ); } From d9adb308419e12bfce0b97b3bd8d1661d1cd0e60 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 16:48:38 +0900 Subject: [PATCH 03/12] Adjust redraw logic --- .../plugins/images/ImageOverlayPlugin.js | 10 +++--- .../images/sources/GeoJSONImageSource.js | 14 ++++---- .../plugins/images/sources/MVTImageSource.js | 35 ++++++++++--------- .../images/sources/PMTilesImageSource.js | 4 +-- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/three/plugins/images/ImageOverlayPlugin.js b/src/three/plugins/images/ImageOverlayPlugin.js index 5c111ced2..47ff3501a 100644 --- a/src/three/plugins/images/ImageOverlayPlugin.js +++ b/src/three/plugins/images/ImageOverlayPlugin.js @@ -228,8 +228,6 @@ export class ImageOverlayPlugin { this.overlayInfo.forEach( ( { tileInfo }, overlay ) => { const info = tileInfo.get( tile ); - if ( ! info || ! info.range ) return; - overlay.setRegionVisible( info.range, visible ); } ); @@ -1034,7 +1032,6 @@ export class ImageOverlayPlugin { info.range = range; overlay.lockTexture( range ); - overlay.setRegionVisible( range, true ); } @@ -1127,7 +1124,12 @@ export class ImageOverlayPlugin { info.range = range; overlay.lockTexture( range ); - overlay.setRegionVisible( range, true ); + + } + + if ( tile.traversal.visible ) { + + overlay.setRegionVisible( info.range, true ); } diff --git a/src/three/plugins/images/sources/GeoJSONImageSource.js b/src/three/plugins/images/sources/GeoJSONImageSource.js index 020b99e90..fa21db65f 100644 --- a/src/three/plugins/images/sources/GeoJSONImageSource.js +++ b/src/three/plugins/images/sources/GeoJSONImageSource.js @@ -122,15 +122,17 @@ export class GeoJSONImageSource extends RegionImageSource { } - redraw() { + redraw( args ) { - this._updateCache( true ); - this.forEachItem( ( tex, args ) => { + const tex = this.get( ...args ); + if ( ! tex ) { - this._drawToCanvas( tex.image, args ); - tex.needsUpdate = true; + return; - } ); + } + + this._drawToCanvas( tex.image, args ); + tex.needsUpdate = true; } diff --git a/src/three/plugins/images/sources/MVTImageSource.js b/src/three/plugins/images/sources/MVTImageSource.js index dbd073d8a..3a92b1a10 100644 --- a/src/three/plugins/images/sources/MVTImageSource.js +++ b/src/three/plugins/images/sources/MVTImageSource.js @@ -290,33 +290,36 @@ export class MVTImageSource extends RegionImageSource { } - redraw() { + redraw( args ) { - this.forEachItem( ( tex, args ) => { + const [ minX, minY, maxX, maxY, level ] = args; + const tex = this.get( minX, minY, maxX, maxY, level ); + if ( ! tex ) { - const [ minX, minY, maxX, maxY, level ] = args; - const regionBounds = [ minX, minY, maxX, maxY ]; - const canvas = tex.image; - const ctx = canvas.getContext( '2d' ); - ctx.clearRect( 0, 0, canvas.width, canvas.height ); + return; - forEachTileInBounds( regionBounds, level, this._contentCache.tiling, ( tx, ty, tl ) => { + } - const vectorTile = this._contentCache.get( tx, ty, tl ); - if ( vectorTile ) { + const regionBounds = [ minX, minY, maxX, maxY ]; + const canvas = tex.image; + const ctx = canvas.getContext( '2d' ); + ctx.clearRect( 0, 0, canvas.width, canvas.height ); - const tileBounds = this._contentCache.tiling.getTileBounds( tx, ty, tl, true, false ); - this._canvasRenderer.setFrame( ctx, tileBounds, regionBounds ); - this._renderVectorTile( vectorTile ); + forEachTileInBounds( regionBounds, level, this._contentCache.tiling, ( tx, ty, tl ) => { - } + const vectorTile = this._contentCache.get( tx, ty, tl ); + if ( vectorTile ) { - } ); + const tileBounds = this._contentCache.tiling.getTileBounds( tx, ty, tl, true, false ); + this._canvasRenderer.setFrame( ctx, tileBounds, regionBounds ); + this._renderVectorTile( vectorTile ); - tex.needsUpdate = true; + } } ); + tex.needsUpdate = true; + } dispose() { diff --git a/src/three/plugins/images/sources/PMTilesImageSource.js b/src/three/plugins/images/sources/PMTilesImageSource.js index 3e17b8914..4a7238433 100644 --- a/src/three/plugins/images/sources/PMTilesImageSource.js +++ b/src/three/plugins/images/sources/PMTilesImageSource.js @@ -239,11 +239,11 @@ export class PMTilesImageSource extends RegionImageSource { } - redraw() { + redraw( ...args ) { if ( this._deferredSource instanceof MVTImageSource ) { - this._deferredSource.redraw(); + this._deferredSource.redraw( ...args ); } From 9bef1d9e3cb0d78da42691cb6a229e18889061a6 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 17:02:14 +0900 Subject: [PATCH 04/12] Only redraw visible tiles --- .../plugins/images/ImageOverlayPlugin.js | 27 +++++++++---------- src/three/plugins/images/MVTOverlay.js | 6 ++++- .../images/sources/GeoJSONImageSource.js | 2 +- .../plugins/images/sources/MVTImageSource.js | 2 +- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/three/plugins/images/ImageOverlayPlugin.js b/src/three/plugins/images/ImageOverlayPlugin.js index 47ff3501a..696bc83d4 100644 --- a/src/three/plugins/images/ImageOverlayPlugin.js +++ b/src/three/plugins/images/ImageOverlayPlugin.js @@ -1411,29 +1411,24 @@ export class ImageOverlay { const { _visibleRegionCounts } = this; const key = range.join( '_' ); - let count = _visibleRegionCounts.get( key ) || 0; - if ( visible ) { + let entry = _visibleRegionCounts.get( key ); + if ( ! entry ) { - count ++; - - } else { - - count --; + entry = { range: [ ...range ], count: 0 }; + _visibleRegionCounts.set( key, entry ); } - if ( count < 0 ) { + entry.count += visible ? 1 : - 1; + + if ( entry.count < 0 ) { throw new Error(); - } else if ( count === 0 ) { + } else if ( entry.count === 0 ) { _visibleRegionCounts.delete( key ); - } else { - - _visibleRegionCounts.set( key, count ); - } } @@ -1790,7 +1785,11 @@ export class GeoJSONOverlay extends ImageOverlay { redraw() { - this.imageSource.redraw(); + for ( const { range } of this._visibleRegionCounts.values() ) { + + this.imageSource.redraw( range ); + + } } diff --git a/src/three/plugins/images/MVTOverlay.js b/src/three/plugins/images/MVTOverlay.js index bdc53e2ac..83c127f97 100644 --- a/src/three/plugins/images/MVTOverlay.js +++ b/src/three/plugins/images/MVTOverlay.js @@ -142,7 +142,11 @@ export class MVTOverlay extends ImageOverlay { redraw() { - this.imageSource.redraw(); + for ( const { range } of this._visibleRegionCounts.values() ) { + + this.imageSource.redraw( ...range, this.calculateLevel( range ) ); + + } } diff --git a/src/three/plugins/images/sources/GeoJSONImageSource.js b/src/three/plugins/images/sources/GeoJSONImageSource.js index fa21db65f..e6d73feed 100644 --- a/src/three/plugins/images/sources/GeoJSONImageSource.js +++ b/src/three/plugins/images/sources/GeoJSONImageSource.js @@ -122,7 +122,7 @@ export class GeoJSONImageSource extends RegionImageSource { } - redraw( args ) { + redraw( ...args ) { const tex = this.get( ...args ); if ( ! tex ) { diff --git a/src/three/plugins/images/sources/MVTImageSource.js b/src/three/plugins/images/sources/MVTImageSource.js index 3a92b1a10..90bc47cee 100644 --- a/src/three/plugins/images/sources/MVTImageSource.js +++ b/src/three/plugins/images/sources/MVTImageSource.js @@ -290,7 +290,7 @@ export class MVTImageSource extends RegionImageSource { } - redraw( args ) { + redraw( ...args ) { const [ minX, minY, maxX, maxY, level ] = args; const tex = this.get( minX, minY, maxX, maxY, level ); From 03954ec551fc5c01a7afb7628147e31c7d9818d2 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 17:33:50 +0900 Subject: [PATCH 05/12] Add dirty tracking --- .../plugins/images/ImageOverlayPlugin.js | 53 +++++++++++++++---- src/three/plugins/images/MVTOverlay.js | 46 +++++++++++++++- .../images/sources/PMTilesImageSource.js | 7 +++ 3 files changed, 95 insertions(+), 11 deletions(-) diff --git a/src/three/plugins/images/ImageOverlayPlugin.js b/src/three/plugins/images/ImageOverlayPlugin.js index 696bc83d4..0a8115f48 100644 --- a/src/three/plugins/images/ImageOverlayPlugin.js +++ b/src/three/plugins/images/ImageOverlayPlugin.js @@ -305,7 +305,6 @@ export class ImageOverlayPlugin { if ( range !== null ) { - overlay.setRegionVisible( range, false ); overlay.releaseTexture( range ); } @@ -1433,12 +1432,6 @@ export class ImageOverlay { } - isRegionVisible( range ) { - - return this._visibleRegionCounts.has( range.join( '_' ) ); - - } - } /** @@ -1737,6 +1730,7 @@ export class GeoJSONOverlay extends ImageOverlay { super( options ); this.imageSource = new GeoJSONImageSource( options ); + this._dirtyRegions = new Map(); } @@ -1783,14 +1777,55 @@ export class GeoJSONOverlay extends ImageOverlay { } + setRegionVisible( range, visible ) { + + super.setRegionVisible( range, visible ); + + if ( visible ) { + + const { + imageSource, + _dirtyRegions, + } = this; + + const key = range.join( '_' ); + if ( _dirtyRegions.has( key ) ) { + + const args = _dirtyRegions.get( key ); + imageSource.redraw( ...args ); + _dirtyRegions.delete( key ); + + } + + } + + } + redraw() { - for ( const { range } of this._visibleRegionCounts.values() ) { + const { + imageSource, + _visibleRegionCounts, + _dirtyRegions, + } = this; + + for ( const { range } of _visibleRegionCounts.values() ) { - this.imageSource.redraw( range ); + imageSource.redraw( ...range ); } + imageSource.forEachItem( ( _, args ) => { + + const key = args.join( '_' ); + if ( ! _visibleRegionCounts.has( key ) ) { + + _dirtyRegions.set( key, args ); + + } + + } ); + } } diff --git a/src/three/plugins/images/MVTOverlay.js b/src/three/plugins/images/MVTOverlay.js index 83c127f97..e1c90c4c1 100644 --- a/src/three/plugins/images/MVTOverlay.js +++ b/src/three/plugins/images/MVTOverlay.js @@ -63,6 +63,7 @@ export class MVTOverlay extends ImageOverlay { super( options ); this.imageSource = options.imageSource ?? new MVTImageSource( options ); + this._dirtyRegions = new Map(); } @@ -140,14 +141,55 @@ export class MVTOverlay extends ImageOverlay { } + setRegionVisible( range, visible ) { + + super.setRegionVisible( range, visible ); + + if ( visible ) { + + const { + imageSource, + _dirtyRegions, + } = this; + + const key = range.join( '_' ); + if ( _dirtyRegions.has( key ) ) { + + const range = _dirtyRegions.get( key ); + imageSource.redraw( ...range, this.calculateLevel( range ) ); + _dirtyRegions.delete( key ); + + } + + } + + } + redraw() { - for ( const { range } of this._visibleRegionCounts.values() ) { + const { + imageSource, + _visibleRegionCounts, + _dirtyRegions, + } = this; + + for ( const { range } of _visibleRegionCounts.values() ) { - this.imageSource.redraw( ...range, this.calculateLevel( range ) ); + imageSource.redraw( ...range, this.calculateLevel( range ) ); } + imageSource.forEachItem( ( _, args ) => { + + const key = args.slice( 0, 4 ).join( '_' ); + if ( ! _visibleRegionCounts.has( key ) ) { + + _dirtyRegions.set( key, args ); + + } + + } ); + } } diff --git a/src/three/plugins/images/sources/PMTilesImageSource.js b/src/three/plugins/images/sources/PMTilesImageSource.js index 4a7238433..a2371ddcc 100644 --- a/src/three/plugins/images/sources/PMTilesImageSource.js +++ b/src/three/plugins/images/sources/PMTilesImageSource.js @@ -124,6 +124,7 @@ class PMTilesContentCache extends MVTContentCache { } +// TODO: this should probably be a form of proxy export class PMTilesImageSource extends RegionImageSource { get tiling() { @@ -249,6 +250,12 @@ export class PMTilesImageSource extends RegionImageSource { } + forEachItem( ...args ) { + + return this._deferredSource.forEachItem( ...args ); + + } + dispose() { super.dispose(); From 55d354e1d9707d7f2dfd7a0b30bdbcf49f7a9ca9 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 18:00:35 +0900 Subject: [PATCH 06/12] Progressive updates --- src/three/plugins/images/MVTOverlay.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/three/plugins/images/MVTOverlay.js b/src/three/plugins/images/MVTOverlay.js index e1c90c4c1..22feddc0b 100644 --- a/src/three/plugins/images/MVTOverlay.js +++ b/src/three/plugins/images/MVTOverlay.js @@ -2,6 +2,7 @@ import { ImageOverlay } from './ImageOverlayPlugin.js'; import { MVTImageSource } from './sources/MVTImageSource.js'; import { PMTilesImageSource } from './sources/PMTilesImageSource.js'; +import { PriorityQueue } from '3d-tiles-renderer/core'; /** * @callback MVTGetStyleCallback @@ -65,6 +66,10 @@ export class MVTOverlay extends ImageOverlay { this.imageSource = options.imageSource ?? new MVTImageSource( options ); this._dirtyRegions = new Map(); + this._redrawQueue = new PriorityQueue(); + this._redrawQueue.maxJobs = 4; + this._redrawQueue.priorityCallback = () => 0; + } _init() { @@ -150,14 +155,16 @@ export class MVTOverlay extends ImageOverlay { const { imageSource, _dirtyRegions, + _redrawQueue, } = this; const key = range.join( '_' ); if ( _dirtyRegions.has( key ) ) { - const range = _dirtyRegions.get( key ); - imageSource.redraw( ...range, this.calculateLevel( range ) ); + const args = _dirtyRegions.get( key ); _dirtyRegions.delete( key ); + _redrawQueue.remove( args ); + imageSource.redraw( ...args ); } @@ -169,6 +176,7 @@ export class MVTOverlay extends ImageOverlay { const { imageSource, + _redrawQueue, _visibleRegionCounts, _dirtyRegions, } = this; @@ -182,9 +190,14 @@ export class MVTOverlay extends ImageOverlay { imageSource.forEachItem( ( _, args ) => { const key = args.slice( 0, 4 ).join( '_' ); - if ( ! _visibleRegionCounts.has( key ) ) { + if ( ! _visibleRegionCounts.has( key ) && ! _dirtyRegions.has( key ) ) { + + _redrawQueue.add( args, async args => { + + _dirtyRegions.delete( key ); + imageSource.redraw( ...args ); - _dirtyRegions.set( key, args ); + } ); } From 0f6666618fa78ffadf310aa57d1e492ff4e0a67c Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 18:24:34 +0900 Subject: [PATCH 07/12] Add new priority queue function, flush --- src/core/renderer/utilities/PriorityQueue.js | 48 ++++++++++++++++ src/three/plugins/images/MVTOverlay.js | 1 + test/core/PriorityQueue.test.js | 58 ++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/src/core/renderer/utilities/PriorityQueue.js b/src/core/renderer/utilities/PriorityQueue.js index c802364e7..fd1328534 100644 --- a/src/core/renderer/utilities/PriorityQueue.js +++ b/src/core/renderer/utilities/PriorityQueue.js @@ -305,6 +305,54 @@ export class PriorityQueue { } + /** + * Immediately runs the callback for the given item, removing it from the queue. + * Does nothing if the item is not queued. + * @param {any} item + * @returns {Promise|any} + */ + flush( item ) { + + const { items, callbacks } = this; + const index = items.indexOf( item ); + if ( ! callbacks.has( item ) ) { + + return; + + } + + const { callback, resolve, reject } = callbacks.get( item ); + callbacks.delete( item ); + items.splice( index, 1 ); + + let result; + try { + + result = callback( item ); + + } catch ( err ) { + + reject( err ); + return; + + } + + if ( result instanceof Promise ) { + + result + .then( resolve ) + .catch( reject ); + + } else { + + resolve( result ); + + } + + return result; + + } + /** * Schedules a deferred call to `tryRunJobs` via `schedulingCallback`. */ diff --git a/src/three/plugins/images/MVTOverlay.js b/src/three/plugins/images/MVTOverlay.js index 22feddc0b..df59f4967 100644 --- a/src/three/plugins/images/MVTOverlay.js +++ b/src/three/plugins/images/MVTOverlay.js @@ -192,6 +192,7 @@ export class MVTOverlay extends ImageOverlay { const key = args.slice( 0, 4 ).join( '_' ); if ( ! _visibleRegionCounts.has( key ) && ! _dirtyRegions.has( key ) ) { + _dirtyRegions.set( key, args ); _redrawQueue.add( args, async args => { _dirtyRegions.delete( key ); diff --git a/test/core/PriorityQueue.test.js b/test/core/PriorityQueue.test.js index 5cbdefbbf..cc26b9c6d 100644 --- a/test/core/PriorityQueue.test.js +++ b/test/core/PriorityQueue.test.js @@ -276,4 +276,62 @@ describe( 'PriorityQueue', () => { } ); + it( 'should immediately run the callback for the flushed item.', () => { + + const queue = new PriorityQueue(); + queue.autoUpdate = false; + + const order = []; + const A = {}; + const B = {}; + const C = {}; + queue.add( A, () => order.push( 'A' ) ); + queue.add( B, () => order.push( 'B' ) ); + queue.add( C, () => order.push( 'C' ) ); + + queue.flush( B ); + + expect( order ).toEqual( [ 'B' ] ); + expect( queue.items ).toHaveLength( 2 ); + expect( queue.callbacks.size ).toEqual( 2 ); + expect( queue.items ).toHaveLength( queue.callbacks.size ); + + } ); + + it( 'should resolve the promise returned by add when flushed.', async () => { + + const queue = new PriorityQueue(); + queue.autoUpdate = false; + + const key = {}; + let resolved = false; + const promise = queue.add( key, () => 42 ).then( val => { + + expect( val ).toEqual( 42 ); + resolved = true; + + } ); + + queue.flush( key ); + + await promise; + + expect( resolved ).toEqual( true ); + + } ); + + it( 'should do nothing when flushing an item not in the queue.', () => { + + const queue = new PriorityQueue(); + queue.autoUpdate = false; + + const A = {}; + const B = {}; + queue.add( A, () => {} ); + + expect( () => queue.flush( B ) ).not.toThrow(); + expect( queue.items ).toHaveLength( 1 ); + + } ); + } ); From 4407a9dd317f17ef5a5b74bef5f8e344de55b8bb Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 18:28:57 +0900 Subject: [PATCH 08/12] Cleanup --- src/three/plugins/images/MVTOverlay.js | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/three/plugins/images/MVTOverlay.js b/src/three/plugins/images/MVTOverlay.js index df59f4967..7a9da7199 100644 --- a/src/three/plugins/images/MVTOverlay.js +++ b/src/three/plugins/images/MVTOverlay.js @@ -64,7 +64,6 @@ export class MVTOverlay extends ImageOverlay { super( options ); this.imageSource = options.imageSource ?? new MVTImageSource( options ); - this._dirtyRegions = new Map(); this._redrawQueue = new PriorityQueue(); this._redrawQueue.maxJobs = 4; @@ -152,19 +151,11 @@ export class MVTOverlay extends ImageOverlay { if ( visible ) { - const { - imageSource, - _dirtyRegions, - _redrawQueue, - } = this; - + const { _redrawQueue } = this; const key = range.join( '_' ); - if ( _dirtyRegions.has( key ) ) { + if ( _redrawQueue.has( key ) ) { - const args = _dirtyRegions.get( key ); - _dirtyRegions.delete( key ); - _redrawQueue.remove( args ); - imageSource.redraw( ...args ); + _redrawQueue.flush( key ); } @@ -178,7 +169,6 @@ export class MVTOverlay extends ImageOverlay { imageSource, _redrawQueue, _visibleRegionCounts, - _dirtyRegions, } = this; for ( const { range } of _visibleRegionCounts.values() ) { @@ -190,12 +180,10 @@ export class MVTOverlay extends ImageOverlay { imageSource.forEachItem( ( _, args ) => { const key = args.slice( 0, 4 ).join( '_' ); - if ( ! _visibleRegionCounts.has( key ) && ! _dirtyRegions.has( key ) ) { + if ( ! _visibleRegionCounts.has( key ) && ! _redrawQueue.has( key ) ) { - _dirtyRegions.set( key, args ); - _redrawQueue.add( args, async args => { + _redrawQueue.add( key, () => { - _dirtyRegions.delete( key ); imageSource.redraw( ...args ); } ); From c9385a0105a09fbf002e21a34e53ae7dd762bdca Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 18:33:23 +0900 Subject: [PATCH 09/12] Adjust GeoJSONOverlay redraw logic --- .../plugins/images/ImageOverlayPlugin.js | 27 ++++++++++--------- src/three/plugins/images/MVTOverlay.js | 4 +-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/three/plugins/images/ImageOverlayPlugin.js b/src/three/plugins/images/ImageOverlayPlugin.js index 0a8115f48..7353b7116 100644 --- a/src/three/plugins/images/ImageOverlayPlugin.js +++ b/src/three/plugins/images/ImageOverlayPlugin.js @@ -1730,7 +1730,10 @@ export class GeoJSONOverlay extends ImageOverlay { super( options ); this.imageSource = new GeoJSONImageSource( options ); - this._dirtyRegions = new Map(); + + this._redrawQueue = new PriorityQueue(); + this._redrawQueue.maxJobs = 4; + this._redrawQueue.priorityCallback = () => 0; } @@ -1783,17 +1786,11 @@ export class GeoJSONOverlay extends ImageOverlay { if ( visible ) { - const { - imageSource, - _dirtyRegions, - } = this; - + const { _redrawQueue } = this; const key = range.join( '_' ); - if ( _dirtyRegions.has( key ) ) { + if ( _redrawQueue.has( key ) ) { - const args = _dirtyRegions.get( key ); - imageSource.redraw( ...args ); - _dirtyRegions.delete( key ); + _redrawQueue.flush( key ); } @@ -1805,8 +1802,8 @@ export class GeoJSONOverlay extends ImageOverlay { const { imageSource, + _redrawQueue, _visibleRegionCounts, - _dirtyRegions, } = this; for ( const { range } of _visibleRegionCounts.values() ) { @@ -1818,9 +1815,13 @@ export class GeoJSONOverlay extends ImageOverlay { imageSource.forEachItem( ( _, args ) => { const key = args.join( '_' ); - if ( ! _visibleRegionCounts.has( key ) ) { + if ( ! _visibleRegionCounts.has( key ) && ! _redrawQueue.has( key ) ) { + + _redrawQueue.add( key, () => { - _dirtyRegions.set( key, args ); + imageSource.redraw( ...args ); + + } ); } diff --git a/src/three/plugins/images/MVTOverlay.js b/src/three/plugins/images/MVTOverlay.js index 7a9da7199..0ed7c4fcf 100644 --- a/src/three/plugins/images/MVTOverlay.js +++ b/src/three/plugins/images/MVTOverlay.js @@ -152,7 +152,7 @@ export class MVTOverlay extends ImageOverlay { if ( visible ) { const { _redrawQueue } = this; - const key = range.join( '_' ); + const key = range.join( '_' ) + '_' + this.calculateLevel( range ); if ( _redrawQueue.has( key ) ) { _redrawQueue.flush( key ); @@ -179,7 +179,7 @@ export class MVTOverlay extends ImageOverlay { imageSource.forEachItem( ( _, args ) => { - const key = args.slice( 0, 4 ).join( '_' ); + const key = args.join( '_' ); if ( ! _visibleRegionCounts.has( key ) && ! _redrawQueue.has( key ) ) { _redrawQueue.add( key, () => { From b5a8fd6ec31cf6e57bac4aa004303bc43de22713 Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 18:34:04 +0900 Subject: [PATCH 10/12] Update d.ts --- src/core/renderer/utilities/PriorityQueue.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/renderer/utilities/PriorityQueue.d.ts b/src/core/renderer/utilities/PriorityQueue.d.ts index 67c90f8cc..20f8951e4 100644 --- a/src/core/renderer/utilities/PriorityQueue.d.ts +++ b/src/core/renderer/utilities/PriorityQueue.d.ts @@ -9,6 +9,7 @@ export class PriorityQueue { get running(): boolean; sort() : void; + flush( item : any ) : any; has( item : any ) : boolean; add( item : any, callback : ( item : any ) => any ) : Promise< any >; remove( item : any ) : void; From 4022ef9e4d8acbe7bde9a6f59dc099711bac8d9b Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 18:43:37 +0900 Subject: [PATCH 11/12] Cleanup --- .../plugins/images/sources/MVTImageSource.js | 100 ++++++++---------- 1 file changed, 46 insertions(+), 54 deletions(-) diff --git a/src/three/plugins/images/sources/MVTImageSource.js b/src/three/plugins/images/sources/MVTImageSource.js index 90bc47cee..abd58e371 100644 --- a/src/three/plugins/images/sources/MVTImageSource.js +++ b/src/three/plugins/images/sources/MVTImageSource.js @@ -184,35 +184,24 @@ export class MVTImageSource extends RegionImageSource { async fetchItem( [ minX, minY, maxX, maxY, level ], _signal ) { - const { resolution } = this; + const { resolution, _contentCache } = this; const canvas = document.createElement( 'canvas' ); canvas.width = resolution; canvas.height = resolution; - const ctx = canvas.getContext( '2d' ); const regionBounds = [ minX, minY, maxX, maxY ]; - const { _contentCache, _canvasRenderer } = this; const promises = []; forEachTileInBounds( regionBounds, level, _contentCache.tiling, ( tx, ty, tl ) => { - promises.push( ( async () => { - - const vectorTile = await _contentCache.lock( tx, ty, tl ); - if ( vectorTile ) { - - const tileBounds = _contentCache.tiling.getTileBounds( tx, ty, tl, true, false ); - _canvasRenderer.setFrame( ctx, tileBounds, regionBounds ); - this._renderVectorTile( vectorTile ); - - } - - } )() ); + promises.push( _contentCache.lock( tx, ty, tl ) ); } ); await Promise.all( promises ); + this._drawToCanvas( canvas, regionBounds, level ); + const tex = new CanvasTexture( canvas ); tex.colorSpace = SRGBColorSpace; tex.generateMipmaps = false; @@ -235,6 +224,48 @@ export class MVTImageSource extends RegionImageSource { } + redraw( ...args ) { + + const [ minX, minY, maxX, maxY, level ] = args; + const tex = this.get( minX, minY, maxX, maxY, level ); + if ( ! tex ) { + + return; + + } + + this._drawToCanvas( tex.image, [ minX, minY, maxX, maxY ], level ); + tex.needsUpdate = true; + + } + + dispose() { + + super.dispose(); + this._contentCache.dispose(); + + } + + _drawToCanvas( canvas, regionBounds, level ) { + + const { _contentCache, _canvasRenderer } = this; + const ctx = canvas.getContext( '2d' ); + forEachTileInBounds( regionBounds, level, _contentCache.tiling, ( tx, ty, tl ) => { + + const tileBounds = _contentCache.tiling.getTileBounds( tx, ty, tl, true, false ); + _canvasRenderer.setFrame( ctx, tileBounds, regionBounds ); + + const vectorTile = _contentCache.get( tx, ty, tl ); + if ( vectorTile ) { + + this._renderVectorTile( vectorTile ); + + } + + } ); + + } + _renderVectorTile( vectorTile ) { const { _canvasRenderer, getStyle } = this; @@ -290,43 +321,4 @@ export class MVTImageSource extends RegionImageSource { } - redraw( ...args ) { - - const [ minX, minY, maxX, maxY, level ] = args; - const tex = this.get( minX, minY, maxX, maxY, level ); - if ( ! tex ) { - - return; - - } - - const regionBounds = [ minX, minY, maxX, maxY ]; - const canvas = tex.image; - const ctx = canvas.getContext( '2d' ); - ctx.clearRect( 0, 0, canvas.width, canvas.height ); - - forEachTileInBounds( regionBounds, level, this._contentCache.tiling, ( tx, ty, tl ) => { - - const vectorTile = this._contentCache.get( tx, ty, tl ); - if ( vectorTile ) { - - const tileBounds = this._contentCache.tiling.getTileBounds( tx, ty, tl, true, false ); - this._canvasRenderer.setFrame( ctx, tileBounds, regionBounds ); - this._renderVectorTile( vectorTile ); - - } - - } ); - - tex.needsUpdate = true; - - } - - dispose() { - - super.dispose(); - this._contentCache.dispose(); - - } - } From af9a24837e87bc9a8a49e9adbf708d3a8bc2a91d Mon Sep 17 00:00:00 2001 From: Garrett Johnson Date: Wed, 20 May 2026 18:46:06 +0900 Subject: [PATCH 12/12] Remove empty line --- src/three/plugins/images/ImageOverlayPlugin.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/three/plugins/images/ImageOverlayPlugin.js b/src/three/plugins/images/ImageOverlayPlugin.js index 7353b7116..f8e5ce95a 100644 --- a/src/three/plugins/images/ImageOverlayPlugin.js +++ b/src/three/plugins/images/ImageOverlayPlugin.js @@ -325,7 +325,6 @@ export class ImageOverlayPlugin { } - calculateBytesUsed( tile ) { const { overlayInfo } = this;