Repository: Mapsui/Mapsui
Pull request #2989: Fix RotationTo at map start
Author: login: pauldendulk
Created: 2025-05-06T12:50:33Z | Merged: 2025-05-19T16:03:33Z



Diff:
diff --git a/.github/workflows/dotnet-docs.yml b/.github/workflows/dotnet-docs.yml
index 3810154a8a..2bd53195ab 100644
--- a/.github/workflows/dotnet-docs.yml
+++ b/.github/workflows/dotnet-docs.yml
@@ -42,7 +42,7 @@ jobs:
       uses: actions/setup-dotnet@v4
       with:
          dotnet-version: |
-           9.0.202
+           9.0.203
            8.0.407
     - name: install workloads
       run: dotnet workload install maui macos android ios maccatalyst wasm-tools wasm-tools-net8 
diff --git a/.github/workflows/dotnet-release-nugets.yml b/.github/workflows/dotnet-release-nugets.yml
index e586cc7d4a..4388083c97 100644
--- a/.github/workflows/dotnet-release-nugets.yml
+++ b/.github/workflows/dotnet-release-nugets.yml
@@ -33,7 +33,7 @@ jobs:
       uses: actions/setup-dotnet@v4
       with:
          dotnet-version: |
-           9.0.202
+           9.0.203
            8.0.407
 
     # The version tag needs to be set before the release. If not if will fail on nuget publish.
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 5cd6814dfc..9a9e82f82a 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -56,7 +56,7 @@ jobs:
       uses: actions/setup-dotnet@v4
       with:
          dotnet-version: |
-           9.0.202
+           9.0.203
            8.0.407
     # Open JDK
     - name: Install Open JDK 11
@@ -74,7 +74,7 @@ jobs:
     - name: Install Uno Check
       run: dotnet tool install -g Uno.Check --version 1.30.1
     - name: Uno Check
-      run: uno-check -v --ci --non-interactive --fix --skip androidemulator --dotnet 9.0.202
+      run: uno-check -v --ci --non-interactive --fix --skip androidemulator --dotnet 9.0.203
     - name: Restore dependencies
       run: dotnet restore Mapsui.Linux.slnf
     - name: Build Mapsui.Linux.slnf
@@ -125,18 +125,20 @@ jobs:
       uses: actions/setup-dotnet@v4
       with:
          dotnet-version: |
-           9.0.202
+           9.0.203
            8.0.407
+    - name: Set MSBuildEnableWorkloadResolver
+      run: echo "MSBuildEnableWorkloadResolver=true" >> $GITHUB_ENV
     - name: install workloads maui macos android ios maccatalyst wasm-tools wasm-tools-net8
       run: dotnet workload install maui macos android ios maccatalyst wasm-tools wasm-tools-net8
     - name: install Uno.Check 1.26.1
       run: dotnet tool install --global Uno.Check --version 1.26.1
     - name: install Android SDK 34
-      run: uno-check -v --ci --non-interactive --fix --target android --skip androidemulator --dotnet 9.0.202
+      run: uno-check -v --ci --non-interactive --fix --target android --skip androidemulator --dotnet 9.0.203
     - name: Install newest Uno Check
       run: dotnet tool update -g uno.check
     - name: Uno Check
-      run: uno-check -v --ci --non-interactive --fix --skip xcode --skip androidemulator --dotnet 9.0.202
+      run: uno-check -v --ci --non-interactive --fix --skip xcode --skip androidemulator --dotnet 9.0.203
     - name: Restore dependencies Mapsui.Mac
       run: dotnet restore Mapsui.Mac.slnf       
     # Build        
@@ -197,7 +199,7 @@ jobs:
       uses: actions/setup-dotnet@v4
       with:
          dotnet-version: |
-           9.0.202
+           9.0.203
            8.0.407
     # Java Sdk install 11
     - name: Install Open JDK 11
@@ -214,7 +216,7 @@ jobs:
     - name: Install Uno Check
       run: dotnet tool install -g Uno.Check --version 1.30.1
     - name: Uno Check
-      run: uno-check -v --ci --non-interactive --fix --skip vswin --skip androidemulator --skip androidsdk --skip psexecpolicy --dotnet 9.0.202
+      run: uno-check -v --ci --non-interactive --fix --skip vswin --skip androidemulator --skip androidsdk --skip psexecpolicy --dotnet 9.0.203
     - name: Restore dependencies (dotnet)
       run: dotnet restore Mapsui.sln  
     - name: Run dotnet format
