2016-03-22 23:02:25 +03:00
|
|
|
using System;
|
2016-09-16 21:55:38 +03:00
|
|
|
using System.Collections;
|
2016-03-22 23:02:25 +03:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
using System.Collections.Specialized;
|
|
|
|
using System.ComponentModel;
|
|
|
|
using System.Linq;
|
|
|
|
using Android.Gms.Maps;
|
|
|
|
using Android.Gms.Maps.Model;
|
|
|
|
using Android.OS;
|
|
|
|
using Java.Lang;
|
2017-03-07 22:56:24 +03:00
|
|
|
using Xamarin.Forms.Internals;
|
2016-03-22 23:02:25 +03:00
|
|
|
using Xamarin.Forms.Platform.Android;
|
|
|
|
using Math = System.Math;
|
|
|
|
|
|
|
|
namespace Xamarin.Forms.Maps.Android
|
|
|
|
{
|
2016-09-16 21:55:38 +03:00
|
|
|
public class MapRenderer : ViewRenderer<Map, MapView>,
|
2017-03-21 13:38:36 +03:00
|
|
|
GoogleMap.IOnCameraChangeListener, IOnMapReadyCallback
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-09-16 21:55:38 +03:00
|
|
|
const string MoveMessageName = "MapMoveToRegion";
|
2016-03-22 23:02:25 +03:00
|
|
|
|
|
|
|
static Bundle s_bundle;
|
2016-09-16 21:55:38 +03:00
|
|
|
|
|
|
|
bool _disposed;
|
|
|
|
|
|
|
|
bool _init = true;
|
2016-03-22 23:02:25 +03:00
|
|
|
|
|
|
|
List<Marker> _markers;
|
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
public MapRenderer()
|
|
|
|
{
|
|
|
|
AutoPackage = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected Map Map => Element;
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2017-03-21 13:38:36 +03:00
|
|
|
protected GoogleMap NativeMap;
|
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
internal static Bundle Bundle
|
|
|
|
{
|
|
|
|
set { s_bundle = value; }
|
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
public void OnCameraChange(CameraPosition pos)
|
|
|
|
{
|
|
|
|
UpdateVisibleRegion(pos.Target);
|
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
public override SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-09-16 21:55:38 +03:00
|
|
|
return new SizeRequest(new Size(Context.ToPixels(40), Context.ToPixels(40)));
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
|
|
|
|
2016-08-30 20:37:07 +03:00
|
|
|
protected override MapView CreateNativeControl()
|
|
|
|
{
|
|
|
|
return new MapView(Context);
|
|
|
|
}
|
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
protected override void Dispose(bool disposing)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-09-27 13:51:22 +03:00
|
|
|
if (_disposed)
|
2016-09-16 21:55:38 +03:00
|
|
|
{
|
2016-09-27 13:51:22 +03:00
|
|
|
return;
|
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-09-27 13:51:22 +03:00
|
|
|
_disposed = true;
|
|
|
|
|
|
|
|
if (disposing)
|
|
|
|
{
|
|
|
|
if (Element != null)
|
2016-09-16 21:55:38 +03:00
|
|
|
{
|
|
|
|
MessagingCenter.Unsubscribe<Map, MapSpan>(this, MoveMessageName);
|
2016-09-27 13:51:22 +03:00
|
|
|
((ObservableCollection<Pin>)Element.Pins).CollectionChanged -= OnCollectionChanged;
|
2016-09-16 21:55:38 +03:00
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-09-27 13:51:22 +03:00
|
|
|
if (NativeMap != null)
|
|
|
|
{
|
|
|
|
NativeMap.MyLocationEnabled = false;
|
|
|
|
NativeMap.SetOnCameraChangeListener(null);
|
|
|
|
NativeMap.InfoWindowClick -= MapOnMarkerClick;
|
|
|
|
NativeMap.Dispose();
|
2017-03-21 13:38:36 +03:00
|
|
|
NativeMap = null;
|
|
|
|
}
|
2016-09-27 13:51:22 +03:00
|
|
|
|
|
|
|
Control?.OnDestroy();
|
2016-09-16 21:55:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
base.Dispose(disposing);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
|
|
|
|
{
|
|
|
|
base.OnElementChanged(e);
|
|
|
|
|
|
|
|
MapView oldMapView = Control;
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
MapView mapView = CreateNativeControl();
|
|
|
|
mapView.OnCreate(s_bundle);
|
|
|
|
mapView.OnResume();
|
|
|
|
SetNativeControl(mapView);
|
|
|
|
|
|
|
|
if (e.OldElement != null)
|
|
|
|
{
|
|
|
|
Map oldMapModel = e.OldElement;
|
2016-03-22 23:02:25 +03:00
|
|
|
((ObservableCollection<Pin>)oldMapModel.Pins).CollectionChanged -= OnCollectionChanged;
|
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
MessagingCenter.Unsubscribe<Map, MapSpan>(this, MoveMessageName);
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2017-03-21 13:38:36 +03:00
|
|
|
if (NativeMap != null)
|
2016-09-16 21:55:38 +03:00
|
|
|
{
|
2017-03-21 13:38:36 +03:00
|
|
|
NativeMap.SetOnCameraChangeListener(null);
|
2016-03-22 23:02:25 +03:00
|
|
|
NativeMap.InfoWindowClick -= MapOnMarkerClick;
|
2017-03-21 13:38:36 +03:00
|
|
|
NativeMap = null;
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
oldMapView.Dispose();
|
|
|
|
}
|
|
|
|
|
2017-03-21 13:38:36 +03:00
|
|
|
Control.GetMapAsync(this);
|
2016-09-16 21:55:38 +03:00
|
|
|
|
|
|
|
MessagingCenter.Subscribe<Map, MapSpan>(this, MoveMessageName, OnMoveToRegionMessage, Map);
|
2016-03-22 23:02:25 +03:00
|
|
|
|
|
|
|
var incc = Map.Pins as INotifyCollectionChanged;
|
|
|
|
if (incc != null)
|
2016-09-16 21:55:38 +03:00
|
|
|
{
|
2016-03-22 23:02:25 +03:00
|
|
|
incc.CollectionChanged += OnCollectionChanged;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-09-16 21:55:38 +03:00
|
|
|
base.OnElementPropertyChanged(sender, e);
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
if (e.PropertyName == Map.MapTypeProperty.PropertyName)
|
|
|
|
{
|
2016-03-22 23:02:25 +03:00
|
|
|
SetMapType();
|
|
|
|
return;
|
|
|
|
}
|
2016-09-16 21:55:38 +03:00
|
|
|
|
|
|
|
GoogleMap gmap = NativeMap;
|
2016-03-22 23:02:25 +03:00
|
|
|
if (gmap == null)
|
2016-09-16 21:55:38 +03:00
|
|
|
{
|
2016-03-22 23:02:25 +03:00
|
|
|
return;
|
2016-09-16 21:55:38 +03:00
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
|
|
|
|
if (e.PropertyName == Map.IsShowingUserProperty.PropertyName)
|
2016-09-16 21:55:38 +03:00
|
|
|
{
|
2016-03-22 23:02:25 +03:00
|
|
|
gmap.MyLocationEnabled = gmap.UiSettings.MyLocationButtonEnabled = Map.IsShowingUser;
|
2016-09-16 21:55:38 +03:00
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
else if (e.PropertyName == Map.HasScrollEnabledProperty.PropertyName)
|
2016-09-16 21:55:38 +03:00
|
|
|
{
|
2016-03-22 23:02:25 +03:00
|
|
|
gmap.UiSettings.ScrollGesturesEnabled = Map.HasScrollEnabled;
|
2016-09-16 21:55:38 +03:00
|
|
|
}
|
|
|
|
else if (e.PropertyName == Map.HasZoomEnabledProperty.PropertyName)
|
|
|
|
{
|
2016-03-22 23:02:25 +03:00
|
|
|
gmap.UiSettings.ZoomControlsEnabled = Map.HasZoomEnabled;
|
|
|
|
gmap.UiSettings.ZoomGesturesEnabled = Map.HasZoomEnabled;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
protected override void OnLayout(bool changed, int l, int t, int r, int b)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-09-16 21:55:38 +03:00
|
|
|
base.OnLayout(changed, l, t, r, b);
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
if (_init)
|
|
|
|
{
|
2017-03-21 13:38:36 +03:00
|
|
|
if (NativeMap != null)
|
|
|
|
{
|
|
|
|
MoveToRegion(Element.LastMoveToRegion, false);
|
|
|
|
OnCollectionChanged(Element.Pins, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
|
|
|
_init = false;
|
|
|
|
}
|
2016-09-16 21:55:38 +03:00
|
|
|
}
|
|
|
|
else if (changed)
|
|
|
|
{
|
2017-03-21 13:38:36 +03:00
|
|
|
if (NativeMap != null)
|
|
|
|
{
|
|
|
|
UpdateVisibleRegion(NativeMap.CameraPosition.Target);
|
|
|
|
}
|
2017-01-03 14:35:54 +03:00
|
|
|
MoveToRegion(Element.LastMoveToRegion, false);
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
|
|
|
}
|
2017-04-12 01:52:04 +03:00
|
|
|
|
|
|
|
protected virtual void OnMapReady(GoogleMap map)
|
|
|
|
{
|
|
|
|
if (map == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
map.SetOnCameraChangeListener(this);
|
|
|
|
map.InfoWindowClick += MapOnMarkerClick;
|
|
|
|
|
|
|
|
map.UiSettings.ZoomControlsEnabled = Map.HasZoomEnabled;
|
|
|
|
map.UiSettings.ZoomGesturesEnabled = Map.HasZoomEnabled;
|
|
|
|
map.UiSettings.ScrollGesturesEnabled = Map.HasScrollEnabled;
|
|
|
|
map.MyLocationEnabled = map.UiSettings.MyLocationButtonEnabled = Map.IsShowingUser;
|
|
|
|
SetMapType();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual MarkerOptions CreateMarker(Pin pin)
|
|
|
|
{
|
|
|
|
var opts = new MarkerOptions();
|
|
|
|
opts.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
|
|
|
|
opts.SetTitle(pin.Label);
|
|
|
|
opts.SetSnippet(pin.Address);
|
|
|
|
|
|
|
|
return opts;
|
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
void AddPins(IList pins)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-09-16 21:55:38 +03:00
|
|
|
GoogleMap map = NativeMap;
|
2016-03-22 23:02:25 +03:00
|
|
|
if (map == null)
|
2016-09-16 21:55:38 +03:00
|
|
|
{
|
2016-03-22 23:02:25 +03:00
|
|
|
return;
|
2016-09-16 21:55:38 +03:00
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
|
|
|
|
if (_markers == null)
|
2016-09-16 21:55:38 +03:00
|
|
|
{
|
|
|
|
_markers = new List<Marker>();
|
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
_markers.AddRange(pins.Cast<Pin>().Select(p =>
|
|
|
|
{
|
|
|
|
Pin pin = p;
|
2017-04-12 01:52:04 +03:00
|
|
|
var opts = CreateMarker(pin);
|
2016-09-16 21:55:38 +03:00
|
|
|
var marker = map.AddMarker(opts);
|
2016-03-22 23:02:25 +03:00
|
|
|
|
|
|
|
// associate pin with marker for later lookup in event handlers
|
|
|
|
pin.Id = marker.Id;
|
|
|
|
return marker;
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
void MapOnMarkerClick(object sender, GoogleMap.InfoWindowClickEventArgs eventArgs)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
|
|
|
// clicked marker
|
|
|
|
var marker = eventArgs.Marker;
|
|
|
|
|
|
|
|
// lookup pin
|
|
|
|
Pin targetPin = null;
|
2016-09-16 21:55:38 +03:00
|
|
|
for (var i = 0; i < Map.Pins.Count; i++)
|
|
|
|
{
|
|
|
|
Pin pin = Map.Pins[i];
|
2016-03-22 23:02:25 +03:00
|
|
|
if ((string)pin.Id != marker.Id)
|
2016-09-16 21:55:38 +03:00
|
|
|
{
|
2016-03-22 23:02:25 +03:00
|
|
|
continue;
|
2016-09-16 21:55:38 +03:00
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
|
|
|
|
targetPin = pin;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// only consider event handled if a handler is present.
|
|
|
|
// Else allow default behavior of displaying an info window.
|
2016-09-16 21:55:38 +03:00
|
|
|
targetPin?.SendTap();
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
void MoveToRegion(MapSpan span, bool animate)
|
2016-03-22 23:02:25 +03:00
|
|
|
{
|
2016-09-16 21:55:38 +03:00
|
|
|
GoogleMap map = NativeMap;
|
|
|
|
if (map == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
span = span.ClampLatitude(85, -85);
|
|
|
|
var ne = new LatLng(span.Center.Latitude + span.LatitudeDegrees / 2,
|
|
|
|
span.Center.Longitude + span.LongitudeDegrees / 2);
|
|
|
|
var sw = new LatLng(span.Center.Latitude - span.LatitudeDegrees / 2,
|
|
|
|
span.Center.Longitude - span.LongitudeDegrees / 2);
|
|
|
|
CameraUpdate update = CameraUpdateFactory.NewLatLngBounds(new LatLngBounds(sw, ne), 0);
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (animate)
|
|
|
|
{
|
|
|
|
map.AnimateCamera(update);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
map.MoveCamera(update);
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
2016-09-16 21:55:38 +03:00
|
|
|
}
|
|
|
|
catch (IllegalStateException exc)
|
|
|
|
{
|
|
|
|
System.Diagnostics.Debug.WriteLine("MoveToRegion exception: " + exc);
|
2016-09-27 13:51:22 +03:00
|
|
|
Log.Warning("Xamarin.Forms MapRenderer", $"MoveToRegion exception: {exc}");
|
2016-09-16 21:55:38 +03:00
|
|
|
}
|
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
|
|
|
|
{
|
|
|
|
switch (notifyCollectionChangedEventArgs.Action)
|
|
|
|
{
|
|
|
|
case NotifyCollectionChangedAction.Add:
|
|
|
|
AddPins(notifyCollectionChangedEventArgs.NewItems);
|
|
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Remove:
|
|
|
|
RemovePins(notifyCollectionChangedEventArgs.OldItems);
|
|
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Replace:
|
|
|
|
RemovePins(notifyCollectionChangedEventArgs.OldItems);
|
|
|
|
AddPins(notifyCollectionChangedEventArgs.NewItems);
|
|
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Reset:
|
|
|
|
_markers?.ForEach(m => m.Remove());
|
|
|
|
_markers = null;
|
|
|
|
AddPins((IList)Element.Pins);
|
|
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Move:
|
|
|
|
//do nothing
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OnMoveToRegionMessage(Map s, MapSpan a)
|
|
|
|
{
|
|
|
|
MoveToRegion(a, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RemovePins(IList pins)
|
|
|
|
{
|
|
|
|
GoogleMap map = NativeMap;
|
|
|
|
if (map == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (_markers == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (Pin p in pins)
|
|
|
|
{
|
|
|
|
var marker = _markers.FirstOrDefault(m => (object)m.Id == p.Id);
|
|
|
|
if (marker == null)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
marker.Remove();
|
|
|
|
_markers.Remove(marker);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetMapType()
|
|
|
|
{
|
|
|
|
GoogleMap map = NativeMap;
|
|
|
|
if (map == null)
|
|
|
|
{
|
|
|
|
return;
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
|
|
|
|
2016-09-16 21:55:38 +03:00
|
|
|
switch (Map.MapType)
|
|
|
|
{
|
|
|
|
case MapType.Street:
|
|
|
|
map.MapType = GoogleMap.MapTypeNormal;
|
|
|
|
break;
|
|
|
|
case MapType.Satellite:
|
|
|
|
map.MapType = GoogleMap.MapTypeSatellite;
|
|
|
|
break;
|
|
|
|
case MapType.Hybrid:
|
|
|
|
map.MapType = GoogleMap.MapTypeHybrid;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void UpdateVisibleRegion(LatLng pos)
|
|
|
|
{
|
|
|
|
GoogleMap map = NativeMap;
|
|
|
|
if (map == null)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Projection projection = map.Projection;
|
|
|
|
int width = Control.Width;
|
|
|
|
int height = Control.Height;
|
|
|
|
LatLng ul = projection.FromScreenLocation(new global::Android.Graphics.Point(0, 0));
|
|
|
|
LatLng ur = projection.FromScreenLocation(new global::Android.Graphics.Point(width, 0));
|
|
|
|
LatLng ll = projection.FromScreenLocation(new global::Android.Graphics.Point(0, height));
|
|
|
|
LatLng lr = projection.FromScreenLocation(new global::Android.Graphics.Point(width, height));
|
|
|
|
double dlat = Math.Max(Math.Abs(ul.Latitude - lr.Latitude), Math.Abs(ur.Latitude - ll.Latitude));
|
|
|
|
double dlong = Math.Max(Math.Abs(ul.Longitude - lr.Longitude), Math.Abs(ur.Longitude - ll.Longitude));
|
2017-07-24 23:23:14 +03:00
|
|
|
Element.SetVisibleRegion(new MapSpan(new Position(pos.Latitude, pos.Longitude), dlat, dlong));
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
2017-03-21 13:38:36 +03:00
|
|
|
|
|
|
|
void IOnMapReadyCallback.OnMapReady(GoogleMap map)
|
|
|
|
{
|
|
|
|
NativeMap = map;
|
2017-04-12 01:52:04 +03:00
|
|
|
OnMapReady(map);
|
2017-03-21 13:38:36 +03:00
|
|
|
}
|
2016-03-22 23:02:25 +03:00
|
|
|
}
|
2017-04-12 01:52:04 +03:00
|
|
|
}
|