Skip to content

Commit 07bf855

Browse files
committed
SimpleWeather: prepare for BingMaps shutdown
* BingMaps will shut down on June 30, 2025 * Limit BingMaps usage in preparation * Add MapBox and OpenStreetMap as the alternate base map for Windows * Add Radar.com as a location provider
1 parent 87913ef commit 07bf855

File tree

58 files changed

+1313
-517
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1313
-517
lines changed

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ API_KEY.txt
279279
/SimpleWeather.Weather-API/Keys/Keys.cs
280280
/SimpleWeather.Weather-API/Keys/Keys.cs.bak
281281
/SimpleWeather.Weather-API/Keys/Keys.7z
282+
/SimpleWeather.Weather-API/Keys/MapBoxConfig.cs
283+
/SimpleWeather.Weather-API/Keys/MapBoxConfig.cs.bak
282284
/SimpleWeather.Weather-API/Keys/WeatherKitConfig.cs
283285
/SimpleWeather.Weather-API/Keys/WeatherKitConfig.cs.bak
284286
/Maui/SimpleToolkit

Diff for: CreateMigration.bat

-7
This file was deleted.

Diff for: MauiUnitTestProject/UnitTests.cs

+18
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
using Debug = System.Diagnostics.Debug;
3131
using ThreadState = System.Threading.ThreadState;
3232
using WeatherUtils = SimpleWeather.Utils.WeatherUtils;
33+
using SimpleWeather.Weather_API.Radar;
34+
3335
#if __IOS__
3436
using FirebaseRemoteConfig = Firebase.RemoteConfig.RemoteConfig;
3537

@@ -388,6 +390,22 @@ public async Task WeatherAPILocationTest()
388390
Assert.NotNull(nameModel);
389391
}
390392

