@@ -16,6 +16,7 @@ namespace Alidade.Services;
1616public class EditBufferService : IDisposable
1717{
1818 private const double MinFetchZoom = 17.0 ;
19+ private const double EvictionMultiplier = 2.0 ;
1920 private readonly IMediator _mediator ;
2021 private readonly NsiService _nsi ;
2122 private readonly IndexedDBService _storage ;
@@ -96,6 +97,9 @@ private void OnMapStateChanged(object? sender, EventArgs e)
9697 _fetchCts . Cancel ( ) ;
9798 _fetchCts . Dispose ( ) ;
9899 _fetchCts = new CancellationTokenSource ( ) ;
100+
101+ EvictOutOfViewportData ( bounds ) ;
102+
99103 _ = _mediator . Publish ( new FetchBboxRequested . Notification ( bounds ) , _fetchCts . Token ) ;
100104
101105 if ( bounds . Zoom >= MinFetchZoom )
@@ -417,6 +421,151 @@ private async Task SetSourceAsync(string sourceId, IReadOnlyList<NetTopologySuit
417421 #endregion
418422
419423 #region Data fetch
424+ private void EvictOutOfViewportData ( MapBounds bounds )
425+ {
426+ EditBufferState state = _editState . State ;
427+
428+ double latSpan = bounds . North - bounds . South ;
429+ double lonSpan = bounds . East - bounds . West ;
430+ double latPad = latSpan * ( EvictionMultiplier - 1.0 ) / 2.0 ;
431+ double lonPad = lonSpan * ( EvictionMultiplier - 1.0 ) / 2.0 ;
432+ double keepSouth = bounds . South - latPad ;
433+ double keepNorth = bounds . North + latPad ;
434+ double keepWest = bounds . West - lonPad ;
435+ double keepEast = bounds . East + lonPad ;
436+
437+ // Pass 1: evict Fetched ways with no node inside keep-bounds.
438+ List < long > wayIdsToEvict = [ ] ;
439+ foreach ( KeyValuePair < long , OsmWay > kv in state . Ways )
440+ {
441+ if ( state . EditStates . GetValueOrDefault ( kv . Value . Ref ) != EditState . Fetched )
442+ {
443+ continue ;
444+ }
445+
446+ bool hasNodeInBounds = false ;
447+ foreach ( long nodeId in kv . Value . NodeIds )
448+ {
449+ if ( state . Nodes . TryGetValue ( nodeId , out OsmNode ? n )
450+ && n . Lat <= keepNorth
451+ && n . Lon <= keepEast
452+ && n . Lat >= keepSouth
453+ && n . Lon >= keepWest )
454+ {
455+ hasNodeInBounds = true ;
456+ break ;
457+ }
458+ }
459+
460+ if ( ! hasNodeInBounds )
461+ {
462+ wayIdsToEvict . Add ( kv . Key ) ;
463+ }
464+ }
465+
466+ // Node IDs still claimed by surviving ways must be retained.
467+ HashSet < long > survivingWayNodeIds = [ ] ;
468+ foreach ( KeyValuePair < long , OsmWay > kv in state . Ways )
469+ {
470+ if ( ! wayIdsToEvict . Contains ( kv . Key ) )
471+ {
472+ foreach ( long nodeId in kv . Value . NodeIds )
473+ {
474+ survivingWayNodeIds . Add ( nodeId ) ;
475+ }
476+ }
477+ }
478+
479+ // Pass 2: evict Fetched nodes outside keep-bounds not claimed by a surviving way.
480+ List < long > nodeIdsToEvict = [ ] ;
481+ foreach ( KeyValuePair < long , OsmNode > kv in state . Nodes )
482+ {
483+ if ( state . EditStates . GetValueOrDefault ( kv . Value . Ref ) != EditState . Fetched )
484+ {
485+ continue ;
486+ }
487+
488+ OsmNode node = kv . Value ;
489+ bool outsideBounds = node . Lat < keepSouth || node . Lat > keepNorth
490+ || node . Lon < keepWest || node . Lon > keepEast ;
491+
492+ if ( outsideBounds && ! survivingWayNodeIds . Contains ( kv . Key ) )
493+ {
494+ nodeIdsToEvict . Add ( kv . Key ) ;
495+ }
496+ }
497+
498+ // Pass 3: evict Fetched relations with no surviving member remaining in the buffer.
499+ HashSet < long > evictedNodeIds = [ .. nodeIdsToEvict ] ;
500+ HashSet < long > evictedWayIds = [ .. wayIdsToEvict ] ;
501+
502+ List < long > relIdsToEvict = [ ] ;
503+ foreach ( KeyValuePair < long , OsmRelation > kv in state . Relations )
504+ {
505+ if ( state . EditStates . GetValueOrDefault ( kv . Value . Ref ) != EditState . Fetched )
506+ {
507+ continue ;
508+ }
509+
510+ bool hasSurvivingMember = false ;
511+ foreach ( OsmMember member in kv . Value . Members )
512+ {
513+ switch ( member . Type )
514+ {
515+ case OsmElementTypes . Node when state . Nodes . ContainsKey ( member . Ref )
516+ && ! evictedNodeIds . Contains ( member . Ref ) :
517+ hasSurvivingMember = true ;
518+ break ;
519+
520+ case OsmElementTypes . Way when state . Ways . ContainsKey ( member . Ref )
521+ && ! evictedWayIds . Contains ( member . Ref ) :
522+ hasSurvivingMember = true ;
523+ break ;
524+
525+ case OsmElementTypes . Relation when state . Relations . ContainsKey ( member . Ref ) :
526+ hasSurvivingMember = true ;
527+ break ;
528+ }
529+
530+ if ( hasSurvivingMember )
531+ {
532+ break ;
533+ }
534+ }
535+
536+ if ( ! hasSurvivingMember )
537+ {
538+ relIdsToEvict . Add ( kv . Key ) ;
539+ }
540+ }
541+
542+ if ( wayIdsToEvict . Count == 0 && nodeIdsToEvict . Count == 0 && relIdsToEvict . Count == 0 )
543+ {
544+ return ;
545+ }
546+
547+ List < OsmElementRef > editStateKeysToEvict = [ ] ;
548+ foreach ( long id in nodeIdsToEvict )
549+ {
550+ editStateKeysToEvict . Add ( new OsmElementRef ( OsmElementTypes . Node , id ) ) ;
551+ }
552+ foreach ( long id in wayIdsToEvict )
553+ {
554+ editStateKeysToEvict . Add ( new OsmElementRef ( OsmElementTypes . Way , id ) ) ;
555+ }
556+ foreach ( long id in relIdsToEvict )
557+ {
558+ editStateKeysToEvict . Add ( new OsmElementRef ( OsmElementTypes . Relation , id ) ) ;
559+ }
560+
561+ _editState . SetState ( state with
562+ {
563+ Nodes = state . Nodes . RemoveRange ( nodeIdsToEvict ) ,
564+ Ways = state . Ways . RemoveRange ( wayIdsToEvict ) ,
565+ Relations = state . Relations . RemoveRange ( relIdsToEvict ) ,
566+ EditStates = state . EditStates . RemoveRange ( editStateKeysToEvict )
567+ } ) ;
568+ }
420569
421570 internal async Task RunFetchBboxAsync ( MapBounds bounds , IMediator mediator , CancellationToken ct )
422571 {
0 commit comments