From 17fdde66d94155fc62a034fa6658995bef6fd6e5 Mon Sep 17 00:00:00 2001 From: Jason Smith Date: Tue, 22 Mar 2016 13:02:25 -0700 Subject: Initial import --- Xamarin.Forms.Maps.UWP/FormsMaps.cs | 32 +++ Xamarin.Forms.Maps.UWP/GeocoderBackend.cs | 86 ++++++ Xamarin.Forms.Maps.UWP/MapRenderer.cs | 297 +++++++++++++++++++++ Xamarin.Forms.Maps.UWP/Properties/AssemblyInfo.cs | 29 ++ .../Properties/Xamarin.Forms.Maps.UWP.rd.xml | 33 +++ Xamarin.Forms.Maps.UWP/PushPin.cs | 70 +++++ .../Xamarin.Forms.Maps.UWP.csproj | 142 ++++++++++ Xamarin.Forms.Maps.UWP/project.json | 16 ++ 8 files changed, 705 insertions(+) create mode 100644 Xamarin.Forms.Maps.UWP/FormsMaps.cs create mode 100644 Xamarin.Forms.Maps.UWP/GeocoderBackend.cs create mode 100644 Xamarin.Forms.Maps.UWP/MapRenderer.cs create mode 100644 Xamarin.Forms.Maps.UWP/Properties/AssemblyInfo.cs create mode 100644 Xamarin.Forms.Maps.UWP/Properties/Xamarin.Forms.Maps.UWP.rd.xml create mode 100644 Xamarin.Forms.Maps.UWP/PushPin.cs create mode 100644 Xamarin.Forms.Maps.UWP/Xamarin.Forms.Maps.UWP.csproj create mode 100644 Xamarin.Forms.Maps.UWP/project.json (limited to 'Xamarin.Forms.Maps.UWP') diff --git a/Xamarin.Forms.Maps.UWP/FormsMaps.cs b/Xamarin.Forms.Maps.UWP/FormsMaps.cs new file mode 100644 index 00000000..ee631355 --- /dev/null +++ b/Xamarin.Forms.Maps.UWP/FormsMaps.cs @@ -0,0 +1,32 @@ +#if WINDOWS_UWP +using Xamarin.Forms.Maps.UWP; + +#else +using Xamarin.Forms.Maps.WinRT; + +#endif + +namespace Xamarin +{ + public static class FormsMaps + { + static bool s_isInitialized; + + internal static string AuthenticationToken { get; set; } + + public static void Init() + { + if (s_isInitialized) + return; + GeocoderBackend.Register(); + s_isInitialized = true; + } + + public static void Init(string authenticationToken) + { + AuthenticationToken = authenticationToken; + + Init(); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Maps.UWP/GeocoderBackend.cs b/Xamarin.Forms.Maps.UWP/GeocoderBackend.cs new file mode 100644 index 00000000..10f624a6 --- /dev/null +++ b/Xamarin.Forms.Maps.UWP/GeocoderBackend.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Windows.Devices.Geolocation; +using Windows.Services.Maps; + +#if WINDOWS_UWP + +namespace Xamarin.Forms.Maps.UWP +#else + +namespace Xamarin.Forms.Maps.WinRT +#endif +{ + internal class GeocoderBackend + { + public static void Register() + { + Geocoder.GetPositionsForAddressAsyncFunc = GetPositionsForAddress; + Geocoder.GetAddressesForPositionFuncAsync = GetAddressesForPositionAsync; + } + + static string AddressToString(MapAddress address) + { + string building = "", house = "", city = "", country = ""; + + var bldg = new List(); + if (!"".Equals(address.BuildingRoom)) + bldg.Add(address.BuildingRoom); + if (!"".Equals(address.BuildingFloor)) + bldg.Add(address.BuildingFloor); + if (!"".Equals(address.BuildingName)) + bldg.Add(address.BuildingName); + if (!"".Equals(address.BuildingWing)) + bldg.Add(address.BuildingWing); + if (bldg.Count > 0) + building = string.Join(" ", bldg) + Environment.NewLine; + + var hs = new List(); + if (!"".Equals(address.StreetNumber)) + hs.Add(address.StreetNumber); + if (!"".Equals(address.Street)) + hs.Add(address.Street); + if (hs.Count > 0) + house = string.Join(" ", hs) + Environment.NewLine; + + var cs = new List(); + if (!"".Equals(address.Town)) + cs.Add(address.Town); + if (!"".Equals(address.Neighborhood)) + cs.Add(address.Neighborhood); + else if (!"".Equals(address.Region)) + cs.Add(address.Region); + if (!"".Equals(address.PostCode)) + cs.Add(address.PostCode); + if (cs.Count > 0) + city = string.Join(" ", cs) + Environment.NewLine; + + if (!"".Equals(address.Country)) + country = address.Country; + return building + house + city + country; + } + + static async Task> GetAddressesForPositionAsync(Position position) + { + var queryResults = + await + MapLocationFinder.FindLocationsAtAsync( + new Geopoint(new BasicGeoposition { Latitude = position.Latitude, Longitude = position.Longitude })); + var addresses = new List(); + foreach (var result in queryResults?.Locations) + addresses.Add(AddressToString(result.Address)); + + return addresses; + } + + static async Task> GetPositionsForAddress(string address) + { + var queryResults = await MapLocationFinder.FindLocationsAsync(address, null, 10); + var positions = new List(); + foreach (var result in queryResults?.Locations) + positions.Add(new Position(result.Point.Position.Latitude, result.Point.Position.Longitude)); + return positions; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Maps.UWP/MapRenderer.cs b/Xamarin.Forms.Maps.UWP/MapRenderer.cs new file mode 100644 index 00000000..4d3564e7 --- /dev/null +++ b/Xamarin.Forms.Maps.UWP/MapRenderer.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using Windows.Devices.Geolocation; +using Windows.UI; +using Windows.UI.Xaml.Controls.Maps; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Shapes; +#if WINDOWS_UWP +using Xamarin.Forms.Platform.UWP; + +#else +using Xamarin.Forms.Platform.WinRT; + +#endif + +#if WINDOWS_UWP + +namespace Xamarin.Forms.Maps.UWP +#else + +namespace Xamarin.Forms.Maps.WinRT +#endif +{ + public class MapRenderer : ViewRenderer + { + protected override async void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e.OldElement != null) + { + var mapModel = e.OldElement; + MessagingCenter.Unsubscribe(this, "MapMoveToRegion"); + ((ObservableCollection)mapModel.Pins).CollectionChanged -= OnCollectionChanged; + } + + if (e.NewElement != null) + { + var mapModel = e.NewElement; + + if (Control == null) + { + SetNativeControl(new MapControl()); + Control.MapServiceToken = FormsMaps.AuthenticationToken; + Control.ZoomLevelChanged += async (s, a) => await UpdateVisibleRegion(); + Control.CenterChanged += async (s, a) => await UpdateVisibleRegion(); + } + + MessagingCenter.Subscribe(this, "MapMoveToRegion", async (s, a) => await MoveToRegion(a), mapModel); + + UpdateMapType(); + UpdateHasScrollEnabled(); + UpdateHasZoomEnabled(); + + ((ObservableCollection)mapModel.Pins).CollectionChanged += OnCollectionChanged; + + if (mapModel.Pins.Any()) + LoadPins(); + + await UpdateIsShowingUser(); + } + } + + protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Map.MapTypeProperty.PropertyName) + UpdateMapType(); + else if (e.PropertyName == Map.IsShowingUserProperty.PropertyName) + await UpdateIsShowingUser(); + else if (e.PropertyName == Map.HasScrollEnabledProperty.PropertyName) + UpdateHasScrollEnabled(); + else if (e.PropertyName == Map.HasZoomEnabledProperty.PropertyName) + UpdateHasZoomEnabled(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + _disposed = true; + + MessagingCenter.Unsubscribe(this, "MapMoveToRegion"); + + if (Element != null) + ((ObservableCollection)Element.Pins).CollectionChanged -= OnCollectionChanged; + } + base.Dispose(disposing); + } + + bool _disposed; + bool _firstZoomLevelChangeFired; + Ellipse _userPositionCircle; + + void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (Pin pin in e.NewItems) + LoadPin(pin); + break; + case NotifyCollectionChangedAction.Move: + // no matter + break; + case NotifyCollectionChangedAction.Remove: + foreach (Pin pin in e.OldItems) + RemovePin(pin); + break; + case NotifyCollectionChangedAction.Replace: + foreach (Pin pin in e.OldItems) + RemovePin(pin); + foreach (Pin pin in e.NewItems) + LoadPin(pin); + break; + case NotifyCollectionChangedAction.Reset: + ClearPins(); + break; + } + } + + void LoadPins() + { + foreach (var pin in Element.Pins) + LoadPin(pin); + } + + void ClearPins() + { + Control.Children.Clear(); + UpdateIsShowingUser(); + } + + void RemovePin(Pin pinToRemove) + { + var pushPin = Control.Children.FirstOrDefault(c => + { + var pin = (c as PushPin); + return (pin != null && pin.DataContext.Equals(pinToRemove)); + }); + + if (pushPin != null) + Control.Children.Remove(pushPin); + } + + void LoadPin(Pin pin) + { + Control.Children.Add(new PushPin(pin)); + } + + async Task UpdateIsShowingUser() + { + if (Element.IsShowingUser) + { + var myGeolocator = new Geolocator(); + if (myGeolocator.LocationStatus != PositionStatus.NotAvailable && + myGeolocator.LocationStatus != PositionStatus.Disabled) + { + var userPosition = await myGeolocator.GetGeopositionAsync(); + if (userPosition?.Coordinate != null) + LoadUserPosition(userPosition.Coordinate, true); + } + } + else if (_userPositionCircle != null && Control.Children.Contains(_userPositionCircle)) + Control.Children.Remove(_userPositionCircle); + } + + async Task MoveToRegion(MapSpan span, MapAnimationKind animation = MapAnimationKind.Bow) + { + var nw = new BasicGeoposition + { + Latitude = span.Center.Latitude + span.LatitudeDegrees / 2, + Longitude = span.Center.Longitude - span.LongitudeDegrees / 2 + }; + var se = new BasicGeoposition + { + Latitude = span.Center.Latitude - span.LatitudeDegrees / 2, + Longitude = span.Center.Longitude + span.LongitudeDegrees / 2 + }; + var boundingBox = new GeoboundingBox(nw, se); + await Control.TrySetViewBoundsAsync(boundingBox, null, animation); + } + + async Task UpdateVisibleRegion() + { + if (Control == null || Element == null) + return; + + if (!_firstZoomLevelChangeFired) + { + await MoveToRegion(Element.LastMoveToRegion, MapAnimationKind.None); + _firstZoomLevelChangeFired = true; + return; + } + Geopoint nw, se = null; + try + { + Control.GetLocationFromOffset(new Windows.Foundation.Point(0, 0), out nw); + Control.GetLocationFromOffset(new Windows.Foundation.Point(Control.ActualWidth, Control.ActualHeight), out se); + } + catch (Exception) + { + return; + } + + if (nw != null && se != null) + { + var boundingBox = new GeoboundingBox(nw.Position, se.Position); + var center = new Position(boundingBox.Center.Latitude, boundingBox.Center.Longitude); + var latitudeDelta = Math.Abs(center.Latitude - boundingBox.NorthwestCorner.Latitude); + var longitudeDelta = Math.Abs(center.Longitude - boundingBox.NorthwestCorner.Longitude); + Element.VisibleRegion = new MapSpan(center, latitudeDelta, longitudeDelta); + } + } + + void LoadUserPosition(Geocoordinate userCoordinate, bool center) + { + var userPosition = new BasicGeoposition + { + Latitude = userCoordinate.Point.Position.Latitude, + Longitude = userCoordinate.Point.Position.Longitude + }; + + var point = new Geopoint(userPosition); + + if (_userPositionCircle == null) + { + _userPositionCircle = new Ellipse + { + Stroke = new SolidColorBrush(Colors.White), + Fill = new SolidColorBrush(Colors.Blue), + StrokeThickness = 2, + Height = 20, + Width = 20, + Opacity = 50 + }; + } + + if (Control.Children.Contains(_userPositionCircle)) + Control.Children.Remove(_userPositionCircle); + + MapControl.SetLocation(_userPositionCircle, point); + MapControl.SetNormalizedAnchorPoint(_userPositionCircle, new Windows.Foundation.Point(0.5, 0.5)); + + Control.Children.Add(_userPositionCircle); + + if (center) + { + Control.Center = point; + Control.ZoomLevel = 13; + } + } + + void UpdateMapType() + { + switch (Element.MapType) + { + case MapType.Street: + Control.Style = MapStyle.Road; + break; + case MapType.Satellite: + Control.Style = MapStyle.Aerial; + break; + case MapType.Hybrid: + Control.Style = MapStyle.AerialWithRoads; + break; + } + } + +#if WINDOWS_UWP + void UpdateHasZoomEnabled() + { + Control.ZoomInteractionMode = Element.HasZoomEnabled + ? MapInteractionMode.GestureAndControl + : MapInteractionMode.ControlOnly; + } + + void UpdateHasScrollEnabled() + { + Control.PanInteractionMode = Element.HasScrollEnabled ? MapPanInteractionMode.Auto : MapPanInteractionMode.Disabled; + } +#else + void UpdateHasZoomEnabled() + { + } + + void UpdateHasScrollEnabled() + { + } +#endif + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Maps.UWP/Properties/AssemblyInfo.cs b/Xamarin.Forms.Maps.UWP/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..5d78702b --- /dev/null +++ b/Xamarin.Forms.Maps.UWP/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using Xamarin.Forms.Maps; +using Xamarin.Forms.Maps.UWP; +using Xamarin.Forms.Platform.UWP; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("Xamarin.Forms.Maps.WindowsUniversal")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] +[assembly: ExportRenderer(typeof (Map), typeof (MapRenderer))] \ No newline at end of file diff --git a/Xamarin.Forms.Maps.UWP/Properties/Xamarin.Forms.Maps.UWP.rd.xml b/Xamarin.Forms.Maps.UWP/Properties/Xamarin.Forms.Maps.UWP.rd.xml new file mode 100644 index 00000000..275268e6 --- /dev/null +++ b/Xamarin.Forms.Maps.UWP/Properties/Xamarin.Forms.Maps.UWP.rd.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/Xamarin.Forms.Maps.UWP/PushPin.cs b/Xamarin.Forms.Maps.UWP/PushPin.cs new file mode 100644 index 00000000..2eaaa658 --- /dev/null +++ b/Xamarin.Forms.Maps.UWP/PushPin.cs @@ -0,0 +1,70 @@ +using System; +using System.ComponentModel; +using Windows.Devices.Geolocation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Maps; +using Windows.UI.Xaml.Input; + +#if WINDOWS_UWP + +namespace Xamarin.Forms.Maps.UWP +#else + +namespace Xamarin.Forms.Maps.WinRT +#endif +{ + internal class PushPin : ContentControl + { + readonly Pin _pin; + + internal PushPin(Pin pin) + { + if (pin == null) + throw new ArgumentNullException(); + + ContentTemplate = Windows.UI.Xaml.Application.Current.Resources["pushPinTemplate"] as Windows.UI.Xaml.DataTemplate; + DataContext = Content = _pin = pin; + + UpdateLocation(); + + Loaded += PushPinLoaded; + Unloaded += PushPinUnloaded; + Tapped += PushPinTapped; + } + + void PushPinLoaded(object sender, RoutedEventArgs e) + { + _pin.PropertyChanged += PinPropertyChanged; + } + + void PushPinUnloaded(object sender, RoutedEventArgs e) + { + _pin.PropertyChanged -= PinPropertyChanged; + Tapped -= PushPinTapped; + } + + void PinPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Pin.PositionProperty.PropertyName) + UpdateLocation(); + } + + void PushPinTapped(object sender, TappedRoutedEventArgs e) + { + _pin.SendTap(); + } + + void UpdateLocation() + { + var anchor = new Windows.Foundation.Point(0.65, 1); + var location = new Geopoint(new BasicGeoposition + { + Latitude = _pin.Position.Latitude, + Longitude = _pin.Position.Longitude + }); + MapControl.SetLocation(this, location); + MapControl.SetNormalizedAnchorPoint(this, anchor); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Maps.UWP/Xamarin.Forms.Maps.UWP.csproj b/Xamarin.Forms.Maps.UWP/Xamarin.Forms.Maps.UWP.csproj new file mode 100644 index 00000000..295a95fe --- /dev/null +++ b/Xamarin.Forms.Maps.UWP/Xamarin.Forms.Maps.UWP.csproj @@ -0,0 +1,142 @@ + + + + + Debug + AnyCPU + {04D89A60-78EF-4A32-AE17-87E47E0233A5} + Library + Properties + Xamarin.Forms.Maps.UWP + Xamarin.Forms.Maps.UWP + en-US + UAP + 10.0.10240.0 + 10.0.10240.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + x86 + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + + + x86 + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + + + ARM + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + + + ARM + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + + + x64 + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + + + x64 + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + + + + + + + + + + + + + + + + {57b8b73d-c3b5-4c42-869e-7b2f17d354ac} + Xamarin.Forms.Core + + + {7d13bac2-c6a4-416a-b07e-c169b199e52b} + Xamarin.Forms.Maps + + + {00d8d049-ffaa-4759-8fc9-1eca30777f72} + Xamarin.Forms.Platform.UAP + + + + 14.0 + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Maps.UWP/project.json b/Xamarin.Forms.Maps.UWP/project.json new file mode 100644 index 00000000..c5949392 --- /dev/null +++ b/Xamarin.Forms.Maps.UWP/project.json @@ -0,0 +1,16 @@ +{ + "dependencies": { + "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0" + }, + "frameworks": { + "uap10.0": {} + }, + "runtimes": { + "win10-arm": {}, + "win10-arm-aot": {}, + "win10-x86": {}, + "win10-x86-aot": {}, + "win10-x64": {}, + "win10-x64-aot": {} + } +} \ No newline at end of file -- cgit v1.2.3