393+
[Fact]
394+
public async Task RadarAPILocationTest()
395+
{
396+
var locationProvider = new RadarLocationProvider();
397+
var locations = await locationProvider.GetLocations("Redmond, WA", WeatherAPI.WeatherApi)
398+
.ConfigureAwait(false);
399+
Assert.True(locations?.Count > 0);
400+
401+
var queryVM = locations.FirstOrDefault(l => l != null && l.LocationName.StartsWith("Redmond"));
402+
Assert.NotNull(queryVM);
403+
404+
var geocodeVM = await locationProvider.GetLocation(new WeatherUtils.Coordinate(34.0207305, -118.6919157), WeatherAPI.WeatherApi);
405+
Assert.NotNull(geocodeVM);
406+
Assert.True(geocodeVM.LocationName != null && geocodeVM.LocationName.StartsWith("Malibu"));
407+
}
408+
391409
[Fact]
392410
public async Task GetMeteoFranceWeather()
393411
{

Diff for: SimpleWeather.Common/Location/LocationProvider.cs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using AndroidX.Core.Content;
44
#endif
55
#if WINUI
6+
using System.Threading;
67
using Windows.Devices.Geolocation;
78
#else
89
using Microsoft.Maui.ApplicationModel;

Diff for: SimpleWeather.Common/Migrations/VersionMigrations.cs

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
using System.Collections.Generic;
2-
using System.Globalization;
3-
using System.Threading.Tasks;
4-
using CommunityToolkit.Mvvm.DependencyInjection;
5-
using Microsoft.Maui.ApplicationModel;
6-
using SimpleWeather.Preferences;
1+
using CommunityToolkit.Mvvm.DependencyInjection;
72
using SimpleWeather.Utils;
83
using SimpleWeather.Weather_API;
94
using SimpleWeather.WeatherData;
105
using SimpleWeather.WeatherData.Images;
116
using SQLite;
7+
using System.Collections.Generic;
8+
using System.Globalization;
9+
using System.Threading.Tasks;
1210

1311
namespace SimpleWeather.Common.Migrations
1412
{
@@ -22,7 +20,7 @@ internal static async Task PerformVersionMigrations(SQLiteAsyncConnection weathe
2220
#if WINUI
2321
var PackageVersion = Windows.ApplicationModel.Package.Current.Id.Version;
2422
#else
25-
var PackageVersion = AppInfo.Version;
23+
var PackageVersion = Microsoft.Maui.ApplicationModel.AppInfo.Version;
2624
#endif
2725
var version = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}0",
2826
PackageVersion.Major, PackageVersion.Minor,
@@ -169,7 +167,7 @@ internal static async Task PerformVersionMigrations(SQLiteAsyncConnection weathe
169167
// Windows: unregister all bg tasks
170168
if (SettingsMgr.VersionCode < 5801)
171169
{
172-
var ImageDataContainer = new SettingsContainer("images");
170+
var ImageDataContainer = new Preferences.SettingsContainer("images");
173171
ImageDataContainer.Clear();
174172

175173
#if WINDOWS || WINUI
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using Mapsui;
2+
using Mapsui.Extensions;
3+
using Mapsui.Rendering.Skia;
4+
using Mapsui.Rendering.Skia.Cache;
5+
using Mapsui.Rendering.Skia.Extensions;
6+
using Mapsui.Rendering.Skia.Images;
7+
using Mapsui.Rendering.Skia.SkiaWidgets;
8+
using Mapsui.Styles;
9+
using Mapsui.Widgets;
10+
using Mapsui.Widgets.ButtonWidgets;
11+
using SimpleWeather.SkiaSharp;
12+
using SkiaSharp;
13+
using Svg.Skia;
14+
using System;
15+
16+
namespace SimpleWeather.NET.MapsUi
17+
{
18+
public class ImageButtonWidgetRenderer : ISkiaWidgetRenderer
19+
{
20+
public void Draw(SKCanvas canvas, Viewport viewport, IWidget widget, RenderService renderService, float layerOpacity)
21+
{
22+
var button = (ImageButtonWidget)widget;
23+
24+
if (button.ImageSource == null)
25+
throw new InvalidOperationException("ImageSource is not set");
26+
27+
var drawableImage = renderService.DrawableImageCache.GetOrCreate(button.ImageSource,
28+
() => TryCreateDrawableImage(button.ImageSource, renderService.ImageSourceCache));
29+
if (drawableImage == null)
30+
return;
31+
32+
button.UpdateEnvelope(
33+
button.Width != 0 ? button.Width : drawableImage.Width + button.Padding.Left + button.Padding.Right,
34+
button.Height != 0 ? button.Height : drawableImage.Height + button.Padding.Top + button.Padding.Bottom,
35+
viewport.Width,
36+
37+
viewport.Height);
38+
39+
if (button.Envelope == null)
40+
return;
41+
42+
using var backPaint = new SKPaint { Color = button.BackColor.ToSkia(layerOpacity), IsAntialias = true };
43+
canvas.DrawRoundRect(button.Envelope.ToSkia(), (float)button.CornerRadius, (float)button.CornerRadius, backPaint);
44+
45+
// Get the scale for picture in each direction
46+
var scaleX = (button.Envelope.Width - button.Padding.Left - button.Padding.Right) / drawableImage.Width;
47+
var scaleY = (button.Envelope.Height - button.Padding.Top - button.Padding.Bottom) / drawableImage.Height;
48+
49+
50+
using var skPaint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, IsDither = true };
51+
if (drawableImage is SKBitmapDrawableImage bitmapImage)
52+
{
53+
int cnt = canvas.Save();
54+
55+
// Rotate picture
56+
var matrix = SKMatrix.CreateRotationDegrees((float)button.Rotation, drawableImage.Width / 2f, drawableImage.Height / 2f);
57+
// Create a scale matrix
58+
matrix = matrix.PostConcat(SKMatrix.CreateScale((float)scaleX, (float)scaleY));
59+
// Translate picture to right place
60+
matrix = matrix.PostConcat(SKMatrix.CreateTranslation((float)(button.Envelope.MinX + button.Padding.Left), (float)(button.Envelope.MinY + button.Padding.Top)));
61+
// Draw picture
62+
canvas.SetMatrix(matrix);
63+
canvas.DrawBitmap(bitmapImage.Bitmap, 0, 0, skPaint);
64+
65+
canvas.RestoreToCount(cnt);
66+
}
67+
else if (drawableImage is SvgDrawableImage svgImage)
68+
{
69+
// Rotate picture
70+
var matrix = SKMatrix.CreateRotationDegrees((float)button.Rotation, drawableImage.Width / 2f, drawableImage.Height / 2f);
71+
// Create a scale matrix
72+
matrix = matrix.PostConcat(SKMatrix.CreateScale((float)scaleX, (float)scaleY));
73+
// Translate picture to right place
74+
matrix = matrix.PostConcat(SKMatrix.CreateTranslation((float)(button.Envelope.MinX + button.Padding.Left), (float)(button.Envelope.MinY + button.Padding.Top)));
75+
// Draw picture
76+
canvas.DrawPicture(svgImage.Picture, in matrix, skPaint);
77+
}
78+
else
79+
throw new NotSupportedException("DrawableImage type not supported");
80+
}
81+
82+
private static IDrawableImage? TryCreateDrawableImage(string key, ImageSourceCache imageSourceCache)
83+
{
84+
byte[] array = imageSourceCache.Get(key);
85+
if (array == null)
86+
{
87+
return null;
88+
}
89+
90+
return ToDrawableImage(array);
91+
}
92+
93+
private static IDrawableImage ToDrawableImage(byte[] bytes)
94+
{
95+
if (bytes.IsSvg())
96+
{
97+
return new SvgDrawableImage(SKSvg.CreateFromStream(new MemoryStream(bytes)));
98+
}
99+
100+
return new SKBitmapDrawableImage(SKBitmap.Decode(bytes));
101+
}
102+
103+
private class SvgDrawableImage(SKSvg svg) : SKSvgDrawable(svg), IDrawableImage
104+
{
105+
public float Width => svg.Picture.CullRect.Width;
106+
107+
public float Height => svg.Picture.CullRect.Height;
108+
109+
public SKPicture Picture => svg.Picture;
110+
}
111+
112+
private class SKBitmapDrawableImage(SKBitmap bitmap) : SKBitmapDrawable(bitmap), IDrawableImage
113+
{
114+
public float Width => bitmap.Width;
115+
116+
public float Height => bitmap.Height;
117+
118+
public SKBitmap Bitmap => bitmap;
119+
}
120+
}
121+
}

Diff for: SimpleWeather.NET.Shared/MapsUi/MapBox.cs

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#if !__IOS__
2+
using BingMapsRESTToolkit;
3+
using BruTile;
4+
using BruTile.Cache;
5+
using BruTile.Predefined;
6+
using BruTile.Web;
7+
using Mapsui.Extensions;
8+
using Mapsui.Layers;
9+
using Mapsui.Styles;
10+
using Mapsui.Tiling.Layers;
11+
using Mapsui.UI.WinUI;
12+
using Mapsui.Widgets;
13+
using Mapsui.Widgets.ButtonWidgets;
14+
using SimpleWeather.Helpers;
15+
using SimpleWeather.Preferences;
16+
using SimpleWeather.Weather_API.Keys;
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Net.Http.Headers;
20+
using System.Text;
21+
using Windows.Storage;
22+
#if WINDOWS
23+
using MapControl = Mapsui.UI.WinUI.MapControl;
24+
#else
25+
using MapControl = Mapsui.UI.Maui.MapControl;
26+
#endif
27+
28+
namespace SimpleWeather.NET.MapsUi
29+
{
30+
public static class MapBox
31+
{
32+
public static TileLayer CreateMapBoxLayer(bool isDarkMode = false)
33+
{
34+
return new TileLayer(GetMapBoxTileSource(isDarkMode))
35+
{
36+
Name = "Root"
37+
};
38+
}
39+
40+
private static HttpTileSource GetMapBoxTileSource(bool isDarkMode = false)
41+
{
42+
string tileUrlTemplate;
43+
44+
if (!string.IsNullOrWhiteSpace(MapBoxConfig.GetMapBoxMapStyle()))
45+
{
46+
tileUrlTemplate = $"https://api.mapbox.com/styles/v1/{MapBoxConfig.GetMapBoxMapStyle()}/tiles/256/{{z}}/{{x}}/{{y}}?access_token={{k}}";
47+
}
48+
else
49+
{
50+
tileUrlTemplate = $"https://api.mapbox.com/v4/mapbox.satellite/{{z}}/{{x}}/{{y}}.jpg90?access_token={{k}}";
51+
}
52+
53+
return new CustomHttpTileSource(
54+
new GlobalSphericalMercator(name: "MapBox", yAxis: YAxis.OSM, minZoomLevel: 2, maxZoomLevel: 18, format: "jpeg"),
55+
urlBuilder: new BasicUrlBuilder(tileUrlTemplate, apiKey: MapBoxConfig.GetMapBoxKey()),
56+
name: "MapBox",
57+
attribution: null,
58+
persistentCache: new FileCache(
59+
Path.Combine(ApplicationDataHelper.GetLocalCacheFolderPath(), Constants.TILE_CACHE_DIR, "MapBox"), "tile.jpeg"),
60+
configureHttpRequestMessage: request =>
61+
{
62+
request.Headers.CacheControl = new CacheControlHeaderValue()
63+
{
64+
MaxAge = TimeSpan.FromDays(30)
65+
};
66+
}
67+
);
68+
}
69+
70+
public static async Task CreateLayersAndWidgets(MapControl mapControl)
71+
{
72+
mapControl?.Map?.Layers?.Insert(1, new Layer("Root0")
73+
{
74+
Attribution = new Mapsui.Widgets.ButtonWidgets.HyperlinkWidget()
75+
{
76+
HorizontalAlignment = HorizontalAlignment.Right,
77+
VerticalAlignment = VerticalAlignment.Bottom,
78+
Url = "https://www.mapbox.com/about/maps/",
79+
Text = "© Mapbox |",
80+
BackColor = Color.Transparent,
81+
TextColor = Color.LightSkyBlue
82+
}
83+
});
84+
mapControl?.Map?.Layers?.Insert(1, new Layer("Root1")
85+
{
86+
Attribution = new Mapsui.Widgets.ButtonWidgets.HyperlinkWidget()
87+
{
88+
HorizontalAlignment = HorizontalAlignment.Right,
89+
VerticalAlignment = VerticalAlignment.Bottom,
90+
Url = "https://www.openstreetmap.org/about/",
91+
Text = "© OpenStreetMap |",
92+
BackColor = Color.Transparent,
93+
TextColor = Color.LightSkyBlue
94+
}
95+
});
96+
mapControl?.Map?.Layers?.Insert(1, new Layer("Root2")
97+
{
98+
Attribution = new Mapsui.Widgets.ButtonWidgets.HyperlinkWidget()
99+
{
100+
HorizontalAlignment = HorizontalAlignment.Right,
101+
VerticalAlignment = VerticalAlignment.Bottom,
102+
Url = "https://www.mapbox.com/contribute/",
103+
Text = "Improve this map |",
104+
BackColor = Color.Transparent,
105+
TextColor = Color.LightSkyBlue
106+
}
107+
});
108+
mapControl?.Map?.Layers?.Insert(1, new Layer("Root3")
109+
{
110+
Attribution = new Mapsui.Widgets.ButtonWidgets.HyperlinkWidget()
111+
{
112+
HorizontalAlignment = HorizontalAlignment.Right,
113+
VerticalAlignment = VerticalAlignment.Bottom,
114+
Url = "https://www.maxar.com/",
115+
Text = "Maxar",
116+
BackColor = Color.Transparent,
117+
TextColor = Color.LightSkyBlue
118+
}
119+
});
120+
121+
#if WINDOWS
122+
var logoFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///SimpleWeather.Shared/Resources/Images/Providers/mapbox_logo_white.svg"));
123+
var path = new Uri(logoFile.Path).ToString();
124+
mapControl?.Map?.Widgets?.Add(new Mapsui.Widgets.ButtonWidgets.ImageButtonWidget()
125+
{
126+
HorizontalAlignment = HorizontalAlignment.Left,
127+
VerticalAlignment = VerticalAlignment.Bottom,
128+
Margin = new Mapsui.MRect(5),
129+
ImageSource = path,
130+
Width = 100,
131+
Height = 22.5,
132+
});
133+
134+
mapControl.Renderer.WidgetRenders[typeof(ImageButtonWidget)] = new ImageButtonWidgetRenderer();
135+
#endif
136+
}
137+
}
138+
}
139+
#endif

Diff for: SimpleWeather.NET.Shared/MapsUi/OpenStreetMap.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public static TileLayer CreateTileLayer()
2424
private static HttpTileSource CreateTileSource()
2525
{
2626
return new CustomHttpTileSource(
27-
new GlobalSphericalMercator(),
27+
new GlobalSphericalMercator(name: "OpenStreetMap", minZoomLevel: 2, maxZoomLevel: 18),
2828
uri: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
2929
name: "OpenStreetMap", attribution: _openStreetMapAttribution,
3030
persistentCache: new FileCache(
@@ -33,7 +33,7 @@ private static HttpTileSource CreateTileSource()
3333
{
3434
request.Headers.CacheControl = new CacheControlHeaderValue()
3535
{
36-
MaxAge = TimeSpan.FromDays(7)
36+
MaxAge = TimeSpan.FromDays(30)
3737
};
3838
}
3939
);

Diff for: SimpleWeather.NET.Shared/Radar/ECCC/ECCCRadarViewProvider.Mapsui.cs

+2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
using SimpleWeather.Weather_API.Utils;
1717
using HorizontalAlignment = Mapsui.Widgets.HorizontalAlignment;
1818
using VerticalAlignment = Mapsui.Widgets.VerticalAlignment;
19+
1920
#if WINDOWS
2021
using MapControl = Mapsui.UI.WinUI.MapControl;
2122
using Microsoft.UI.Xaml;
23+
using Microsoft.UI.Xaml.Controls;
2224
#else
2325
using Mapsui.UI.Maui;
2426
using MapControl = Mapsui.UI.Maui.MapControl;

0 commit comments

Comments
 (0)