Add zoom-gated AIS subscription with viewport-span threshold#149
Merged
Conversation
The AIS overlay used to subscribe to aisstream.io immediately on viewer startup, using whatever the initial (effectively global) viewport was. That flooded the map with vessels far from anything the user was looking at, wasted aisstream.io quota, and slowed cold start. This change introduces a viewer-side gate that defers the first subscription until the visible viewport's lat-span and lon-span have both shrunk to or below a configurable threshold. Once the gate trips, the subscription is opened with the live viewport bounding box, and subsequent pans / zooms keep the bbox in sync via debounced UpdateArea calls. Activation is one-shot: the subscription stays alive even if the user later zooms back out. * AisOverlaySettings.ActivationViewportSpanDegrees (nullable double, default 50.0; null preserves the legacy subscribe-immediately behaviour) is round-trippable through ViewerSettings JSON. * DeferredAisFeatureSource decorator wraps the real AisDynamicFeatureSource and gates construction on the first qualifying viewport snapshot. * IMapViewportNotifier + MapViewportNotifier translate Mapsui's EPSG:3857 viewport into a lat/lon snapshot and publish it to subscribers. MainWindow binds the notifier to the live Navigator before the dynamic-source overlay host is built. * Settings dialog gets a localised numeric input for the threshold with tooltip + helper text. * Design doc docs/design/ais-zoom-gated-subscription.md captures the Q1-Q6 decisions; viewer + AIS READMEs and docs/toc.yml are updated. Tests: AisOverlaySettings round-trip; DeferredAisFeatureSource gate-closed/open semantics, one-shot activation, debounced UpdateArea, dispose; MapViewportNotifier projection; SettingsViewModel persistence and <=0 normalisation; factory deferred-wrapper branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
Performance Gate✅ PASSED — no regressions. Threshold: 10.0%, MAD multiplier (k): 3.0, retry-zone mult: 2.0× Scenario summary
exchange-set-openIteration statistics
Spans (sum of all iterations)
Metrics
s101-portray-coldIteration statistics
Spans (sum of all iterations)
Metrics
s101-portray-warmIteration statistics
Spans (sum of all iterations)
Metrics
s101-render-warmIteration statistics
Spans (sum of all iterations)
Metrics
s102-coverageIteration statistics
Spans (sum of all iterations)
Metrics
s102-coverage-openIteration statistics
Spans (sum of all iterations)
Metrics
s102-coverage-render-largeIteration statistics
Spans (sum of all iterations)
Metrics
s124-vectorIteration statistics
Spans (sum of all iterations)
Metrics
s201-vectorIteration statistics
Spans (sum of all iterations)
Metrics
Generated by EncDotNet.S100.PerfReport gate command |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
PR-D3 (#142) shipped the AIS overlay; PR-D4 (#147) added click-to-identify. Today the overlay subscribes to aisstream.io immediately on viewer startup with whatever the initial (effectively global) viewport is. That floods the map with vessels far from anything the user is looking at, wastes aisstream.io quota, and slows cold start.
What
This PR adds a viewer-side gate that defers the first AIS subscription until the visible viewport's lat-span and lon-span have both shrunk to or below a configurable threshold. Once the gate trips, the subscription opens with the live viewport bounding box, and subsequent pans / zooms keep the bbox in sync via debounced
UpdateAreacalls. Activation is one-shot — the subscription stays alive even if the user later zooms back out.Decisions (full rationale in the design doc)
UpdateArea(debounced 250 ms).AisOverlaySettings.ActivationViewportSpanDegrees— nullable double, default50.0.null= no gate (legacy behaviour).IDynamicFeatureSource. Lazily constructs the realAisDynamicFeatureSourceon first activation; pre-activationCurrentFeaturesis empty andChangednever fires.Changes
AisOverlaySettings.ActivationViewportSpanDegrees(nullable double, default50.0); null preserves the legacy subscribe-immediately path.DeferredAisFeatureSourcewraps the realAisDynamicFeatureSource, gates construction on the first qualifying viewport snapshot, then debouncesUpdateArea(250 ms trailing-edge by default;TimeSpan.Zerofor tests).IMapViewportNotifier+MapViewportNotifiertranslate Mapsui's EPSG:3857 viewport into a lat/lon snapshot.MainWindowbinds the notifier to the liveNavigatorbefore the dynamic-source overlay host is built. This is also the first timeUpdateAreais wired anywhere in the viewer.docs/design/ais-zoom-gated-subscription.md; updated viewer + DynamicSources.Ais READMEs; new entry indocs/toc.ymlunder Design notes.Tests
AisOverlaySettingsTestsViewerSettingsJSON.DeferredAisFeatureSourceTestsUpdateArea; debounce coalesces bursts;Changedevents forward;DisposeAsyncdisposes inner; reads initial snapshot from notifier at construction; throws on non-positive threshold.MapViewportNotifierTestsTryProjectreturns null for unsized viewport; round-trips throughSphericalMercator; whole-world view yields ±180/±~85° clamped extents.SettingsViewModelAisActivationTests≤ 0normalises to null; null persists as null.AisOverlayFactoryTests(extended)BuildSourcereturns the deferred wrapper when threshold is set and notifier is provided; returns the real source when threshold is null OR notifier is unavailable.dotnet build -c Releaseis clean (0 errors).dotnet test -c Releaseis green across all test projects.Out of scope
DynamicSourceMetadata.Descriptionisn't surfaced on the row today).