diff --git a/src/Controls/Maps/src/Map.cs b/src/Controls/Maps/src/Map.cs
index 52875e5650ad..27a6a9a165e4 100644
--- a/src/Controls/Maps/src/Map.cs
+++ b/src/Controls/Maps/src/Map.cs
@@ -42,6 +42,10 @@ public partial class Map : View
public static readonly BindableProperty ItemTemplateSelectorProperty = BindableProperty.Create(nameof(ItemTemplateSelector), typeof(DataTemplateSelector), typeof(Map), default(DataTemplateSelector),
propertyChanged: (b, o, n) => ((Map)b).OnItemTemplateSelectorPropertyChanged());
+ /// Bindable property for .
+ public static readonly BindableProperty RegionProperty = BindableProperty.Create(nameof(Region), typeof(MapSpan), typeof(Map), null,
+ propertyChanged: (b, o, n) => ((Map)b).OnRegionPropertyChanged((MapSpan?)n));
+
readonly ObservableCollection _pins = new();
readonly ObservableCollection _mapElements = new();
MapSpan? _visibleRegion;
@@ -159,6 +163,16 @@ public DataTemplateSelector ItemTemplateSelector
set { SetValue(ItemTemplateSelectorProperty, value); }
}
+ ///
+ /// Gets or sets the region displayed by the map. Setting this property moves the map to the specified region.
+ /// This is a bindable property.
+ ///
+ public MapSpan? Region
+ {
+ get { return (MapSpan?)GetValue(RegionProperty); }
+ set { SetValue(RegionProperty, value); }
+ }
+
///
/// Gets the elements (pins, polygons, polylines, etc.) currently added to this map.
///
@@ -224,6 +238,14 @@ void SetVisibleRegion(MapSpan? visibleRegion)
OnPropertyChanged(nameof(VisibleRegion));
}
+ void OnRegionPropertyChanged(MapSpan? newRegion)
+ {
+ if (newRegion is not null)
+ {
+ MoveToRegion(newRegion);
+ }
+ }
+
void PinsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems is not null && e.NewItems.Cast().Any(pin => pin.Label is null))
diff --git a/src/Controls/Maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/Maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
index 7dc5c58110bf..31029602ec64 100644
--- a/src/Controls/Maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
+++ b/src/Controls/Maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
@@ -1 +1,4 @@
#nullable enable
+static readonly Microsoft.Maui.Controls.Maps.Map.RegionProperty -> Microsoft.Maui.Controls.BindableProperty!
+Microsoft.Maui.Controls.Maps.Map.Region.get -> Microsoft.Maui.Maps.MapSpan?
+Microsoft.Maui.Controls.Maps.Map.Region.set -> void
diff --git a/src/Controls/Maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/Maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
index 7dc5c58110bf..31029602ec64 100644
--- a/src/Controls/Maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
+++ b/src/Controls/Maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
@@ -1 +1,4 @@
#nullable enable
+static readonly Microsoft.Maui.Controls.Maps.Map.RegionProperty -> Microsoft.Maui.Controls.BindableProperty!
+Microsoft.Maui.Controls.Maps.Map.Region.get -> Microsoft.Maui.Maps.MapSpan?
+Microsoft.Maui.Controls.Maps.Map.Region.set -> void
diff --git a/src/Controls/Maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/Maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
index 7dc5c58110bf..31029602ec64 100644
--- a/src/Controls/Maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
+++ b/src/Controls/Maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
@@ -1 +1,4 @@
#nullable enable
+static readonly Microsoft.Maui.Controls.Maps.Map.RegionProperty -> Microsoft.Maui.Controls.BindableProperty!
+Microsoft.Maui.Controls.Maps.Map.Region.get -> Microsoft.Maui.Maps.MapSpan?
+Microsoft.Maui.Controls.Maps.Map.Region.set -> void
diff --git a/src/Controls/Maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/Controls/Maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
index 7dc5c58110bf..31029602ec64 100644
--- a/src/Controls/Maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
+++ b/src/Controls/Maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
@@ -1 +1,4 @@
#nullable enable
+static readonly Microsoft.Maui.Controls.Maps.Map.RegionProperty -> Microsoft.Maui.Controls.BindableProperty!
+Microsoft.Maui.Controls.Maps.Map.Region.get -> Microsoft.Maui.Maps.MapSpan?
+Microsoft.Maui.Controls.Maps.Map.Region.set -> void
diff --git a/src/Controls/Maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/Maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
index 7dc5c58110bf..31029602ec64 100644
--- a/src/Controls/Maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
+++ b/src/Controls/Maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
@@ -1 +1,4 @@
#nullable enable
+static readonly Microsoft.Maui.Controls.Maps.Map.RegionProperty -> Microsoft.Maui.Controls.BindableProperty!
+Microsoft.Maui.Controls.Maps.Map.Region.get -> Microsoft.Maui.Maps.MapSpan?
+Microsoft.Maui.Controls.Maps.Map.Region.set -> void
diff --git a/src/Controls/Maps/src/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Controls/Maps/src/PublicAPI/net/PublicAPI.Unshipped.txt
index 7dc5c58110bf..31029602ec64 100644
--- a/src/Controls/Maps/src/PublicAPI/net/PublicAPI.Unshipped.txt
+++ b/src/Controls/Maps/src/PublicAPI/net/PublicAPI.Unshipped.txt
@@ -1 +1,4 @@
#nullable enable
+static readonly Microsoft.Maui.Controls.Maps.Map.RegionProperty -> Microsoft.Maui.Controls.BindableProperty!
+Microsoft.Maui.Controls.Maps.Map.Region.get -> Microsoft.Maui.Maps.MapSpan?
+Microsoft.Maui.Controls.Maps.Map.Region.set -> void
diff --git a/src/Controls/Maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Controls/Maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
index 7dc5c58110bf..31029602ec64 100644
--- a/src/Controls/Maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
+++ b/src/Controls/Maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
@@ -1 +1,4 @@
#nullable enable
+static readonly Microsoft.Maui.Controls.Maps.Map.RegionProperty -> Microsoft.Maui.Controls.BindableProperty!
+Microsoft.Maui.Controls.Maps.Map.Region.get -> Microsoft.Maui.Maps.MapSpan?
+Microsoft.Maui.Controls.Maps.Map.Region.set -> void
diff --git a/src/Controls/tests/Core.UnitTests/MapSpanTypeConverterTests.cs b/src/Controls/tests/Core.UnitTests/MapSpanTypeConverterTests.cs
new file mode 100644
index 000000000000..5aaba1efa6f9
--- /dev/null
+++ b/src/Controls/tests/Core.UnitTests/MapSpanTypeConverterTests.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Globalization;
+using Microsoft.Maui.Devices.Sensors;
+using Microsoft.Maui.Maps;
+using Xunit;
+
+namespace Microsoft.Maui.Controls.Core.UnitTests
+{
+ public class MapSpanTypeConverterTests : BaseTestFixture
+ {
+ [Fact]
+ public void ConvertFromValidString()
+ {
+ var converter = new MapSpanTypeConverter();
+ var result = (MapSpan)converter.ConvertFrom(null, CultureInfo.InvariantCulture, "36.9628,-122.0195,0.01,0.02")!;
+
+ Assert.Equal(36.9628, result.Center.Latitude, 4);
+ Assert.Equal(-122.0195, result.Center.Longitude, 4);
+ Assert.Equal(0.01, result.LatitudeDegrees, 10);
+ Assert.Equal(0.02, result.LongitudeDegrees, 10);
+ }
+
+ [Fact]
+ public void ConvertFromWithSpaces()
+ {
+ var converter = new MapSpanTypeConverter();
+ var result = (MapSpan)converter.ConvertFrom(null, CultureInfo.InvariantCulture, " 36.9628 , -122.0195 , 0.01 , 0.02 ")!;
+
+ Assert.Equal(36.9628, result.Center.Latitude, 4);
+ Assert.Equal(-122.0195, result.Center.Longitude, 4);
+ }
+
+ [Fact]
+ public void ConvertToString()
+ {
+ var converter = new MapSpanTypeConverter();
+ var span = new MapSpan(new Location(36.9628, -122.0195), 0.01, 0.02);
+ var result = converter.ConvertTo(null, CultureInfo.InvariantCulture, span, typeof(string));
+
+ Assert.Equal("36.9628,-122.0195,0.01,0.02", result);
+ }
+
+ [Fact]
+ public void ConvertFromInvalidStringThrows()
+ {
+ var converter = new MapSpanTypeConverter();
+ Assert.Throws(() =>
+ converter.ConvertFrom(null, CultureInfo.InvariantCulture, "invalid"));
+ }
+
+ [Fact]
+ public void ConvertFromTooFewPartsThrows()
+ {
+ var converter = new MapSpanTypeConverter();
+ Assert.Throws(() =>
+ converter.ConvertFrom(null, CultureInfo.InvariantCulture, "36.9628,-122.0195"));
+ }
+
+ [Fact]
+ public void ConvertFromEmptyStringThrows()
+ {
+ var converter = new MapSpanTypeConverter();
+ Assert.Throws(() =>
+ converter.ConvertFrom(null, CultureInfo.InvariantCulture, ""));
+ }
+
+ [Fact]
+ public void CanConvertFromString()
+ {
+ var converter = new MapSpanTypeConverter();
+ Assert.True(converter.CanConvertFrom(null, typeof(string)));
+ Assert.False(converter.CanConvertFrom(null, typeof(int)));
+ }
+
+ [Fact]
+ public void RoundTrip()
+ {
+ var converter = new MapSpanTypeConverter();
+ var original = new MapSpan(new Location(47.6062, -122.3321), 0.5, 0.5);
+ var str = (string)converter.ConvertTo(null, CultureInfo.InvariantCulture, original, typeof(string))!;
+ var result = (MapSpan)converter.ConvertFrom(null, CultureInfo.InvariantCulture, str)!;
+
+ Assert.Equal(original.Center.Latitude, result.Center.Latitude, 10);
+ Assert.Equal(original.Center.Longitude, result.Center.Longitude, 10);
+ Assert.Equal(original.LatitudeDegrees, result.LatitudeDegrees, 10);
+ Assert.Equal(original.LongitudeDegrees, result.LongitudeDegrees, 10);
+ }
+ }
+}
diff --git a/src/Core/maps/src/Converters/MapSpanTypeConverter.cs b/src/Core/maps/src/Converters/MapSpanTypeConverter.cs
new file mode 100644
index 000000000000..74ff7f401e25
--- /dev/null
+++ b/src/Core/maps/src/Converters/MapSpanTypeConverter.cs
@@ -0,0 +1,59 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using Microsoft.Maui.Devices.Sensors;
+
+namespace Microsoft.Maui.Maps
+{
+ ///
+ /// A type converter that converts a string representation to a object.
+ ///
+ ///
+ /// Supported formats:
+ ///
+ /// - "latitude,longitude,latitudeDegrees,longitudeDegrees" (e.g., "36.9628,-122.0195,0.01,0.01")
+ ///
+ ///
+ public class MapSpanTypeConverter : TypeConverter
+ {
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ => sourceType == typeof(string);
+
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
+ => destinationType == typeof(string);
+
+ ///
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ var strValue = value?.ToString()?.Trim();
+
+ if (string.IsNullOrEmpty(strValue))
+ throw new InvalidOperationException($"Cannot convert \"{value}\" into {typeof(MapSpan)}");
+
+ var parts = strValue.Split(',');
+
+ if (parts.Length == 4
+ && double.TryParse(parts[0].Trim(), NumberStyles.Float | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out double latitude)
+ && double.TryParse(parts[1].Trim(), NumberStyles.Float | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out double longitude)
+ && double.TryParse(parts[2].Trim(), NumberStyles.Float | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out double latDegrees)
+ && double.TryParse(parts[3].Trim(), NumberStyles.Float | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out double lonDegrees))
+ {
+ return new MapSpan(new Location(latitude, longitude), latDegrees, lonDegrees);
+ }
+
+ throw new InvalidOperationException($"Cannot convert \"{strValue}\" into {typeof(MapSpan)}");
+ }
+
+ ///
+ public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
+ {
+ if (value is not MapSpan span)
+ throw new NotSupportedException();
+
+ return $"{span.Center.Latitude.ToString(CultureInfo.InvariantCulture)},{span.Center.Longitude.ToString(CultureInfo.InvariantCulture)}," +
+ $"{span.LatitudeDegrees.ToString(CultureInfo.InvariantCulture)},{span.LongitudeDegrees.ToString(CultureInfo.InvariantCulture)}";
+ }
+ }
+}
diff --git a/src/Core/maps/src/Primitives/MapSpan.cs b/src/Core/maps/src/Primitives/MapSpan.cs
index d5e7fe6d7d92..49ed0d191b5d 100644
--- a/src/Core/maps/src/Primitives/MapSpan.cs
+++ b/src/Core/maps/src/Primitives/MapSpan.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using Microsoft.Maui.Devices.Sensors;
namespace Microsoft.Maui.Maps
@@ -6,6 +7,7 @@ namespace Microsoft.Maui.Maps
///
/// Represents a rectangular region on the map, defined by a center point and span.
///
+ [TypeConverter(typeof(MapSpanTypeConverter))]
public sealed class MapSpan
{
const double EarthCircumferenceKm = GeographyUtils.EarthRadiusKm * 2 * Math.PI;
diff --git a/src/Core/maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Core/maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9bd77ba275b9 100644
--- a/src/Core/maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
+++ b/src/Core/maps/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Maps.MapSpanTypeConverter
+Microsoft.Maui.Maps.MapSpanTypeConverter.MapSpanTypeConverter() -> void
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type! sourceType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object?
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type! destinationType) -> object?
diff --git a/src/Core/maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Core/maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9bd77ba275b9 100644
--- a/src/Core/maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
+++ b/src/Core/maps/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Maps.MapSpanTypeConverter
+Microsoft.Maui.Maps.MapSpanTypeConverter.MapSpanTypeConverter() -> void
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type! sourceType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object?
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type! destinationType) -> object?
diff --git a/src/Core/maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Core/maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9bd77ba275b9 100644
--- a/src/Core/maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
+++ b/src/Core/maps/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Maps.MapSpanTypeConverter
+Microsoft.Maui.Maps.MapSpanTypeConverter.MapSpanTypeConverter() -> void
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type! sourceType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object?
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type! destinationType) -> object?
diff --git a/src/Core/maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/Core/maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9bd77ba275b9 100644
--- a/src/Core/maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
+++ b/src/Core/maps/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Maps.MapSpanTypeConverter
+Microsoft.Maui.Maps.MapSpanTypeConverter.MapSpanTypeConverter() -> void
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type! sourceType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object?
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type! destinationType) -> object?
diff --git a/src/Core/maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Core/maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9bd77ba275b9 100644
--- a/src/Core/maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
+++ b/src/Core/maps/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Maps.MapSpanTypeConverter
+Microsoft.Maui.Maps.MapSpanTypeConverter.MapSpanTypeConverter() -> void
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type! sourceType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object?
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type! destinationType) -> object?
diff --git a/src/Core/maps/src/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Core/maps/src/PublicAPI/net/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9bd77ba275b9 100644
--- a/src/Core/maps/src/PublicAPI/net/PublicAPI.Unshipped.txt
+++ b/src/Core/maps/src/PublicAPI/net/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Maps.MapSpanTypeConverter
+Microsoft.Maui.Maps.MapSpanTypeConverter.MapSpanTypeConverter() -> void
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type! sourceType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object?
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type! destinationType) -> object?
diff --git a/src/Core/maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Core/maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9bd77ba275b9 100644
--- a/src/Core/maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
+++ b/src/Core/maps/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Maps.MapSpanTypeConverter
+Microsoft.Maui.Maps.MapSpanTypeConverter.MapSpanTypeConverter() -> void
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type! sourceType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object! value) -> object?
+override Microsoft.Maui.Maps.MapSpanTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type! destinationType) -> object?
diff --git a/src/Essentials/src/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Essentials/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9939e6dfc2d4 100644
--- a/src/Essentials/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
+++ b/src/Essentials/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter.LocationTypeConverter() -> void
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type sourceType) -> bool
+override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object value) -> object?
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type destinationType) -> object?
diff --git a/src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9939e6dfc2d4 100644
--- a/src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
+++ b/src/Essentials/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter.LocationTypeConverter() -> void
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type sourceType) -> bool
+override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object value) -> object?
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type destinationType) -> object?
diff --git a/src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9939e6dfc2d4 100644
--- a/src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
+++ b/src/Essentials/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter.LocationTypeConverter() -> void
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type sourceType) -> bool
+override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object value) -> object?
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type destinationType) -> object?
diff --git a/src/Essentials/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/Essentials/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9939e6dfc2d4 100644
--- a/src/Essentials/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
+++ b/src/Essentials/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter.LocationTypeConverter() -> void
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type sourceType) -> bool
+override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object value) -> object?
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type destinationType) -> object?
diff --git a/src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9939e6dfc2d4 100644
--- a/src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
+++ b/src/Essentials/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter.LocationTypeConverter() -> void
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type sourceType) -> bool
+override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object value) -> object?
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type destinationType) -> object?
diff --git a/src/Essentials/src/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Essentials/src/PublicAPI/net/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9939e6dfc2d4 100644
--- a/src/Essentials/src/PublicAPI/net/PublicAPI.Unshipped.txt
+++ b/src/Essentials/src/PublicAPI/net/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter.LocationTypeConverter() -> void
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type sourceType) -> bool
+override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object value) -> object?
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type destinationType) -> object?
diff --git a/src/Essentials/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Essentials/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
index 7dc5c58110bf..9939e6dfc2d4 100644
--- a/src/Essentials/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
+++ b/src/Essentials/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter
+Microsoft.Maui.Devices.Sensors.LocationTypeConverter.LocationTypeConverter() -> void
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Type sourceType) -> bool
+override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.CanConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Type? destinationType) -> bool
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertFrom(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object value) -> object?
+~override Microsoft.Maui.Devices.Sensors.LocationTypeConverter.ConvertTo(System.ComponentModel.ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object? value, System.Type destinationType) -> object?
diff --git a/src/Essentials/src/Types/Location.shared.cs b/src/Essentials/src/Types/Location.shared.cs
index 892cf582d4f3..98df2068b11e 100644
--- a/src/Essentials/src/Types/Location.shared.cs
+++ b/src/Essentials/src/Types/Location.shared.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using Microsoft.Maui.Media;
namespace Microsoft.Maui.Devices.Sensors
@@ -37,6 +38,7 @@ public enum AltitudeReferenceSystem
///
/// Represents a physical location with the latitude, longitude, altitude and time information reported by the device.
///
+ [TypeConverter(typeof(LocationTypeConverter))]
public class Location
{
///
diff --git a/src/Essentials/src/Types/LocationTypeConverter.shared.cs b/src/Essentials/src/Types/LocationTypeConverter.shared.cs
new file mode 100644
index 000000000000..f212c4354781
--- /dev/null
+++ b/src/Essentials/src/Types/LocationTypeConverter.shared.cs
@@ -0,0 +1,55 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Microsoft.Maui.Devices.Sensors
+{
+ ///
+ /// A type converter that converts a string representation of latitude and longitude to a object.
+ ///
+ ///
+ /// Supported formats:
+ ///
+ /// - "latitude,longitude" (e.g., "36.9628066,-122.0194722")
+ ///
+ ///
+ public class LocationTypeConverter : TypeConverter
+ {
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ => sourceType == typeof(string);
+
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
+ => destinationType == typeof(string);
+
+ ///
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ var strValue = value?.ToString()?.Trim();
+
+ if (string.IsNullOrEmpty(strValue))
+ throw new InvalidOperationException($"Cannot convert \"{value}\" into {typeof(Location)}");
+
+ var parts = strValue.Split(',');
+
+ if (parts.Length == 2
+ && double.TryParse(parts[0].Trim(), NumberStyles.Float | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out double latitude)
+ && double.TryParse(parts[1].Trim(), NumberStyles.Float | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out double longitude))
+ {
+ return new Location(latitude, longitude);
+ }
+
+ throw new InvalidOperationException($"Cannot convert \"{strValue}\" into {typeof(Location)}");
+ }
+
+ ///
+ public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
+ {
+ if (value is not Location location)
+ throw new NotSupportedException();
+
+ return $"{location.Latitude.ToString(CultureInfo.InvariantCulture)},{location.Longitude.ToString(CultureInfo.InvariantCulture)}";
+ }
+ }
+}
diff --git a/src/Essentials/test/UnitTests/LocationTypeConverter_Tests.cs b/src/Essentials/test/UnitTests/LocationTypeConverter_Tests.cs
new file mode 100644
index 000000000000..f37b0c88a49b
--- /dev/null
+++ b/src/Essentials/test/UnitTests/LocationTypeConverter_Tests.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Globalization;
+using Microsoft.Maui.Devices.Sensors;
+using Xunit;
+
+namespace Tests
+{
+ public class LocationTypeConverter_Tests
+ {
+ [Fact]
+ public void ConvertFromValidString()
+ {
+ var converter = new LocationTypeConverter();
+ var result = (Location)converter.ConvertFrom(null, CultureInfo.InvariantCulture, "36.9628066,-122.0194722")!;
+
+ Assert.Equal(36.9628066, result.Latitude, 7);
+ Assert.Equal(-122.0194722, result.Longitude, 7);
+ }
+
+ [Fact]
+ public void ConvertFromWithSpaces()
+ {
+ var converter = new LocationTypeConverter();
+ var result = (Location)converter.ConvertFrom(null, CultureInfo.InvariantCulture, " 36.9628066 , -122.0194722 ")!;
+
+ Assert.Equal(36.9628066, result.Latitude, 7);
+ Assert.Equal(-122.0194722, result.Longitude, 7);
+ }
+
+ [Fact]
+ public void ConvertFromNegativeValues()
+ {
+ var converter = new LocationTypeConverter();
+ var result = (Location)converter.ConvertFrom(null, CultureInfo.InvariantCulture, "-33.8688,151.2093")!;
+
+ Assert.Equal(-33.8688, result.Latitude, 4);
+ Assert.Equal(151.2093, result.Longitude, 4);
+ }
+
+ [Fact]
+ public void ConvertFromZero()
+ {
+ var converter = new LocationTypeConverter();
+ var result = (Location)converter.ConvertFrom(null, CultureInfo.InvariantCulture, "0,0")!;
+
+ Assert.Equal(0, result.Latitude);
+ Assert.Equal(0, result.Longitude);
+ }
+
+ [Fact]
+ public void ConvertToString()
+ {
+ var converter = new LocationTypeConverter();
+ var location = new Location(36.9628066, -122.0194722);
+ var result = converter.ConvertTo(null, CultureInfo.InvariantCulture, location, typeof(string));
+
+ Assert.Equal("36.9628066,-122.0194722", result);
+ }
+
+ [Fact]
+ public void ConvertFromInvalidStringThrows()
+ {
+ var converter = new LocationTypeConverter();
+ Assert.Throws(() =>
+ converter.ConvertFrom(null, CultureInfo.InvariantCulture, "invalid"));
+ }
+
+ [Fact]
+ public void ConvertFromEmptyStringThrows()
+ {
+ var converter = new LocationTypeConverter();
+ Assert.Throws(() =>
+ converter.ConvertFrom(null, CultureInfo.InvariantCulture, ""));
+ }
+
+ [Fact]
+ public void ConvertFromTooManyPartsThrows()
+ {
+ var converter = new LocationTypeConverter();
+ Assert.Throws(() =>
+ converter.ConvertFrom(null, CultureInfo.InvariantCulture, "1,2,3"));
+ }
+
+ [Fact]
+ public void CanConvertFromString()
+ {
+ var converter = new LocationTypeConverter();
+ Assert.True(converter.CanConvertFrom(null, typeof(string)));
+ Assert.False(converter.CanConvertFrom(null, typeof(int)));
+ }
+
+ [Fact]
+ public void CanConvertToString()
+ {
+ var converter = new LocationTypeConverter();
+ Assert.True(converter.CanConvertTo(null, typeof(string)));
+ Assert.False(converter.CanConvertTo(null, typeof(int)));
+ }
+
+ [Fact]
+ public void RoundTrip()
+ {
+ var converter = new LocationTypeConverter();
+ var original = new Location(47.6062, -122.3321);
+ var str = (string)converter.ConvertTo(null, CultureInfo.InvariantCulture, original, typeof(string))!;
+ var result = (Location)converter.ConvertFrom(null, CultureInfo.InvariantCulture, str)!;
+
+ Assert.Equal(original.Latitude, result.Latitude, 10);
+ Assert.Equal(original.Longitude, result.Longitude, 10);
+ }
+ }
+}