diff --git a/Mapsui/Navigator.cs b/Mapsui/Navigator.cs
index 154afbbebe..48440c2730 100644
--- a/Mapsui/Navigator.cs
+++ b/Mapsui/Navigator.cs
@@ -14,7 +14,7 @@ public class Navigator
 {
     private Viewport _viewport = new();
     private IEnumerable<AnimationEntry<Viewport>> _animations = [];
-    private readonly List<Action> _initialization = [];
+    private readonly List<Action> _initialization;
     private MMinMax? _defaultZoomBounds;
     private MRect? _defaultPanBounds;
     private MMinMax? _overrideZoomBounds;
@@ -115,24 +115,26 @@ private set
 
     public bool IsInitialized { get; private set; } = false;
 
-    private void Initialize()
+    public Navigator()
+    {
+        _initialization = new List<Action> { () => ZoomToPanBounds() };
+    }
+
+    private void Initialize(MRect extent)
     {
         if (!IsInitialized)
         {
             IsInitialized = true;
 
-            if (_initialization.Count == 0)
-            {
-                ZoomToPanBounds();
-                return;
-            }
-
+            // Actions could either modify the current state (after ZoomToBox above) or override the current state.
             foreach (var action in _initialization)
             {
                 action();
             }
 
             _initialization.Clear();
+
+            OnViewportChanged(_viewport);
         }
     }
 
@@ -530,7 +532,7 @@ public void SetSize(double width, double height)
     private void InitializeIfNeeded()
     {
         if (ShouldInitialize())
-            Initialize();
+            Initialize(PanBounds ?? throw new Exception("PanBounds is null. This should not be possible when initializing."));
     }
 
     private void OnRefreshDataRequest(ChangeType changeType)
@@ -625,6 +627,9 @@ private Viewport LimitXYProportionalToResolution(
         // as the resolution. This is to prevent the situation where you zoom out while hitting the zoom bounds
         // and you see no change in resolution, but you will see a change in pan.
 
+        if (originalViewport.Resolution <= 0)
+            return goalViewport;
+
         var resolutionLimiting = CalculateResolutionLimiting(
             originalViewport.Resolution, goalViewport.Resolution, limitedViewport.Resolution);
 
@@ -712,7 +717,7 @@ private void AddToInitialization(Action action)
 
     private bool ShouldInitialize() => !IsInitialized && CanInitialize();
 
-    private bool CanInitialize() => Viewport.HasSize() && PanBounds is not null; // Should we check on ZoomBounds too?
+    private bool CanInitialize() => Viewport.HasSize() && PanBounds is not null;
 
     internal int GetAnimationsCount => _animations.Count();
 
diff --git a/Samples/Mapsui.Samples.Common/Maps/Demo/OsmSample.cs b/Samples/Mapsui.Samples.Common/Maps/Demo/OsmSample.cs
index 142e3bc9dc..53456fe253 100644
--- a/Samples/Mapsui.Samples.Common/Maps/Demo/OsmSample.cs
+++ b/Samples/Mapsui.Samples.Common/Maps/Demo/OsmSample.cs
@@ -11,7 +11,7 @@ namespace Mapsui.Samples.Common.Maps.Demo;
 public class OsmSample : ISample
 {
     public string Name => "OpenStreetMap";
-    public string Category => "1";
+    public string Category => "Demo";
 
     public Task<Map> CreateMapAsync()
     {
diff --git a/Samples/Mapsui.Samples.Common/Maps/Navigation/KeepCenterInMapSample.cs b/Samples/Mapsui.Samples.Common/Maps/Navigation/KeepCenterInMapSample.cs
index 673f99b25b..64a59b21db 100644
--- a/Samples/Mapsui.Samples.Common/Maps/Navigation/KeepCenterInMapSample.cs
+++ b/Samples/Mapsui.Samples.Common/Maps/Navigation/KeepCenterInMapSample.cs
@@ -21,20 +21,20 @@ public Task<Map> CreateMapAsync()
         // This is the default limiter. This limiter ensures that the center 
         // of the viewport always is within the PanBounds. When no OverridePanBounds
         // is specified the Navigator uses the Map.Extent as default. In this sample
-        // the extent of adagaskar is used. When the PanBounds are limited it usually makes
+        // the extent of Madagascar is used. When the PanBounds are limited it usually makes
         // sense to also limit the ZoomBounds.
 
-        var panBounds = GetLimitsOfMadagaskar();
+        var panBounds = GetLimitsOfMadagascar();
         map.Layers.Add(CreatePanBoundsLayer(panBounds));
 
         map.Navigator.OverridePanBounds = panBounds;
-        map.Navigator.OverrideZoomBounds = new MMinMax(0.15, 2500);
+        map.Navigator.OverrideZoomBounds = new MMinMax(0.15, 3000);
         map.Navigator.ZoomToBox(panBounds);
 
         return Task.FromResult(map);
     }
 
-    private static MRect GetLimitsOfMadagaskar()
+    private static MRect GetLimitsOfMadagascar()
     {
         var (minX, minY) = SphericalMercator.FromLonLat(41.8, -27.2);
         var (maxX, maxY) = SphericalMercator.FromLonLat(52.5, -11.6);
diff --git a/Samples/Mapsui.Samples.Common/Maps/Navigation/KeepWithinExtentSample.cs b/Samples/Mapsui.Samples.Common/Maps/Navigation/KeepWithinExtentSample.cs
index b975715c26..7f707f8205 100644
--- a/Samples/Mapsui.Samples.Common/Maps/Navigation/KeepWithinExtentSample.cs
+++ b/Samples/Mapsui.Samples.Common/Maps/Navigation/KeepWithinExtentSample.cs
@@ -15,16 +15,15 @@ public Task<Map> CreateMapAsync()
         var map = new Map();
         map.Layers.Add(OpenStreetMap.CreateTileLayer());
 
-        var panBounds = GetLimitsOfMadagaskar();
+        var panBounds = GetLimitsOfMadagascar();
         map.Layers.Add(KeepCenterInMapSample.CreatePanBoundsLayer(panBounds));
         map.Navigator.Limiter = new ViewportLimiterKeepWithinExtent();
         map.Navigator.RotationLock = true;
         map.Navigator.OverridePanBounds = panBounds;
-        map.Navigator.ZoomToBox(panBounds);
         return Task.FromResult(map);
     }
 
-    private static MRect GetLimitsOfMadagaskar()
+    private static MRect GetLimitsOfMadagascar()
     {
         var (minX, minY) = SphericalMercator.FromLonLat(41.8, -27.2);
         var (maxX, maxY) = SphericalMercator.FromLonLat(52.5, -11.6);
diff --git a/Tests/Mapsui.Rendering.Skia.Tests/MapRegressionTests.cs b/Tests/Mapsui.Rendering.Skia.Tests/MapRegressionTests.cs
index f35b09be43..b32a382181 100644
--- a/Tests/Mapsui.Rendering.Skia.Tests/MapRegressionTests.cs
+++ b/Tests/Mapsui.Rendering.Skia.Tests/MapRegressionTests.cs
@@ -10,8 +10,11 @@
 using Mapsui.Samples.Common.Maps.Demo;
 using Mapsui.Samples.Common.Maps.Geometries;
 using Mapsui.Samples.Common.Maps.Info;
+using Mapsui.Samples.Common.Maps.Performance;
 using Mapsui.Samples.Common.Maps.Special;
+using Mapsui.Samples.Common.Maps.WFS;
 using Mapsui.Samples.Common.Maps.Widgets;
+using Mapsui.Samples.Common.Maps.WMS;
 using Mapsui.Styles;
 using Mapsui.UI;
 using Mapsui.Widgets;
@@ -57,6 +60,9 @@ .. AllSamples.GetSamples().Where(f => ExcludedSamples
             new ArcGISDynamicServiceSample(), // Excluded cause it was not reliable and had no priority to fix.
             new CustomSvgStyleSample(), // Is currently not functioning and should be fixed with a redesign.
             new ImageCalloutSample(), // Is currently not functioning and should be fixed with a rewrite of the sample.
+            new WmsBasilicataSample(), // Times out,
+            new RasterizingTileLayerWithThousandsOfPolygonsSample(), // Crashes on the build server. Perhaps a memory limitation.
+            new WfsGeometryFilterSample(), // Crashes on the build server.
         ];
 
     [Test]
diff --git a/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/KeepCenterInMapSample.Regression.png b/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/KeepCenterInMapSample.Regression.png
index c941119076..1f5998fae0 100644
Binary files a/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/KeepCenterInMapSample.Regression.png and b/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/KeepCenterInMapSample.Regression.png differ
diff --git a/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/KeepWithinExtentSample.Regression.png b/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/KeepWithinExtentSample.Regression.png
index 8636ca6c5a..5652d9e4bd 100644
Binary files a/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/KeepWithinExtentSample.Regression.png and b/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/KeepWithinExtentSample.Regression.png differ
diff --git a/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmsGebcoSample.Regression.png b/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmsGebcoSample.Regression.png
index fdecc3a051..d10d837388 100644
Binary files a/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmsGebcoSample.Regression.png and b/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmsGebcoSample.Regression.png differ
diff --git a/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmsOpenSeaSample.Regression.png b/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmsOpenSeaSample.Regression.png
index bc4b3c56b2..57706598b5 100644
Binary files a/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmsOpenSeaSample.Regression.png and b/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmsOpenSeaSample.Regression.png differ
diff --git a/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmtsZurichSample.Regression.png b/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmtsZurichSample.Regression.png
index 27a63ac694..03c4ba7bc6 100644
Binary files a/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmtsZurichSample.Regression.png and b/Tests/Mapsui.Rendering.Skia.Tests/Resources/Images/OriginalRegression/WmtsZurichSample.Regression.png differ
diff --git a/Tests/Mapsui.Tests/UI/ViewportLimiterKeepWithinExtentTests.cs b/Tests/Mapsui.Tests/UI/ViewportLimiterKeepWithinExtentTests.cs
new file mode 100644
index 0000000000..f33ebbfb6a
--- /dev/null
+++ b/Tests/Mapsui.Tests/UI/ViewportLimiterKeepWithinExtentTests.cs
@@ -0,0 +1,35 @@
+﻿using Mapsui.Limiting;
+using NUnit.Framework;
+using NUnit.Framework.Legacy;
+
+namespace Mapsui.Tests.UI;
+
+[TestFixture]
+public class ViewportLimiterKeepWithinExtentTests
+{
+    [Test]
+    public void TestRestrictZoom()
+    {
+        // arrange
+        var viewport1 = new Viewport(12.61510114915, 41.8676902329, 0.014662864685125001, 0, 800, 600);
+        var viewport2 = new Viewport(12.61510114915, 41.8676902329, 0.017492343139666661, 0, 800, 600);
+
+        // viewport.Center is (0, 0) at this point
+        var limiter = new ViewportLimiterKeepWithinExtent();
+        var panBounds = GetLimitsOfItaly();
+
+        // act 
+        var result1 = limiter.Limit(viewport1, panBounds, null);
+        var result2 = limiter.Limit(viewport2, panBounds, null);
+
+        // assert
+        ClassicAssert.AreEqual(result2, result1);
+    }
+
+    private static MRect GetLimitsOfItaly()
+    {
+        var (minX, minY) = (6.7499552751, 36.619987291);
+        var (maxX, maxY) = (18.4802470232, 47.1153931748);
+        return new MRect(minX, minY, maxX, maxY);
+    }
+}
diff --git a/global.json b/global.json
index 9dd81b0838..5d550823fb 100644
--- a/global.json
+++ b/global.json
@@ -1,12 +1,12 @@
 {
   "sdk": {
     "allowPrerelease": false,
-    "version": "9.0.202",
+    "version": "9.0.203",
     "rollForward": "disable"
   },
   "tools": {
     "allowPrerelease": false,
-    "dotnet": "9.0.202",
+    "dotnet": "9.0.203",
     "rollForward": "disable"
   },
   "msbuild-sdks": {


Reviews:
user: login: pauldendulk
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T12:54:36Z
user: login: pauldendulk
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T12:57:59Z

Review comments:
diff_hunk: @@ -121,12 +121,9 @@ private void Initialize()
         {
             IsInitialized = true;
 
-            if (_initialization.Count == 0)
-            {
-                ZoomToPanBounds();
-                return;
-            }
+            ZoomToBox(PanBounds, MBoxFit.Fill);
path: Mapsui/Navigator.cs
user: login: pauldendulk
body: It was already clear there was a problem with this code before this issue. In some cases you want to override the default behavior, in other cases you want to slightly modify it (for instance if you just want rotate). Now this is solved by always calling the default. If you want to override the full extent then call that from your own code, the default call will be irrelevant.
created_at: 2025-05-06T12:54:36Z
author_association: MEMBER
diff_hunk: @@ -21,20 +21,20 @@ public Task<Map> CreateMapAsync()
         // This is the default limiter. This limiter ensures that the center 
         // of the viewport always is within the PanBounds. When no OverridePanBounds
         // is specified the Navigator uses the Map.Extent as default. In this sample
-        // the extent of adagaskar is used. When the PanBounds are limited it usually makes
+        // the extent of Madagascar is used. When the PanBounds are limited it usually makes
         // sense to also limit the ZoomBounds.
 
-        var panBounds = GetLimitsOfMadagaskar();
+        var panBounds = GetLimitsOfMadagascar();
         map.Layers.Add(CreatePanBoundsLayer(panBounds));
 
         map.Navigator.OverridePanBounds = panBounds;
-        map.Navigator.OverrideZoomBounds = new MMinMax(0.15, 2500);
+        map.Navigator.OverrideZoomBounds = new MMinMax(0.15, 3000);
path: Samples/Mapsui.Samples.Common/Maps/Navigation/KeepCenterInMapSample.cs
user: login: pauldendulk
body: The original idea was that this would fit the whole extent, but this depends on the size of the map so does not work in general. I made it a bit bigger so that at least all our samples show the full extent.
created_at: 2025-05-06T12:55:54Z
author_association: MEMBER

PR comments:
user: login: charlenni
created_at: 2025-05-06T13:42:09Z
body: Good solution for this problem. Thank you for looking into this.
author_association: MEMBER
user: login: pauldendulk
created_at: 2025-05-19T16:02:33Z
body: ah, the mac build works again, my plan worked, just wait a week and see if they have fixed the build agents.
author_association: MEMBER