diff --git a/Ooui.Forms.Maps/FormsMaps.cs b/Ooui.Forms.Maps/FormsMaps.cs new file mode 100644 index 0000000..0eeb434 --- /dev/null +++ b/Ooui.Forms.Maps/FormsMaps.cs @@ -0,0 +1,22 @@ +using Ooui.Forms; +using Ooui.Forms.Maps; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Maps; + +[assembly: ExportRenderer(typeof(Map), typeof(MapViewRenderer))] +namespace Xamarin +{ + public static class FormsMaps + { + public static string APIKey { get; private set; } + public static void Init(string apiKey) + { + APIKey = apiKey; + Registrar.RegisterAll(new[] { + typeof(ExportRendererAttribute), + typeof(ExportCellAttribute), + typeof(ExportImageSourceHandlerAttribute), + }); + } + } +} diff --git a/Ooui.Forms.Maps/MapViewRenderer.cs b/Ooui.Forms.Maps/MapViewRenderer.cs new file mode 100644 index 0000000..2d046cd --- /dev/null +++ b/Ooui.Forms.Maps/MapViewRenderer.cs @@ -0,0 +1,289 @@ +using Ooui.Forms.Renderers; +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Xamarin; +using Xamarin.Forms; +using Xamarin.Forms.Maps; + +namespace Ooui.Forms.Maps +{ + public class MapViewRenderer : ViewRenderer + { + bool _disposed; + + static Size initialSize = Size.Zero; + private ObservableCollection _pins; + private MapSpan _currentSpan; + + IElementController ElementController => Element as IElementController; + + public MapViewRenderer():base() + { + + } + + public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + + var size = new Size(Element.WidthRequest,Element.HeightRequest); + var vpadding = 16; + var hpadding = 32; + size = new Size(size.Width + hpadding, size.Height + vpadding); + return new SizeRequest(size, size); + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + if (Element != null) + { + Element.FocusChangeRequested -= Element_FocusChangeRequested; + } + if (Control != null) + { + if (_pins != null) + { + _pins.CollectionChanged -= Pins_CollectionChanged; + _pins.Clear(); + _pins = null; + } + _currentSpan = null; + } + } + + base.Dispose(disposing); + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e.OldElement != null) + { + e.OldElement.FocusChangeRequested -= Element_FocusChangeRequested; + } + + if (e.NewElement == null) + return; + + e.NewElement.FocusChangeRequested += Element_FocusChangeRequested; + + if (Control == null) + { + var map = new Ooui.GoogleMap(apiKey: FormsMaps.APIKey,firstMapControlOnPage: true,mapType: GetMapType(Element), startPos: GetPos(Element)); + foreach (var item in Element.Pins) + { + map.AddMarker(GetMapMarkerFromPin(item)); + } + + _pins = new ObservableCollection(Element.Pins); + _pins.CollectionChanged += Pins_CollectionChanged; + _currentSpan = Element.LastMoveToRegion; + SetNativeControl(map); + + Debug.Assert(Control != null, "Control != null"); + + WatchPins(); + WatchRegion(); + } + } + + private void Pins_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case System.Collections.Specialized.NotifyCollectionChangedAction.Add: + foreach (var item in e.NewItems) + { + Control.AddMarker(GetMapMarkerFromPin(item as Pin)); + } + break; + case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: + foreach (var item in e.OldItems) + { + Control.RemoveMarker(GetMapMarkerFromPin(item as Pin)); + } + break; + case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: + foreach (var item in e.OldItems) + { + Control.RemoveMarker(GetMapMarkerFromPin(item as Pin)); + } + foreach (var item in e.NewItems) + { + Control.AddMarker(GetMapMarkerFromPin(item as Pin)); + } + break; + case System.Collections.Specialized.NotifyCollectionChangedAction.Reset: + Control.ClearMarkers(); + break; + default: + break; + } + } + + private void WatchRegion() + { + ThreadPool.QueueUserWorkItem((state) => + { + while (true) + { + Thread.Sleep(900); + if (_disposed) + { + return; + } + try + { + if (Element.LastMoveToRegion != _currentSpan) + { + _currentSpan = Element.LastMoveToRegion; + Control.CenterOn(GetPos(Element)); + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + continue; + } + } + }); + } + + private void WatchPins() + { + ThreadPool.QueueUserWorkItem((state) => + { + while (true) + { + Thread.Sleep(900); + if (_disposed) + { + return; + } + try + { + if (Element.Pins.Count != _pins.Count) + { + if (Element.Pins.Count == 0) + { + _pins.Clear(); + continue; + } + + foreach (var item in Element.Pins) + { + if (!_pins.Contains(item)) + { + _pins.Add(item); + } + } + + var removedItems = _pins.Where(pin => !Element.Pins.Contains(pin)).ToArray(); + foreach (var item in removedItems) + { + _pins.Remove(item); + } + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + continue; + } + } + }); + } + + private static GoogleMap.MapMarker GetMapMarkerFromPin(Pin item) + { + return new GoogleMap.MapMarker + { + lat = item.Position.Latitude, + lng = item.Position.Longitude, + title = item.Label, + infoWindow = new GoogleMap.MapInfoWindow + { + content = item.Address, + }, + }; + } + + private GoogleMap.Position GetPos(Map element) + { + return new GoogleMap.Position() + { + latitude = element.LastMoveToRegion.Center.Latitude, + longitude = element.LastMoveToRegion.Center.Longitude, + }; + } + + private GoogleMap.MapType GetMapType(Map element) + { + switch (element.MapType) + { + case MapType.Street: + return GoogleMap.MapType.ROADMAP; + case MapType.Satellite: + return GoogleMap.MapType.SATELLITE; + case MapType.Hybrid: + return GoogleMap.MapType.HYBRID; + default: + throw new ArgumentOutOfRangeException(nameof(element.MapType)); + } + } + + void Element_FocusChangeRequested(object sender, VisualElement.FocusRequestArgs e) + { + if (e.Focus && Control != null) + { + Control?.Focus(); + e.Result = true; + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (_disposed) + { + return; + } + if (e.PropertyName == Map.MapTypeProperty.PropertyName) + { + UpdateMapType(); + } + else if (e.PropertyName == Map.IsShowingUserProperty.PropertyName) + { + //TODO: Implement + //UpdateIsShowingUser(); + } + else if (e.PropertyName == Map.HasScrollEnabledProperty.PropertyName) + { + //TODO: Implement + //UpdateHasScrollEnabled(); + } + else if (e.PropertyName == Map.HasZoomEnabledProperty.PropertyName) + { + //TODO: Implement + //UpdateHasZoomEnabled(); + } + + base.OnElementPropertyChanged(sender, e); + } + + private void UpdateMapType() + { + Control.ChangeMapType(GetMapType(Element)); + } + } +} diff --git a/Ooui.Forms.Maps/Ooui.Forms.Maps.csproj b/Ooui.Forms.Maps/Ooui.Forms.Maps.csproj new file mode 100644 index 0000000..89c9070 --- /dev/null +++ b/Ooui.Forms.Maps/Ooui.Forms.Maps.csproj @@ -0,0 +1,28 @@ + + + + 1.0.0 + praeclarum + Xamarin.Forms.Maps backend for the web using Ooui technologies. + Ooui;UI;CrossPlatform;Xamarin.Forms;Xamarin.Forms.Maps + https://github.com/praeclarum/Ooui/raw/master/Documentation/Icon.png + https://github.com/praeclarum/Ooui + https://github.com/praeclarum/Ooui/blob/master/LICENSE + https://github.com/praeclarum/Ooui.git + netstandard2.0 + true + + + + PCL + + + + + + + + + + + diff --git a/Ooui.sln b/Ooui.sln index 7985e6f..5caf8c7 100644 --- a/Ooui.sln +++ b/Ooui.sln @@ -2,23 +2,25 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2010 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ooui", "Ooui\Ooui.csproj", "{DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ooui", "Ooui\Ooui.csproj", "{DFDFD036-BF48-4D3A-BF99-88CA1EA8E4B9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{CDF8BB01-40BB-402F-8446-47AA6F1628F3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "Samples\Samples.csproj", "{CDF8BB01-40BB-402F-8446-47AA6F1628F3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{78F6E9E7-4322-4F87-8CE9-1EEF1B16D268}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ooui.Forms", "Ooui.Forms\Ooui.Forms.csproj", "{DB819A2F-91E1-40FB-8D48-6544169966B8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ooui.Forms", "Ooui.Forms\Ooui.Forms.csproj", "{DB819A2F-91E1-40FB-8D48-6544169966B8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ooui.AspNetCore", "Ooui.AspNetCore\Ooui.AspNetCore.csproj", "{2EDF0328-698B-458A-B10C-AB1B4786A6CA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ooui.AspNetCore", "Ooui.AspNetCore\Ooui.AspNetCore.csproj", "{2EDF0328-698B-458A-B10C-AB1B4786A6CA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PlatformSamples", "PlatformSamples", "{12ADF328-BBA8-48FC-9AF1-F11B7921D9EA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreMvc", "PlatformSamples\AspNetCoreMvc\AspNetCoreMvc.csproj", "{7C6D477C-3378-4A86-9C31-AAD51204120B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreMvc", "PlatformSamples\AspNetCoreMvc\AspNetCoreMvc.csproj", "{7C6D477C-3378-4A86-9C31-AAD51204120B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ooui.Wasm.Build.Tasks", "Ooui.Wasm.Build.Tasks\Ooui.Wasm.Build.Tasks.csproj", "{6E9C9582-0DA8-4496-BAE0-23EFAF4A10C2}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "OouiTemplates", "Templates\OouiTemplates\OouiTemplates.csproj", "{7D3B32CF-38A3-40DE-8123-2E7A0D87104D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OouiTemplates", "Templates\OouiTemplates\OouiTemplates.csproj", "{1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ooui.Forms.Maps", "Ooui.Forms.Maps\Ooui.Forms.Maps.csproj", "{D85547C0-A167-4B11-B5BB-5BD1914C3777}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -114,18 +116,30 @@ Global {6E9C9582-0DA8-4496-BAE0-23EFAF4A10C2}.Release|x64.Build.0 = Release|Any CPU {6E9C9582-0DA8-4496-BAE0-23EFAF4A10C2}.Release|x86.ActiveCfg = Release|Any CPU {6E9C9582-0DA8-4496-BAE0-23EFAF4A10C2}.Release|x86.Build.0 = Release|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Debug|x64.ActiveCfg = Debug|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Debug|x64.Build.0 = Debug|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Debug|x86.ActiveCfg = Debug|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Debug|x86.Build.0 = Debug|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Release|Any CPU.Build.0 = Release|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Release|x64.ActiveCfg = Release|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Release|x64.Build.0 = Release|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Release|x86.ActiveCfg = Release|Any CPU - {7D3B32CF-38A3-40DE-8123-2E7A0D87104D}.Release|x86.Build.0 = Release|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Debug|x64.ActiveCfg = Debug|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Debug|x64.Build.0 = Debug|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Debug|x86.ActiveCfg = Debug|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Debug|x86.Build.0 = Debug|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Release|Any CPU.Build.0 = Release|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Release|x64.ActiveCfg = Release|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Release|x64.Build.0 = Release|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Release|x86.ActiveCfg = Release|Any CPU + {1DA10AAB-EB41-49CF-9441-B4D28D0A7F96}.Release|x86.Build.0 = Release|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Debug|x64.ActiveCfg = Debug|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Debug|x64.Build.0 = Debug|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Debug|x86.ActiveCfg = Debug|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Debug|x86.Build.0 = Debug|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Release|Any CPU.Build.0 = Release|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Release|x64.ActiveCfg = Release|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Release|x64.Build.0 = Release|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Release|x86.ActiveCfg = Release|Any CPU + {D85547C0-A167-4B11-B5BB-5BD1914C3777}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ooui/Client.js b/Ooui/Client.js index c651674..3d3f6fa 100644 --- a/Ooui/Client.js +++ b/Ooui/Client.js @@ -213,6 +213,21 @@ function msgRemAttr (m) { if (debug) console.log ("RemAttr", node, m.k); } +function msgEvent (m) { + const id = m.id; + const node = getNode (id); + if (!node) { + console.error ("Unknown node id", m); + return; + } + const target = node; + const eventName = m.k; + if (debug) console.log("Event", node, eventName, m.v); + + var eventMsg = new CustomEvent(eventName, { detail: m.v }); + target.dispatchEvent(eventMsg); +} + function msgCall (m) { const id = m.id; const node = getNode (id); @@ -243,6 +258,7 @@ function msgListen (m) { } if (debug) console.log ("Listen", node, m.k); node.addEventListener(m.k, function (e) { + if (debug) console.log("Event Caught", node, m.k); const em = { m: "event", id: m.id, @@ -259,6 +275,9 @@ function msgListen (m) { offsetY: e.offsetY, }; } + else if (e.detail) { + em.v = e.detail; + } const ems = JSON.stringify (em); send (ems); if (debug) console.log ("Event", em); @@ -289,6 +308,9 @@ function processMessage (m) { case "listen": msgListen (m); break; + case "event": + msgEvent(m); + break; default: console.error ("Unknown message type", m.m, m); } diff --git a/Ooui/EventTarget.cs b/Ooui/EventTarget.cs index c2bee13..e449dbd 100644 --- a/Ooui/EventTarget.cs +++ b/Ooui/EventTarget.cs @@ -11,9 +11,12 @@ public abstract class EventTarget : INotifyPropertyChanged { readonly List stateMessages = new List (); - readonly Dictionary> eventListeners = + readonly Dictionary> targetEventListeners = new Dictionary> (); + readonly Dictionary> domEventListeners = + new Dictionary> (); + public string Id { get; protected set; } = GenerateId (); public string TagName { get; private set; } @@ -57,10 +60,10 @@ public void AddEventListener (string eventType, TargetEventHandler handler) var sendListen = false; List handlers; - lock (eventListeners) { - if (!eventListeners.TryGetValue (eventType, out handlers)) { + lock (targetEventListeners) { + if (!targetEventListeners.TryGetValue (eventType, out handlers)) { handlers = new List (); - eventListeners[eventType] = handlers; + targetEventListeners[eventType] = handlers; sendListen = true; } handlers.Add (handler); @@ -80,8 +83,46 @@ public void RemoveEventListener (string eventType, TargetEventHandler handler) if (handler == null) return; List handlers; - lock (eventListeners) { - if (eventListeners.TryGetValue (eventType, out handlers)) { + lock (targetEventListeners) { + if (targetEventListeners.TryGetValue (eventType, out handlers)) { + handlers.Remove (handler); + } + } + } + + public void AddEventListener (string eventType, DOMEventHandler handler) + { + if (eventType == null) return; + if (handler == null) return; + + var sendListen = false; + + List handlers; + lock (domEventListeners) { + if (!domEventListeners.TryGetValue (eventType, out handlers)) { + handlers = new List (); + domEventListeners[eventType] = handlers; + sendListen = true; + } + handlers.Add (handler); + } + + if (sendListen) + Send (new Message { + MessageType = MessageType.Listen, + TargetId = Id, + Key = eventType, + }); + } + + public void RemoveEventListener (string eventType, DOMEventHandler handler) + { + if (eventType == null) return; + if (handler == null) return; + + List handlers; + lock (domEventListeners) { + if (domEventListeners.TryGetValue (eventType, out handlers)) { handlers.Remove (handler); } } @@ -190,9 +231,9 @@ protected virtual bool SaveStateMessageIfNeeded (Message message) protected virtual bool TriggerEvent (string name) { List handlers = null; - lock (eventListeners) { + lock (targetEventListeners) { List hs; - if (eventListeners.TryGetValue (name, out hs)) { + if (targetEventListeners.TryGetValue (name, out hs)) { handlers = new List (hs); } } @@ -210,21 +251,45 @@ protected virtual bool TriggerEventFromMessage (Message message) if (message.TargetId != Id) return false; - List handlers = null; - lock (eventListeners) { + List handlers = new List(); + lock (targetEventListeners) { List hs; - if (eventListeners.TryGetValue (message.Key, out hs)) { - handlers = new List (hs); + if (targetEventListeners.TryGetValue (message.Key, out hs)) { + handlers.AddRange(hs); } } - if (handlers != null) { - var args = new TargetEventArgs (); - if (message.Value is Newtonsoft.Json.Linq.JObject o) { - args.OffsetX = (double)o["offsetX"]; - args.OffsetY = (double)o["offsetY"]; + lock (domEventListeners) { + List hs; + if (domEventListeners.TryGetValue (message.Key, out hs)) { + handlers.AddRange(hs); + } + } + if (handlers != null && handlers.Count > 0) { + var tArgs = new TargetEventArgs (); + var domArgs = new DOMEventArgs(); + if (message.Value is Newtonsoft.Json.Linq.JObject o) + { + domArgs.Data = o; + try + { + tArgs.OffsetX = (double)o["offsetX"]; + tArgs.OffsetY = (double)o["offsetY"]; + } + catch { } } foreach (var h in handlers) { - h.Invoke (this, args); + if (h is TargetEventHandler targetEventHandler) + { + targetEventHandler.Invoke(this, tArgs); + } + else if(h is DOMEventHandler domEventHandler) + { + domEventHandler.Invoke(this, domArgs); + } + else + { + h.DynamicInvoke(this, message.Value); + } } } return true; @@ -252,10 +317,16 @@ public override bool CanConvert (Type objectType) } public delegate void TargetEventHandler (object sender, TargetEventArgs e); + public delegate void DOMEventHandler (object sender, DOMEventArgs e); public class TargetEventArgs : EventArgs { public double OffsetX { get; set; } public double OffsetY { get; set; } } + + public class DOMEventArgs : EventArgs + { + public Newtonsoft.Json.Linq.JObject Data { get; set; } + } } diff --git a/Ooui/GoogleMap.cs b/Ooui/GoogleMap.cs new file mode 100644 index 0000000..4df16ae --- /dev/null +++ b/Ooui/GoogleMap.cs @@ -0,0 +1,2872 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; + +/* + + or + available at https://raw.githubusercontent.com/HPNeo/gmaps/master/gmaps.js +*/ +namespace Ooui +{ + public class GoogleMap : Div + { + #region Classes and Enums + /// + /// Supported Map Types by the Google Maps API + /// + public enum MapType + { + /// + /// normal, default 2D map + /// + ROADMAP, + /// + /// photographic map + /// + SATELLITE, + /// + /// photographic map + roads and city names + /// + HYBRID, + /// + /// map with mountains, rivers, etc. + /// + TERRAIN, + } + + public class MapMarker : IEquatable + { + public double lat; + public double lng; + public string title; + public MapInfoWindow infoWindow; + + public bool Equals(MapMarker other) + { + if (lat == other.lat && lng == other.lng && title == other.title && (infoWindow?.content == other.infoWindow?.content)) + { + return true; + } + return base.Equals(other); + } + } + + public class Position + { + public double latitude; + public double longitude; + } + + public class MapInfoWindow + { + public string content; + } + #endregion + + #region Private Globals + private readonly bool _firstMapControlOnPage; + private Position _position; + private MapType _mapType; + private System.Collections.Generic.List Markers { get; } = new System.Collections.Generic.List(); + #endregion + + #region Public Globals + public string APIKey { get; set; } + #endregion + + #region Constructors + public GoogleMap(string apiKey = "", Position startPos = null, MapType mapType = MapType.ROADMAP,/*string mapId = "basic_map",*/ bool firstMapControlOnPage = true) + { + //Id = mapId; + _firstMapControlOnPage = firstMapControlOnPage; + APIKey = apiKey; + _position = startPos ?? new Position + { + latitude = 51.5073346, + longitude = -0.1276831, + }; + _mapType = mapType; + } + #endregion + + #region Overrides + public override void WriteInnerHtml(XmlWriter w) + { + base.WriteInnerHtml(w); + } + + public override void WriteOuterHtml(XmlWriter w) + { + base.WriteOuterHtml(w); + + if (_firstMapControlOnPage) + { + WriteScriptsOnFirstInstance(w); + } + else + { + WriteMapScript(w); + } + } + + public override string OuterHtml + { + get + { + return base.OuterHtml + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">") + ; + } + } + + public override string Text + { + get + { + return base.Text; + } + set + { + base.Text = value; + } + } + + public override string ToString() + { + if (_firstMapControlOnPage) + { + return $@"{base.ToString()} + + + +" + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">") + ; + //"; + } + return $@"{base.ToString()} +" + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">") + ; + } + + #endregion + + #region Public Methods + public void AddMarker(MapMarker marker) + { + try + { + Markers.Add(marker); + Send(Message.Event(Id, "addMarker", marker)); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + throw; + } + } + + public void CenterOn(Position position) + { + try + { + _position = position; + Send(Message.Event(Id, "setCenter", _position)); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + throw; + } + + } + + public async Task GetCenter()//This requires the map to have loaded already + { + try + { + Position pos = null; + await Task.Run(() => + { + var mre = new ManualResetEvent(false); + CentreReceived += GoogleMap_CentreReceived; + Send(Message.Event(Id, "raiseCenter")); + mre.WaitOne(90000);//90 Second Timeout + + void GoogleMap_CentreReceived(object sender, DOMEventArgs e) + { + CentreReceived -= GoogleMap_CentreReceived; + pos = new Position + { + latitude = (double)e.Data["latitude"], + longitude = (double)e.Data["longitude"], + }; + mre.Set(); + } + }); + return pos ?? _position; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + return _position; + } + } + + public void RemoveMarker(MapMarker mapMarker) + { + try + { + var index = Markers.IndexOf(Markers.First(m=>m.Equals(mapMarker))); + Markers.Remove(mapMarker); + Send(Message.Event(Id, "removeMarker", index)); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + throw; + } + } + + public void ClearMarkers() + { + try + { + Markers.Clear(); + Send(Message.Event(Id, "clearMarkers")); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + throw; + } + } + + public void ChangeMapType(MapType mapType) + { + try + { + _mapType = mapType; + Send(Message.Event(Id, "changeMapType", mapType.ToString())); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + throw; + } + } + + #endregion + + #region Private Events + private event DOMEventHandler CentreReceived + { + add => AddEventListener("centerRaised", value); + remove => RemoveEventListener("centerRaised", value); + } + #endregion + + #region Private Methods + private void WriteScriptsOnFirstInstance(XmlWriter w) + { + w.WriteString(@" +"); + w.WriteStartElement("script"); + w.WriteAttributeString("src", "http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"); + w.WriteString(@" +"); + w.WriteFullEndElement(); + + w.WriteString(@" +"); + w.WriteStartElement("script"); + w.WriteAttributeString("type", "text/javascript"); + w.WriteAttributeString("src", $"http://maps.google.com/maps/api/js?key={APIKey}"); + w.WriteString(@" +"); + w.WriteFullEndElement(); + + w.WriteString(@" +"); + w.WriteStartElement("script"); + w.WriteRaw(_gmaps); + w.WriteString(@" +"); + w.WriteFullEndElement(); + + WriteMapScript(w); + } + + private void WriteMapScript(XmlWriter w) + { + w.WriteString(@" +"); + w.WriteStartElement("script"); + w.WriteRaw(ReplaceTokens(MapsScript)); + w.WriteString(@" +"); + w.WriteFullEndElement(); + } + + private string ReplaceTokens(string tokenizedString) + { + return tokenizedString + .Replace("$$Map_ID$$", Id); + } + + private string GetMarkers() + { + string markers = string.Empty; + foreach (var marker in Markers) + { + markers += $@" + map.addMarker({Newtonsoft.Json.JsonConvert.SerializeObject(marker)}); +"; + } + return markers; + } + #endregion + + #region JavaScript Scripts + private string MapsScript => $@" +$(document).ready(function () {{ + + var mapOwner = $('#$$Map_ID$$'); + + mapOwner.height('500px'); + + var map = new GMaps({{ + el: '#$$Map_ID$$', + lat: {_position.latitude.ToString().Replace(',', '.')}, + lng: {_position.longitude.ToString().Replace(',', '.')}, + zoom: 12, + zoomControl: true, + zoomControlOpt: {{ + style: 'SMALL', + position: 'TOP_LEFT' + }}, + panControl: false, + mapType: '{_mapType.ToString()}', + }}); + + {GetMarkers()} + + mapOwner.on('clearMarkers', clearMarkersEventRaised); + + function clearMarkersEventRaised(e) {{ + console.log ('Clearing Map markers',map.markers); + var markerLength = map.markers.length; + for (var i = 0, len = markerLength; i < len; i++) {{ + map.removeMarker(map.markers[0]); + }} + console.log ('After clearing map markers',map.markers); + }} + + mapOwner.on('changeMapType', changeMapTypeEventRaised); + + function changeMapTypeEventRaised(e) {{ + console.log ('Changing map type',e.originalEvent.detail,e.originalEvent,e); + var center = map.getCenter(); + var coords = {{ + latitude: center.lat(), + longitude: center.lng(), + }}; + var arr = map.markers; + map = new GMaps({{ + el: '#$$Map_ID$$', + lat: coords.latitude, + lng: coords.longitude, + zoom: 12, + zoomControl: true, + zoomControlOpt: {{ + style: 'SMALL', + position: 'TOP_LEFT' + }}, + panControl: false, + mapType: e.originalEvent.detail, + }}); + for (var i = 0, len = arr.length; i < len; i++) {{ + map.addMarker(arr[i]); + }} + }} + + mapOwner.on('addMarker', addMarkerEventRaised); + + function addMarkerEventRaised(e) {{ + console.log ('Adding Map marker',e.originalEvent.detail,e.originalEvent,e); + map.addMarker(e.originalEvent.detail); + }} + + mapOwner.on('removeMarker', removeMarkerEventRaised); + + function removeMarkerEventRaised(e) {{ + console.log ('Removing Map marker',e.originalEvent.detail,e.originalEvent,e); + map.removeMarker(map.markers[e.originalEvent.detail]); + }} + + mapOwner.on('setCenter', setCenterEventRaised); + + function setCenterEventRaised(e) {{ + console.log ('Setting Map Center',e.originalEvent.detail,e.originalEvent,e); + map.setCenter(e.originalEvent.detail.latitude, e.originalEvent.detail.longitude); + }} + + mapOwner.on('raiseCenter', raiseCenterEventRaised); + + function raiseCenterEventRaised(e) {{ + + console.log ('Getting Map Center'); + var center = map.getCenter(); + var coords = {{ + latitude: center.lat(), + longitude: center.lng(), + }}; + + console.log ('Current Map Center',center); + + var eventMsg = new CustomEvent('centerRaised', {{ detail: coords }}); + //mapOwner.trigger( 'centerRaised', [ coords ] ); + mapOwner[0].dispatchEvent(eventMsg); + }} + +}}); +"; + + private const string _gmaps = @" +""use strict""; +(function(root, factory) { + if(typeof exports === 'object') { + module.exports = factory(); + } + else if(typeof define === 'function' && define.amd) { + define(['jquery', 'googlemaps!'], factory); + } + else { + root.GMaps = factory(); + } + + +}(this, function() { + +/*! + * GMaps.js v0.4.25 + * http://hpneo.github.com/gmaps/ + * + * Copyright 2017, Gustavo Leon + * Released under the MIT License. + */ + +var extend_object = function(obj, new_obj) { + var name; + + if (obj === new_obj) { + return obj; + } + + for (name in new_obj) { + if (new_obj[name] !== undefined) { + obj[name] = new_obj[name]; + } + } + + return obj; +}; + +var replace_object = function(obj, replace) { + var name; + + if (obj === replace) { + return obj; + } + + for (name in replace) { + if (obj[name] != undefined) { + obj[name] = replace[name]; + } + } + + return obj; +}; + +var array_map = function(array, callback) { + var original_callback_params = Array.prototype.slice.call(arguments, 2), + array_return = [], + array_length = array.length, + i; + + if (Array.prototype.map && array.map === Array.prototype.map) { + array_return = Array.prototype.map.call(array, function(item) { + var callback_params = original_callback_params.slice(0); + callback_params.splice(0, 0, item); + + return callback.apply(this, callback_params); + }); + } + else { + for (i = 0; i < array_length; i++) { + callback_params = original_callback_params; + callback_params.splice(0, 0, array[i]); + array_return.push(callback.apply(this, callback_params)); + } + } + + return array_return; +}; + +var array_flat = function(array) { + var new_array = [], + i; + + for (i = 0; i < array.length; i++) { + new_array = new_array.concat(array[i]); + } + + return new_array; +}; + +var coordsToLatLngs = function(coords, useGeoJSON) { + var first_coord = coords[0], + second_coord = coords[1]; + + if (useGeoJSON) { + first_coord = coords[1]; + second_coord = coords[0]; + } + + return new google.maps.LatLng(first_coord, second_coord); +}; + +var arrayToLatLng = function(coords, useGeoJSON) { + var i; + + for (i = 0; i < coords.length; i++) { + if (!(coords[i] instanceof google.maps.LatLng)) { + if (coords[i].length > 0 && typeof(coords[i][0]) === ""object"") { + coords[i] = arrayToLatLng(coords[i], useGeoJSON); + } + else { + coords[i] = coordsToLatLngs(coords[i], useGeoJSON); + } + } + } + + return coords; +}; + +var getElementsByClassName = function (class_name, context) { + var element, + _class = class_name.replace('.', ''); + + if ('jQuery' in this && context) { + element = $(""."" + _class, context)[0]; + } else { + element = document.getElementsByClassName(_class)[0]; + } + return element; + +}; + +var getElementById = function(id, context) { + var element, + id = id.replace('#', ''); + + if ('jQuery' in window && context) { + element = $('#' + id, context)[0]; + } else { + element = document.getElementById(id); + }; + + return element; +}; + +var findAbsolutePosition = function(obj) { + var curleft = 0, + curtop = 0; + + if (obj.getBoundingClientRect) { + var rect = obj.getBoundingClientRect(); + var sx = -(window.scrollX ? window.scrollX : window.pageXOffset); + var sy = -(window.scrollY ? window.scrollY : window.pageYOffset); + + return [(rect.left - sx), (rect.top - sy)]; + } + + if (obj.offsetParent) { + do { + curleft += obj.offsetLeft; + curtop += obj.offsetTop; + } while (obj = obj.offsetParent); + } + + return [curleft, curtop]; +}; + +var GMaps = (function(global) { + ""use strict""; + + var doc = document; + /** + * Creates a new GMaps instance, including a Google Maps map. + * @class GMaps + * @constructs + * @param {object} options - `options` accepts all the [MapOptions](https://developers.google.com/maps/documentation/javascript/reference#MapOptions) and [events](https://developers.google.com/maps/documentation/javascript/reference#Map) listed in the Google Maps API. Also accepts: + * * `lat` (number): Latitude of the map's center + * * `lng` (number): Longitude of the map's center + * * `el` (string or HTMLElement): container where the map will be rendered + * * `markerClusterer` (function): A function to create a marker cluster. You can use MarkerClusterer or MarkerClustererPlus. + */ + var GMaps = function(options) { + + if (!(typeof window.google === 'object' && window.google.maps)) { + if (typeof window.console === 'object' && window.console.error) { + console.error('Google Maps API is required. Please register the following JavaScript library https://maps.googleapis.com/maps/api/js.'); + } + + return function() {}; + } + + if (!this) return new GMaps(options); + + options.zoom = options.zoom || 15; + options.mapType = options.mapType || 'roadmap'; + + var valueOrDefault = function(value, defaultValue) { + return value === undefined ? defaultValue : value; + }; + + var self = this, + i, + events_that_hide_context_menu = [ + 'bounds_changed', 'center_changed', 'click', 'dblclick', 'drag', + 'dragend', 'dragstart', 'idle', 'maptypeid_changed', 'projection_changed', + 'resize', 'tilesloaded', 'zoom_changed' + ], + events_that_doesnt_hide_context_menu = ['mousemove', 'mouseout', 'mouseover'], + options_to_be_deleted = ['el', 'lat', 'lng', 'mapType', 'width', 'height', 'markerClusterer', 'enableNewStyle'], + identifier = options.el || options.div, + markerClustererFunction = options.markerClusterer, + mapType = google.maps.MapTypeId[options.mapType.toUpperCase()], + map_center = new google.maps.LatLng(options.lat, options.lng), + zoomControl = valueOrDefault(options.zoomControl, true), + zoomControlOpt = options.zoomControlOpt || { + style: 'DEFAULT', + position: 'TOP_LEFT' + }, + zoomControlStyle = zoomControlOpt.style || 'DEFAULT', + zoomControlPosition = zoomControlOpt.position || 'TOP_LEFT', + panControl = valueOrDefault(options.panControl, true), + mapTypeControl = valueOrDefault(options.mapTypeControl, true), + scaleControl = valueOrDefault(options.scaleControl, true), + streetViewControl = valueOrDefault(options.streetViewControl, true), + overviewMapControl = valueOrDefault(overviewMapControl, true), + map_options = {}, + map_base_options = { + zoom: this.zoom, + center: map_center, + mapTypeId: mapType + }, + map_controls_options = { + panControl: panControl, + zoomControl: zoomControl, + zoomControlOptions: { + style: google.maps.ZoomControlStyle[zoomControlStyle], + position: google.maps.ControlPosition[zoomControlPosition] + }, + mapTypeControl: mapTypeControl, + scaleControl: scaleControl, + streetViewControl: streetViewControl, + overviewMapControl: overviewMapControl + }; + + if (typeof(options.el) === 'string' || typeof(options.div) === 'string') { + if (identifier.indexOf(""#"") > -1) { + /** + * Container element + * + * @type {HTMLElement} + */ + this.el = getElementById(identifier, options.context); + } else { + this.el = getElementsByClassName.apply(this, [identifier, options.context]); + } + } else { + this.el = identifier; + } + + if (typeof(this.el) === 'undefined' || this.el === null) { + throw 'No element defined.'; + } + + window.context_menu = window.context_menu || {}; + window.context_menu[self.el.id] = {}; + + /** + * Collection of custom controls in the map UI + * + * @type {array} + */ + this.controls = []; + /** + * Collection of map's overlays + * + * @type {array} + */ + this.overlays = []; + /** + * Collection of KML/GeoRSS and FusionTable layers + * + * @type {array} + */ + this.layers = []; + /** + * Collection of data layers (See {@link GMaps#addLayer}) + * + * @type {object} + */ + this.singleLayers = {}; + /** + * Collection of map's markers + * + * @type {array} + */ + this.markers = []; + /** + * Collection of map's lines + * + * @type {array} + */ + this.polylines = []; + /** + * Collection of map's routes requested by {@link GMaps#getRoutes}, {@link GMaps#renderRoute}, {@link GMaps#drawRoute}, {@link GMaps#travelRoute} or {@link GMaps#drawSteppedRoute} + * + * @type {array} + */ + this.routes = []; + /** + * Collection of map's polygons + * + * @type {array} + */ + this.polygons = []; + this.infoWindow = null; + this.overlay_el = null; + /** + * Current map's zoom + * + * @type {number} + */ + this.zoom = options.zoom; + this.registered_events = {}; + + this.el.style.width = options.width || this.el.scrollWidth || this.el.offsetWidth; + this.el.style.height = options.height || this.el.scrollHeight || this.el.offsetHeight; + + google.maps.visualRefresh = options.enableNewStyle; + + for (i = 0; i < options_to_be_deleted.length; i++) { + delete options[options_to_be_deleted[i]]; + } + + if(options.disableDefaultUI != true) { + map_base_options = extend_object(map_base_options, map_controls_options); + } + + map_options = extend_object(map_base_options, options); + + for (i = 0; i < events_that_hide_context_menu.length; i++) { + delete map_options[events_that_hide_context_menu[i]]; + } + + for (i = 0; i < events_that_doesnt_hide_context_menu.length; i++) { + delete map_options[events_that_doesnt_hide_context_menu[i]]; + } + + /** + * Google Maps map instance + * + * @type {google.maps.Map} + */ + this.map = new google.maps.Map(this.el, map_options); + + if (markerClustererFunction) { + /** + * Marker Clusterer instance + * + * @type {object} + */ + this.markerClusterer = markerClustererFunction.apply(this, [this.map]); + } + + var buildContextMenuHTML = function(control, e) { + var html = '', + options = window.context_menu[self.el.id][control]; + + for (var i in options){ + if (options.hasOwnProperty(i)) { + var option = options[i]; + + html += '
  • ' + option.title + '
  • '; + } + } + + if (!getElementById('gmaps_context_menu')) return; + + var context_menu_element = getElementById('gmaps_context_menu'); + + context_menu_element.innerHTML = html; + + var context_menu_items = context_menu_element.getElementsByTagName('a'), + context_menu_items_count = context_menu_items.length, + i; + + for (i = 0; i < context_menu_items_count; i++) { + var context_menu_item = context_menu_items[i]; + + var assign_menu_item_action = function(ev){ + ev.preventDefault(); + + options[this.id.replace(control + '_', '')].action.apply(self, [e]); + self.hideContextMenu(); + }; + + google.maps.event.clearListeners(context_menu_item, 'click'); + google.maps.event.addDomListenerOnce(context_menu_item, 'click', assign_menu_item_action, false); + } + + var position = findAbsolutePosition.apply(this, [self.el]), + left = position[0] + e.pixel.x - 15, + top = position[1] + e.pixel.y- 15; + + context_menu_element.style.left = left + ""px""; + context_menu_element.style.top = top + ""px""; + + // context_menu_element.style.display = 'block'; + }; + + this.buildContextMenu = function(control, e) { + if (control === 'marker') { + e.pixel = {}; + + var overlay = new google.maps.OverlayView(); + overlay.setMap(self.map); + + overlay.draw = function() { + var projection = overlay.getProjection(), + position = e.marker.getPosition(); + + e.pixel = projection.fromLatLngToContainerPixel(position); + + buildContextMenuHTML(control, e); + }; + } + else { + buildContextMenuHTML(control, e); + } + + var context_menu_element = getElementById('gmaps_context_menu'); + + setTimeout(function() { + context_menu_element.style.display = 'block'; + }, 0); + }; + + /** + * Add a context menu for a map or a marker. + * + * @param {object} options - The `options` object should contain: + * * `control` (string): Kind of control the context menu will be attached. Can be ""map"" or ""marker"". + * * `options` (array): A collection of context menu items: + * * `title` (string): Item's title shown in the context menu. + * * `name` (string): Item's identifier. + * * `action` (function): Function triggered after selecting the context menu item. + */ + this.setContextMenu = function(options) { + window.context_menu[self.el.id][options.control] = {}; + + var i, + ul = doc.createElement('ul'); + + for (i in options.options) { + if (options.options.hasOwnProperty(i)) { + var option = options.options[i]; + + window.context_menu[self.el.id][options.control][option.name] = { + title: option.title, + action: option.action + }; + } + } + + ul.id = 'gmaps_context_menu'; + ul.style.display = 'none'; + ul.style.position = 'absolute'; + ul.style.minWidth = '100px'; + ul.style.background = 'white'; + ul.style.listStyle = 'none'; + ul.style.padding = '8px'; + ul.style.boxShadow = '2px 2px 6px #ccc'; + + if (!getElementById('gmaps_context_menu')) { + doc.body.appendChild(ul); + } + + var context_menu_element = getElementById('gmaps_context_menu'); + + google.maps.event.addDomListener(context_menu_element, 'mouseout', function(ev) { + if (!ev.relatedTarget || !this.contains(ev.relatedTarget)) { + window.setTimeout(function(){ + context_menu_element.style.display = 'none'; + }, 400); + } + }, false); + }; + + /** + * Hide the current context menu + */ + this.hideContextMenu = function() { + var context_menu_element = getElementById('gmaps_context_menu'); + + if (context_menu_element) { + context_menu_element.style.display = 'none'; + } + }; + + var setupListener = function(object, name) { + google.maps.event.addListener(object, name, function(e){ + if (e == undefined) { + e = this; + } + + options[name].apply(this, [e]); + + self.hideContextMenu(); + }); + }; + + //google.maps.event.addListener(this.map, 'idle', this.hideContextMenu); + google.maps.event.addListener(this.map, 'zoom_changed', this.hideContextMenu); + + for (var ev = 0; ev < events_that_hide_context_menu.length; ev++) { + var name = events_that_hide_context_menu[ev]; + + if (name in options) { + setupListener(this.map, name); + } + } + + for (var ev = 0; ev < events_that_doesnt_hide_context_menu.length; ev++) { + var name = events_that_doesnt_hide_context_menu[ev]; + + if (name in options) { + setupListener(this.map, name); + } + } + + google.maps.event.addListener(this.map, 'rightclick', function(e) { + if (options.rightclick) { + options.rightclick.apply(this, [e]); + } + + if(window.context_menu[self.el.id]['map'] != undefined) { + self.buildContextMenu('map', e); + } + }); + + /** + * Trigger a `resize` event, useful if you need to repaint the current map (for changes in the viewport or display / hide actions). + */ + this.refresh = function() { + google.maps.event.trigger(this.map, 'resize'); + }; + + /** + * Adjust the map zoom to include all the markers added in the map. + */ + this.fitZoom = function() { + var latLngs = [], + markers_length = this.markers.length, + i; + + for (i = 0; i < markers_length; i++) { + if(typeof(this.markers[i].visible) === 'boolean' && this.markers[i].visible) { + latLngs.push(this.markers[i].getPosition()); + } + } + + this.fitLatLngBounds(latLngs); + }; + + /** + * Adjust the map zoom to include all the coordinates in the `latLngs` array. + * + * @param {array} latLngs - Collection of `google.maps.LatLng` objects. + */ + this.fitLatLngBounds = function(latLngs) { + var total = latLngs.length, + bounds = new google.maps.LatLngBounds(), + i; + + for(i = 0; i < total; i++) { + bounds.extend(latLngs[i]); + } + + this.map.fitBounds(bounds); + }; + + /** + * Center the map using the `lat` and `lng` coordinates. + * + * @param {number} lat - Latitude of the coordinate. + * @param {number} lng - Longitude of the coordinate. + * @param {function} [callback] - Callback that will be executed after the map is centered. + */ + this.setCenter = function(lat, lng, callback) { + this.map.panTo(new google.maps.LatLng(lat, lng)); + + if (callback) { + callback(); + } + }; + + /** + * Return the HTML element container of the map. + * + * @returns {HTMLElement} the element container. + */ + this.getElement = function() { + return this.el; + }; + + /** + * Increase the map's zoom. + * + * @param {number} [magnitude] - The number of times the map will be zoomed in. + */ + this.zoomIn = function(value) { + value = value || 1; + + this.zoom = this.map.getZoom() + value; + this.map.setZoom(this.zoom); + }; + + /** + * Decrease the map's zoom. + * + * @param {number} [magnitude] - The number of times the map will be zoomed out. + */ + this.zoomOut = function(value) { + value = value || 1; + + this.zoom = this.map.getZoom() - value; + this.map.setZoom(this.zoom); + }; + + var native_methods = [], + method; + + for (method in this.map) { + if (typeof(this.map[method]) == 'function' && !this[method]) { + native_methods.push(method); + } + } + + for (i = 0; i < native_methods.length; i++) { + (function(gmaps, scope, method_name) { + gmaps[method_name] = function(){ + return scope[method_name].apply(scope, arguments); + }; + })(this, this.map, native_methods[i]); + } + }; + + return GMaps; +})(this); + +GMaps.prototype.createControl = function(options) { + var control = document.createElement('div'); + + control.style.cursor = 'pointer'; + + if (options.disableDefaultStyles !== true) { + control.style.fontFamily = 'Roboto, Arial, sans-serif'; + control.style.fontSize = '11px'; + control.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px'; + } + + for (var option in options.style) { + control.style[option] = options.style[option]; + } + + if (options.id) { + control.id = options.id; + } + + if (options.title) { + control.title = options.title; + } + + if (options.classes) { + control.className = options.classes; + } + + if (options.content) { + if (typeof options.content === 'string') { + control.innerHTML = options.content; + } + else if (options.content instanceof HTMLElement) { + control.appendChild(options.content); + } + } + + if (options.position) { + control.position = google.maps.ControlPosition[options.position.toUpperCase()]; + } + + for (var ev in options.events) { + (function(object, name) { + google.maps.event.addDomListener(object, name, function(){ + options.events[name].apply(this, [this]); + }); + })(control, ev); + } + + control.index = 1; + + return control; +}; + +/** + * Add a custom control to the map UI. + * + * @param {object} options - The `options` object should contain: + * * `style` (object): The keys and values of this object should be valid CSS properties and values. + * * `id` (string): The HTML id for the custom control. + * * `classes` (string): A string containing all the HTML classes for the custom control. + * * `content` (string or HTML element): The content of the custom control. + * * `position` (string): Any valid [`google.maps.ControlPosition`](https://developers.google.com/maps/documentation/javascript/controls#ControlPositioning) value, in lower or upper case. + * * `events` (object): The keys of this object should be valid DOM events. The values should be functions. + * * `disableDefaultStyles` (boolean): If false, removes the default styles for the controls like font (family and size), and box shadow. + * @returns {HTMLElement} + */ +GMaps.prototype.addControl = function(options) { + var control = this.createControl(options); + + this.controls.push(control); + this.map.controls[control.position].push(control); + + return control; +}; + +/** + * Remove a control from the map. `control` should be a control returned by `addControl()`. + * + * @param {HTMLElement} control - One of the controls returned by `addControl()`. + * @returns {HTMLElement} the removed control. + */ +GMaps.prototype.removeControl = function(control) { + var position = null, + i; + + for (i = 0; i < this.controls.length; i++) { + if (this.controls[i] == control) { + position = this.controls[i].position; + this.controls.splice(i, 1); + } + } + + if (position) { + for (i = 0; i < this.map.controls.length; i++) { + var controlsForPosition = this.map.controls[control.position]; + + if (controlsForPosition.getAt(i) == control) { + controlsForPosition.removeAt(i); + + break; + } + } + } + + return control; +}; + +GMaps.prototype.createMarker = function(options) { + if (options.lat == undefined && options.lng == undefined && options.position == undefined) { + throw 'No latitude or longitude defined.'; + } + + var self = this, + details = options.details, + fences = options.fences, + outside = options.outside, + base_options = { + position: new google.maps.LatLng(options.lat, options.lng), + map: null + }, + marker_options = extend_object(base_options, options); + + delete marker_options.lat; + delete marker_options.lng; + delete marker_options.fences; + delete marker_options.outside; + + var marker = new google.maps.Marker(marker_options); + + marker.fences = fences; + + if (options.infoWindow) { + marker.infoWindow = new google.maps.InfoWindow(options.infoWindow); + + var info_window_events = ['closeclick', 'content_changed', 'domready', 'position_changed', 'zindex_changed']; + + for (var ev = 0; ev < info_window_events.length; ev++) { + (function(object, name) { + if (options.infoWindow[name]) { + google.maps.event.addListener(object, name, function(e){ + options.infoWindow[name].apply(this, [e]); + }); + } + })(marker.infoWindow, info_window_events[ev]); + } + } + + var marker_events = ['animation_changed', 'clickable_changed', 'cursor_changed', 'draggable_changed', 'flat_changed', 'icon_changed', 'position_changed', 'shadow_changed', 'shape_changed', 'title_changed', 'visible_changed', 'zindex_changed']; + + var marker_events_with_mouse = ['dblclick', 'drag', 'dragend', 'dragstart', 'mousedown', 'mouseout', 'mouseover', 'mouseup']; + + for (var ev = 0; ev < marker_events.length; ev++) { + (function(object, name) { + if (options[name]) { + google.maps.event.addListener(object, name, function(){ + options[name].apply(this, [this]); + }); + } + })(marker, marker_events[ev]); + } + + for (var ev = 0; ev < marker_events_with_mouse.length; ev++) { + (function(map, object, name) { + if (options[name]) { + google.maps.event.addListener(object, name, function(me){ + if(!me.pixel){ + me.pixel = map.getProjection().fromLatLngToPoint(me.latLng) + } + + options[name].apply(this, [me]); + }); + } + })(this.map, marker, marker_events_with_mouse[ev]); + } + + google.maps.event.addListener(marker, 'click', function() { + this.details = details; + + if (options.click) { + options.click.apply(this, [this]); + } + + if (marker.infoWindow) { + self.hideInfoWindows(); + marker.infoWindow.open(self.map, marker); + } + }); + + google.maps.event.addListener(marker, 'rightclick', function(e) { + e.marker = this; + + if (options.rightclick) { + options.rightclick.apply(this, [e]); + } + + if (window.context_menu[self.el.id]['marker'] != undefined) { + self.buildContextMenu('marker', e); + } + }); + + if (marker.fences) { + google.maps.event.addListener(marker, 'dragend', function() { + self.checkMarkerGeofence(marker, function(m, f) { + outside(m, f); + }); + }); + } + + return marker; +}; + +GMaps.prototype.addMarker = function(options) { + var marker; + if(options.hasOwnProperty('gm_accessors_')) { + // Native google.maps.Marker object + marker = options; + } + else { + if ((options.hasOwnProperty('lat') && options.hasOwnProperty('lng')) || options.position) { + marker = this.createMarker(options); + } + else { + throw 'No latitude or longitude defined.'; + } + } + + marker.setMap(this.map); + + if(this.markerClusterer) { + this.markerClusterer.addMarker(marker); + } + + this.markers.push(marker); + + GMaps.fire('marker_added', marker, this); + + return marker; +}; + +GMaps.prototype.addMarkers = function(array) { + for (var i = 0, marker; marker=array[i]; i++) { + this.addMarker(marker); + } + + return this.markers; +}; + +GMaps.prototype.hideInfoWindows = function() { + for (var i = 0, marker; marker = this.markers[i]; i++){ + if (marker.infoWindow) { + marker.infoWindow.close(); + } + } +}; + +GMaps.prototype.removeMarker = function(marker) { + for (var i = 0; i < this.markers.length; i++) { + if (this.markers[i] === marker) { + this.markers[i].setMap(null); + this.markers.splice(i, 1); + + if(this.markerClusterer) { + this.markerClusterer.removeMarker(marker); + } + + GMaps.fire('marker_removed', marker, this); + + break; + } + } + + return marker; +}; + +GMaps.prototype.removeMarkers = function (collection) { + var new_markers = []; + + if (typeof collection == 'undefined') { + for (var i = 0; i < this.markers.length; i++) { + var marker = this.markers[i]; + marker.setMap(null); + + GMaps.fire('marker_removed', marker, this); + } + + if(this.markerClusterer && this.markerClusterer.clearMarkers) { + this.markerClusterer.clearMarkers(); + } + + this.markers = new_markers; + } + else { + for (var i = 0; i < collection.length; i++) { + var index = this.markers.indexOf(collection[i]); + + if (index > -1) { + var marker = this.markers[index]; + marker.setMap(null); + + if(this.markerClusterer) { + this.markerClusterer.removeMarker(marker); + } + + GMaps.fire('marker_removed', marker, this); + } + } + + for (var i = 0; i < this.markers.length; i++) { + var marker = this.markers[i]; + if (marker.getMap() != null) { + new_markers.push(marker); + } + } + + this.markers = new_markers; + } +}; + +GMaps.prototype.drawOverlay = function(options) { + var overlay = new google.maps.OverlayView(), + auto_show = true; + + overlay.setMap(this.map); + + if (options.auto_show != null) { + auto_show = options.auto_show; + } + + overlay.onAdd = function() { + var el = document.createElement('div'); + + el.style.borderStyle = ""none""; + el.style.borderWidth = ""0px""; + el.style.position = ""absolute""; + el.style.zIndex = 100; + el.innerHTML = options.content; + + overlay.el = el; + + if (!options.layer) { + options.layer = 'overlayLayer'; + } + + var panes = this.getPanes(), + overlayLayer = panes[options.layer], + stop_overlay_events = ['contextmenu', 'DOMMouseScroll', 'dblclick', 'mousedown']; + + overlayLayer.appendChild(el); + + for (var ev = 0; ev < stop_overlay_events.length; ev++) { + (function(object, name) { + google.maps.event.addDomListener(object, name, function(e){ + if (navigator.userAgent.toLowerCase().indexOf('msie') != -1 && document.all) { + e.cancelBubble = true; + e.returnValue = false; + } + else { + e.stopPropagation(); + } + }); + })(el, stop_overlay_events[ev]); + } + + if (options.click) { + panes.overlayMouseTarget.appendChild(overlay.el); + google.maps.event.addDomListener(overlay.el, 'click', function() { + options.click.apply(overlay, [overlay]); + }); + } + + google.maps.event.trigger(this, 'ready'); + }; + + overlay.draw = function() { + var projection = this.getProjection(), + pixel = projection.fromLatLngToDivPixel(new google.maps.LatLng(options.lat, options.lng)); + + options.horizontalOffset = options.horizontalOffset || 0; + options.verticalOffset = options.verticalOffset || 0; + + var el = overlay.el, + content = el.children[0], + content_height = content.clientHeight, + content_width = content.clientWidth; + + switch (options.verticalAlign) { + case 'top': + el.style.top = (pixel.y - content_height + options.verticalOffset) + 'px'; + break; + default: + case 'middle': + el.style.top = (pixel.y - (content_height / 2) + options.verticalOffset) + 'px'; + break; + case 'bottom': + el.style.top = (pixel.y + options.verticalOffset) + 'px'; + break; + } + + switch (options.horizontalAlign) { + case 'left': + el.style.left = (pixel.x - content_width + options.horizontalOffset) + 'px'; + break; + default: + case 'center': + el.style.left = (pixel.x - (content_width / 2) + options.horizontalOffset) + 'px'; + break; + case 'right': + el.style.left = (pixel.x + options.horizontalOffset) + 'px'; + break; + } + + el.style.display = auto_show ? 'block' : 'none'; + + if (!auto_show) { + options.show.apply(this, [el]); + } + }; + + overlay.onRemove = function() { + var el = overlay.el; + + if (options.remove) { + options.remove.apply(this, [el]); + } + else { + overlay.el.parentNode.removeChild(overlay.el); + overlay.el = null; + } + }; + + this.overlays.push(overlay); + return overlay; +}; + +GMaps.prototype.removeOverlay = function(overlay) { + for (var i = 0; i < this.overlays.length; i++) { + if (this.overlays[i] === overlay) { + this.overlays[i].setMap(null); + this.overlays.splice(i, 1); + + break; + } + } +}; + +GMaps.prototype.removeOverlays = function() { + for (var i = 0, item; item = this.overlays[i]; i++) { + item.setMap(null); + } + + this.overlays = []; +}; + +GMaps.prototype.drawPolyline = function(options) { + var path = [], + points = options.path; + + if (points.length) { + if (points[0][0] === undefined) { + path = points; + } + else { + for (var i = 0, latlng; latlng = points[i]; i++) { + path.push(new google.maps.LatLng(latlng[0], latlng[1])); + } + } + } + + var polyline_options = { + map: this.map, + path: path, + strokeColor: options.strokeColor, + strokeOpacity: options.strokeOpacity, + strokeWeight: options.strokeWeight, + geodesic: options.geodesic, + clickable: true, + editable: false, + visible: true + }; + + if (options.hasOwnProperty(""clickable"")) { + polyline_options.clickable = options.clickable; + } + + if (options.hasOwnProperty(""editable"")) { + polyline_options.editable = options.editable; + } + + if (options.hasOwnProperty(""icons"")) { + polyline_options.icons = options.icons; + } + + if (options.hasOwnProperty(""zIndex"")) { + polyline_options.zIndex = options.zIndex; + } + + var polyline = new google.maps.Polyline(polyline_options); + + var polyline_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick']; + + for (var ev = 0; ev < polyline_events.length; ev++) { + (function(object, name) { + if (options[name]) { + google.maps.event.addListener(object, name, function(e){ + options[name].apply(this, [e]); + }); + } + })(polyline, polyline_events[ev]); + } + + this.polylines.push(polyline); + + GMaps.fire('polyline_added', polyline, this); + + return polyline; +}; + +GMaps.prototype.removePolyline = function(polyline) { + for (var i = 0; i < this.polylines.length; i++) { + if (this.polylines[i] === polyline) { + this.polylines[i].setMap(null); + this.polylines.splice(i, 1); + + GMaps.fire('polyline_removed', polyline, this); + + break; + } + } +}; + +GMaps.prototype.removePolylines = function() { + for (var i = 0, item; item = this.polylines[i]; i++) { + item.setMap(null); + } + + this.polylines = []; +}; + +GMaps.prototype.drawCircle = function(options) { + options = extend_object({ + map: this.map, + center: new google.maps.LatLng(options.lat, options.lng) + }, options); + + delete options.lat; + delete options.lng; + + var polygon = new google.maps.Circle(options), + polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick']; + + for (var ev = 0; ev < polygon_events.length; ev++) { + (function(object, name) { + if (options[name]) { + google.maps.event.addListener(object, name, function(e){ + options[name].apply(this, [e]); + }); + } + })(polygon, polygon_events[ev]); + } + + this.polygons.push(polygon); + + return polygon; +}; + +GMaps.prototype.drawRectangle = function(options) { + options = extend_object({ + map: this.map + }, options); + + var latLngBounds = new google.maps.LatLngBounds( + new google.maps.LatLng(options.bounds[0][0], options.bounds[0][1]), + new google.maps.LatLng(options.bounds[1][0], options.bounds[1][1]) + ); + + options.bounds = latLngBounds; + + var polygon = new google.maps.Rectangle(options), + polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick']; + + for (var ev = 0; ev < polygon_events.length; ev++) { + (function(object, name) { + if (options[name]) { + google.maps.event.addListener(object, name, function(e){ + options[name].apply(this, [e]); + }); + } + })(polygon, polygon_events[ev]); + } + + this.polygons.push(polygon); + + return polygon; +}; + +GMaps.prototype.drawPolygon = function(options) { + var useGeoJSON = false; + + if(options.hasOwnProperty(""useGeoJSON"")) { + useGeoJSON = options.useGeoJSON; + } + + delete options.useGeoJSON; + + options = extend_object({ + map: this.map + }, options); + + if (useGeoJSON == false) { + options.paths = [options.paths.slice(0)]; + } + + if (options.paths.length > 0) { + if (options.paths[0].length > 0) { + options.paths = array_flat(array_map(options.paths, arrayToLatLng, useGeoJSON)); + } + } + + var polygon = new google.maps.Polygon(options), + polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick']; + + for (var ev = 0; ev < polygon_events.length; ev++) { + (function(object, name) { + if (options[name]) { + google.maps.event.addListener(object, name, function(e){ + options[name].apply(this, [e]); + }); + } + })(polygon, polygon_events[ev]); + } + + this.polygons.push(polygon); + + GMaps.fire('polygon_added', polygon, this); + + return polygon; +}; + +GMaps.prototype.removePolygon = function(polygon) { + for (var i = 0; i < this.polygons.length; i++) { + if (this.polygons[i] === polygon) { + this.polygons[i].setMap(null); + this.polygons.splice(i, 1); + + GMaps.fire('polygon_removed', polygon, this); + + break; + } + } +}; + +GMaps.prototype.removePolygons = function() { + for (var i = 0, item; item = this.polygons[i]; i++) { + item.setMap(null); + } + + this.polygons = []; +}; + +GMaps.prototype.getFromFusionTables = function(options) { + var events = options.events; + + delete options.events; + + var fusion_tables_options = options, + layer = new google.maps.FusionTablesLayer(fusion_tables_options); + + for (var ev in events) { + (function(object, name) { + google.maps.event.addListener(object, name, function(e) { + events[name].apply(this, [e]); + }); + })(layer, ev); + } + + this.layers.push(layer); + + return layer; +}; + +GMaps.prototype.loadFromFusionTables = function(options) { + var layer = this.getFromFusionTables(options); + layer.setMap(this.map); + + return layer; +}; + +GMaps.prototype.getFromKML = function(options) { + var url = options.url, + events = options.events; + + delete options.url; + delete options.events; + + var kml_options = options, + layer = new google.maps.KmlLayer(url, kml_options); + + for (var ev in events) { + (function(object, name) { + google.maps.event.addListener(object, name, function(e) { + events[name].apply(this, [e]); + }); + })(layer, ev); + } + + this.layers.push(layer); + + return layer; +}; + +GMaps.prototype.loadFromKML = function(options) { + var layer = this.getFromKML(options); + layer.setMap(this.map); + + return layer; +}; + +GMaps.prototype.addLayer = function(layerName, options) { + //var default_layers = ['weather', 'clouds', 'traffic', 'transit', 'bicycling', 'panoramio', 'places']; + options = options || {}; + var layer; + + switch(layerName) { + case 'weather': this.singleLayers.weather = layer = new google.maps.weather.WeatherLayer(); + break; + case 'clouds': this.singleLayers.clouds = layer = new google.maps.weather.CloudLayer(); + break; + case 'traffic': this.singleLayers.traffic = layer = new google.maps.TrafficLayer(); + break; + case 'transit': this.singleLayers.transit = layer = new google.maps.TransitLayer(); + break; + case 'bicycling': this.singleLayers.bicycling = layer = new google.maps.BicyclingLayer(); + break; + case 'panoramio': + this.singleLayers.panoramio = layer = new google.maps.panoramio.PanoramioLayer(); + layer.setTag(options.filter); + delete options.filter; + + //click event + if (options.click) { + google.maps.event.addListener(layer, 'click', function(event) { + options.click(event); + delete options.click; + }); + } + break; + case 'places': + this.singleLayers.places = layer = new google.maps.places.PlacesService(this.map); + + //search, nearbySearch, radarSearch callback, Both are the same + if (options.search || options.nearbySearch || options.radarSearch) { + var placeSearchRequest = { + bounds : options.bounds || null, + keyword : options.keyword || null, + location : options.location || null, + name : options.name || null, + radius : options.radius || null, + rankBy : options.rankBy || null, + types : options.types || null + }; + + if (options.radarSearch) { + layer.radarSearch(placeSearchRequest, options.radarSearch); + } + + if (options.search) { + layer.search(placeSearchRequest, options.search); + } + + if (options.nearbySearch) { + layer.nearbySearch(placeSearchRequest, options.nearbySearch); + } + } + + //textSearch callback + if (options.textSearch) { + var textSearchRequest = { + bounds : options.bounds || null, + location : options.location || null, + query : options.query || null, + radius : options.radius || null + }; + + layer.textSearch(textSearchRequest, options.textSearch); + } + break; + } + + if (layer !== undefined) { + if (typeof layer.setOptions == 'function') { + layer.setOptions(options); + } + if (typeof layer.setMap == 'function') { + layer.setMap(this.map); + } + + return layer; + } +}; + +GMaps.prototype.removeLayer = function(layer) { + if (typeof(layer) == ""string"" && this.singleLayers[layer] !== undefined) { + this.singleLayers[layer].setMap(null); + + delete this.singleLayers[layer]; + } + else { + for (var i = 0; i < this.layers.length; i++) { + if (this.layers[i] === layer) { + this.layers[i].setMap(null); + this.layers.splice(i, 1); + + break; + } + } + } +}; + +var travelMode, unitSystem; + +GMaps.prototype.getRoutes = function(options) { + switch (options.travelMode) { + case 'bicycling': + travelMode = google.maps.TravelMode.BICYCLING; + break; + case 'transit': + travelMode = google.maps.TravelMode.TRANSIT; + break; + case 'driving': + travelMode = google.maps.TravelMode.DRIVING; + break; + default: + travelMode = google.maps.TravelMode.WALKING; + break; + } + + if (options.unitSystem === 'imperial') { + unitSystem = google.maps.UnitSystem.IMPERIAL; + } + else { + unitSystem = google.maps.UnitSystem.METRIC; + } + + var base_options = { + avoidHighways: false, + avoidTolls: false, + optimizeWaypoints: false, + waypoints: [] + }, + request_options = extend_object(base_options, options); + + request_options.origin = /string/.test(typeof options.origin) ? options.origin : new google.maps.LatLng(options.origin[0], options.origin[1]); + request_options.destination = /string/.test(typeof options.destination) ? options.destination : new google.maps.LatLng(options.destination[0], options.destination[1]); + request_options.travelMode = travelMode; + request_options.unitSystem = unitSystem; + + delete request_options.callback; + delete request_options.error; + + var self = this, + routes = [], + service = new google.maps.DirectionsService(); + + service.route(request_options, function(result, status) { + if (status === google.maps.DirectionsStatus.OK) { + for (var r in result.routes) { + if (result.routes.hasOwnProperty(r)) { + routes.push(result.routes[r]); + } + } + + if (options.callback) { + options.callback(routes, result, status); + } + } + else { + if (options.error) { + options.error(result, status); + } + } + }); +}; + +GMaps.prototype.removeRoutes = function() { + this.routes.length = 0; +}; + +GMaps.prototype.getElevations = function(options) { + options = extend_object({ + locations: [], + path : false, + samples : 256 + }, options); + + if (options.locations.length > 0) { + if (options.locations[0].length > 0) { + options.locations = array_flat(array_map([options.locations], arrayToLatLng, false)); + } + } + + var callback = options.callback; + delete options.callback; + + var service = new google.maps.ElevationService(); + + //location request + if (!options.path) { + delete options.path; + delete options.samples; + + service.getElevationForLocations(options, function(result, status) { + if (callback && typeof(callback) === ""function"") { + callback(result, status); + } + }); + //path request + } else { + var pathRequest = { + path : options.locations, + samples : options.samples + }; + + service.getElevationAlongPath(pathRequest, function(result, status) { + if (callback && typeof(callback) === ""function"") { + callback(result, status); + } + }); + } +}; + +GMaps.prototype.cleanRoute = GMaps.prototype.removePolylines; + +GMaps.prototype.renderRoute = function(options, renderOptions) { + var self = this, + panel = ((typeof renderOptions.panel === 'string') ? document.getElementById(renderOptions.panel.replace('#', '')) : renderOptions.panel), + display; + + renderOptions.panel = panel; + renderOptions = extend_object({ + map: this.map + }, renderOptions); + display = new google.maps.DirectionsRenderer(renderOptions); + + this.getRoutes({ + origin: options.origin, + destination: options.destination, + travelMode: options.travelMode, + waypoints: options.waypoints, + unitSystem: options.unitSystem, + error: options.error, + avoidHighways: options.avoidHighways, + avoidTolls: options.avoidTolls, + optimizeWaypoints: options.optimizeWaypoints, + callback: function(routes, response, status) { + if (status === google.maps.DirectionsStatus.OK) { + display.setDirections(response); + } + } + }); +}; + +GMaps.prototype.drawRoute = function(options) { + var self = this; + + this.getRoutes({ + origin: options.origin, + destination: options.destination, + travelMode: options.travelMode, + waypoints: options.waypoints, + unitSystem: options.unitSystem, + error: options.error, + avoidHighways: options.avoidHighways, + avoidTolls: options.avoidTolls, + optimizeWaypoints: options.optimizeWaypoints, + callback: function(routes) { + if (routes.length > 0) { + var polyline_options = { + path: routes[routes.length - 1].overview_path, + strokeColor: options.strokeColor, + strokeOpacity: options.strokeOpacity, + strokeWeight: options.strokeWeight + }; + + if (options.hasOwnProperty(""icons"")) { + polyline_options.icons = options.icons; + } + + self.drawPolyline(polyline_options); + + if (options.callback) { + options.callback(routes[routes.length - 1]); + } + } + } + }); +}; + +GMaps.prototype.travelRoute = function(options) { + if (options.origin && options.destination) { + this.getRoutes({ + origin: options.origin, + destination: options.destination, + travelMode: options.travelMode, + waypoints : options.waypoints, + unitSystem: options.unitSystem, + error: options.error, + callback: function(e) { + //start callback + if (e.length > 0 && options.start) { + options.start(e[e.length - 1]); + } + + //step callback + if (e.length > 0 && options.step) { + var route = e[e.length - 1]; + if (route.legs.length > 0) { + var steps = route.legs[0].steps; + for (var i = 0, step; step = steps[i]; i++) { + step.step_number = i; + options.step(step, (route.legs[0].steps.length - 1)); + } + } + } + + //end callback + if (e.length > 0 && options.end) { + options.end(e[e.length - 1]); + } + } + }); + } + else if (options.route) { + if (options.route.legs.length > 0) { + var steps = options.route.legs[0].steps; + for (var i = 0, step; step = steps[i]; i++) { + step.step_number = i; + options.step(step); + } + } + } +}; + +GMaps.prototype.drawSteppedRoute = function(options) { + var self = this; + + if (options.origin && options.destination) { + this.getRoutes({ + origin: options.origin, + destination: options.destination, + travelMode: options.travelMode, + waypoints : options.waypoints, + error: options.error, + callback: function(e) { + //start callback + if (e.length > 0 && options.start) { + options.start(e[e.length - 1]); + } + + //step callback + if (e.length > 0 && options.step) { + var route = e[e.length - 1]; + if (route.legs.length > 0) { + var steps = route.legs[0].steps; + for (var i = 0, step; step = steps[i]; i++) { + step.step_number = i; + var polyline_options = { + path: step.path, + strokeColor: options.strokeColor, + strokeOpacity: options.strokeOpacity, + strokeWeight: options.strokeWeight + }; + + if (options.hasOwnProperty(""icons"")) { + polyline_options.icons = options.icons; + } + + self.drawPolyline(polyline_options); + options.step(step, (route.legs[0].steps.length - 1)); + } + } + } + + //end callback + if (e.length > 0 && options.end) { + options.end(e[e.length - 1]); + } + } + }); + } + else if (options.route) { + if (options.route.legs.length > 0) { + var steps = options.route.legs[0].steps; + for (var i = 0, step; step = steps[i]; i++) { + step.step_number = i; + var polyline_options = { + path: step.path, + strokeColor: options.strokeColor, + strokeOpacity: options.strokeOpacity, + strokeWeight: options.strokeWeight + }; + + if (options.hasOwnProperty(""icons"")) { + polyline_options.icons = options.icons; + } + + self.drawPolyline(polyline_options); + options.step(step); + } + } + } +}; + +GMaps.Route = function(options) { + this.origin = options.origin; + this.destination = options.destination; + this.waypoints = options.waypoints; + + this.map = options.map; + this.route = options.route; + this.step_count = 0; + this.steps = this.route.legs[0].steps; + this.steps_length = this.steps.length; + + var polyline_options = { + path: new google.maps.MVCArray(), + strokeColor: options.strokeColor, + strokeOpacity: options.strokeOpacity, + strokeWeight: options.strokeWeight + }; + + if (options.hasOwnProperty(""icons"")) { + polyline_options.icons = options.icons; + } + + this.polyline = this.map.drawPolyline(polyline_options).getPath(); +}; + +GMaps.Route.prototype.getRoute = function(options) { + var self = this; + + this.map.getRoutes({ + origin : this.origin, + destination : this.destination, + travelMode : options.travelMode, + waypoints : this.waypoints || [], + error: options.error, + callback : function() { + self.route = e[0]; + + if (options.callback) { + options.callback.call(self); + } + } + }); +}; + +GMaps.Route.prototype.back = function() { + if (this.step_count > 0) { + this.step_count--; + var path = this.route.legs[0].steps[this.step_count].path; + + for (var p in path){ + if (path.hasOwnProperty(p)){ + this.polyline.pop(); + } + } + } +}; + +GMaps.Route.prototype.forward = function() { + if (this.step_count < this.steps_length) { + var path = this.route.legs[0].steps[this.step_count].path; + + for (var p in path){ + if (path.hasOwnProperty(p)){ + this.polyline.push(path[p]); + } + } + this.step_count++; + } +}; + +GMaps.prototype.checkGeofence = function(lat, lng, fence) { + return fence.containsLatLng(new google.maps.LatLng(lat, lng)); +}; + +GMaps.prototype.checkMarkerGeofence = function(marker, outside_callback) { + if (marker.fences) { + for (var i = 0, fence; fence = marker.fences[i]; i++) { + var pos = marker.getPosition(); + if (!this.checkGeofence(pos.lat(), pos.lng(), fence)) { + outside_callback(marker, fence); + } + } + } +}; + +GMaps.prototype.toImage = function(options) { + var options = options || {}, + static_map_options = {}; + + static_map_options['size'] = options['size'] || [this.el.clientWidth, this.el.clientHeight]; + static_map_options['lat'] = this.getCenter().lat(); + static_map_options['lng'] = this.getCenter().lng(); + + if (this.markers.length > 0) { + static_map_options['markers'] = []; + + for (var i = 0; i < this.markers.length; i++) { + static_map_options['markers'].push({ + lat: this.markers[i].getPosition().lat(), + lng: this.markers[i].getPosition().lng() + }); + } + } + + if (this.polylines.length > 0) { + var polyline = this.polylines[0]; + + static_map_options['polyline'] = {}; + static_map_options['polyline']['path'] = google.maps.geometry.encoding.encodePath(polyline.getPath()); + static_map_options['polyline']['strokeColor'] = polyline.strokeColor + static_map_options['polyline']['strokeOpacity'] = polyline.strokeOpacity + static_map_options['polyline']['strokeWeight'] = polyline.strokeWeight + } + + return GMaps.staticMapURL(static_map_options); +}; + +GMaps.staticMapURL = function(options){ + var parameters = [], + data, + static_root = (location.protocol === 'file:' ? 'http:' : location.protocol ) + '//maps.googleapis.com/maps/api/staticmap'; + + if (options.url) { + static_root = options.url; + delete options.url; + } + + static_root += '?'; + + var markers = options.markers; + + delete options.markers; + + if (!markers && options.marker) { + markers = [options.marker]; + delete options.marker; + } + + var styles = options.styles; + + delete options.styles; + + var polyline = options.polyline; + delete options.polyline; + + /** Map options **/ + if (options.center) { + parameters.push('center=' + options.center); + delete options.center; + } + else if (options.address) { + parameters.push('center=' + options.address); + delete options.address; + } + else if (options.lat) { + parameters.push(['center=', options.lat, ',', options.lng].join('')); + delete options.lat; + delete options.lng; + } + else if (options.visible) { + var visible = encodeURI(options.visible.join('|')); + parameters.push('visible=' + visible); + } + + var size = options.size; + if (size) { + if (size.join) { + size = size.join('x'); + } + delete options.size; + } + else { + size = '630x300'; + } + parameters.push('size=' + size); + + if (!options.zoom && options.zoom !== false) { + options.zoom = 15; + } + + var sensor = options.hasOwnProperty('sensor') ? !!options.sensor : true; + delete options.sensor; + parameters.push('sensor=' + sensor); + + for (var param in options) { + if (options.hasOwnProperty(param)) { + parameters.push(param + '=' + options[param]); + } + } + + /** Markers **/ + if (markers) { + var marker, loc; + + for (var i = 0; data = markers[i]; i++) { + marker = []; + + if (data.size && data.size !== 'normal') { + marker.push('size:' + data.size); + delete data.size; + } + else if (data.icon) { + marker.push('icon:' + encodeURI(data.icon)); + delete data.icon; + } + + if (data.color) { + marker.push('color:' + data.color.replace('#', '0x')); + delete data.color; + } + + if (data.label) { + marker.push('label:' + data.label[0].toUpperCase()); + delete data.label; + } + + loc = (data.address ? data.address : data.lat + ',' + data.lng); + delete data.address; + delete data.lat; + delete data.lng; + + for(var param in data){ + if (data.hasOwnProperty(param)) { + marker.push(param + ':' + data[param]); + } + } + + if (marker.length || i === 0) { + marker.push(loc); + marker = marker.join('|'); + parameters.push('markers=' + encodeURI(marker)); + } + // New marker without styles + else { + marker = parameters.pop() + encodeURI('|' + loc); + parameters.push(marker); + } + } + } + + /** Map Styles **/ + if (styles) { + for (var i = 0; i < styles.length; i++) { + var styleRule = []; + if (styles[i].featureType){ + styleRule.push('feature:' + styles[i].featureType.toLowerCase()); + } + + if (styles[i].elementType) { + styleRule.push('element:' + styles[i].elementType.toLowerCase()); + } + + for (var j = 0; j < styles[i].stylers.length; j++) { + for (var p in styles[i].stylers[j]) { + var ruleArg = styles[i].stylers[j][p]; + if (p == 'hue' || p == 'color') { + ruleArg = '0x' + ruleArg.substring(1); + } + styleRule.push(p + ':' + ruleArg); + } + } + + var rule = styleRule.join('|'); + if (rule != '') { + parameters.push('style=' + rule); + } + } + } + + /** Polylines **/ + function parseColor(color, opacity) { + if (color[0] === '#'){ + color = color.replace('#', '0x'); + + if (opacity) { + opacity = parseFloat(opacity); + opacity = Math.min(1, Math.max(opacity, 0)); + if (opacity === 0) { + return '0x00000000'; + } + opacity = (opacity * 255).toString(16); + if (opacity.length === 1) { + opacity += opacity; + } + + color = color.slice(0,8) + opacity; + } + } + return color; + } + + if (polyline) { + data = polyline; + polyline = []; + + if (data.strokeWeight) { + polyline.push('weight:' + parseInt(data.strokeWeight, 10)); + } + + if (data.strokeColor) { + var color = parseColor(data.strokeColor, data.strokeOpacity); + polyline.push('color:' + color); + } + + if (data.fillColor) { + var fillcolor = parseColor(data.fillColor, data.fillOpacity); + polyline.push('fillcolor:' + fillcolor); + } + + var path = data.path; + if (path.join) { + for (var j=0, pos; pos=path[j]; j++) { + polyline.push(pos.join(',')); + } + } + else { + polyline.push('enc:' + path); + } + + polyline = polyline.join('|'); + parameters.push('path=' + encodeURI(polyline)); + } + + /** Retina support **/ + var dpi = window.devicePixelRatio || 1; + parameters.push('scale=' + dpi); + + parameters = parameters.join('&'); + return static_root + parameters; +}; + +GMaps.prototype.addMapType = function(mapTypeId, options) { + if (options.hasOwnProperty(""getTileUrl"") && typeof(options[""getTileUrl""]) == ""function"") { + options.tileSize = options.tileSize || new google.maps.Size(256, 256); + + var mapType = new google.maps.ImageMapType(options); + + this.map.mapTypes.set(mapTypeId, mapType); + } + else { + throw ""'getTileUrl' function required.""; + } +}; + +GMaps.prototype.addOverlayMapType = function(options) { + if (options.hasOwnProperty(""getTile"") && typeof(options[""getTile""]) == ""function"") { + var overlayMapTypeIndex = options.index; + + delete options.index; + + this.map.overlayMapTypes.insertAt(overlayMapTypeIndex, options); + } + else { + throw ""'getTile' function required.""; + } +}; + +GMaps.prototype.removeOverlayMapType = function(overlayMapTypeIndex) { + this.map.overlayMapTypes.removeAt(overlayMapTypeIndex); +}; + +GMaps.prototype.addStyle = function(options) { + var styledMapType = new google.maps.StyledMapType(options.styles, { name: options.styledMapName }); + + this.map.mapTypes.set(options.mapTypeId, styledMapType); +}; + +GMaps.prototype.setStyle = function(mapTypeId) { + this.map.setMapTypeId(mapTypeId); +}; + +GMaps.prototype.createPanorama = function(streetview_options) { + if (!streetview_options.hasOwnProperty('lat') || !streetview_options.hasOwnProperty('lng')) { + streetview_options.lat = this.getCenter().lat(); + streetview_options.lng = this.getCenter().lng(); + } + + this.panorama = GMaps.createPanorama(streetview_options); + + this.map.setStreetView(this.panorama); + + return this.panorama; +}; + +GMaps.createPanorama = function(options) { + var el = getElementById(options.el, options.context); + + options.position = new google.maps.LatLng(options.lat, options.lng); + + delete options.el; + delete options.context; + delete options.lat; + delete options.lng; + + var streetview_events = ['closeclick', 'links_changed', 'pano_changed', 'position_changed', 'pov_changed', 'resize', 'visible_changed'], + streetview_options = extend_object({visible : true}, options); + + for (var i = 0; i < streetview_events.length; i++) { + delete streetview_options[streetview_events[i]]; + } + + var panorama = new google.maps.StreetViewPanorama(el, streetview_options); + + for (var i = 0; i < streetview_events.length; i++) { + (function(object, name) { + if (options[name]) { + google.maps.event.addListener(object, name, function(){ + options[name].apply(this); + }); + } + })(panorama, streetview_events[i]); + } + + return panorama; +}; + +GMaps.prototype.on = function(event_name, handler) { + return GMaps.on(event_name, this, handler); +}; + +GMaps.prototype.off = function(event_name) { + GMaps.off(event_name, this); +}; + +GMaps.prototype.once = function(event_name, handler) { + return GMaps.once(event_name, this, handler); +}; + +GMaps.custom_events = ['marker_added', 'marker_removed', 'polyline_added', 'polyline_removed', 'polygon_added', 'polygon_removed', 'geolocated', 'geolocation_failed']; + +GMaps.on = function(event_name, object, handler) { + if (GMaps.custom_events.indexOf(event_name) == -1) { + if(object instanceof GMaps) object = object.map; + return google.maps.event.addListener(object, event_name, handler); + } + else { + var registered_event = { + handler : handler, + eventName : event_name + }; + + object.registered_events[event_name] = object.registered_events[event_name] || []; + object.registered_events[event_name].push(registered_event); + + return registered_event; + } +}; + +GMaps.off = function(event_name, object) { + if (GMaps.custom_events.indexOf(event_name) == -1) { + if(object instanceof GMaps) object = object.map; + google.maps.event.clearListeners(object, event_name); + } + else { + object.registered_events[event_name] = []; + } +}; + +GMaps.once = function(event_name, object, handler) { + if (GMaps.custom_events.indexOf(event_name) == -1) { + if(object instanceof GMaps) object = object.map; + return google.maps.event.addListenerOnce(object, event_name, handler); + } +}; + +GMaps.fire = function(event_name, object, scope) { + if (GMaps.custom_events.indexOf(event_name) == -1) { + google.maps.event.trigger(object, event_name, Array.prototype.slice.apply(arguments).slice(2)); + } + else { + if(event_name in scope.registered_events) { + var firing_events = scope.registered_events[event_name]; + + for(var i = 0; i < firing_events.length; i++) { + (function(handler, scope, object) { + handler.apply(scope, [object]); + })(firing_events[i]['handler'], scope, object); + } + } + } +}; + +GMaps.geolocate = function(options) { + var complete_callback = options.always || options.complete; + + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(function(position) { + options.success(position); + + if (complete_callback) { + complete_callback(); + } + }, function(error) { + options.error(error); + + if (complete_callback) { + complete_callback(); + } + }, options.options); + } + else { + options.not_supported(); + + if (complete_callback) { + complete_callback(); + } + } +}; + +GMaps.geocode = function(options) { + this.geocoder = new google.maps.Geocoder(); + var callback = options.callback; + if (options.hasOwnProperty('lat') && options.hasOwnProperty('lng')) { + options.latLng = new google.maps.LatLng(options.lat, options.lng); + } + + delete options.lat; + delete options.lng; + delete options.callback; + + this.geocoder.geocode(options, function(results, status) { + callback(results, status); + }); +}; + +if (typeof window.google === 'object' && window.google.maps) { + //========================== + // Polygon containsLatLng + // https://github.com/tparkin/Google-Maps-Point-in-Polygon + // Poygon getBounds extension - google-maps-extensions + // http://code.google.com/p/google-maps-extensions/source/browse/google.maps.Polygon.getBounds.js + if (!google.maps.Polygon.prototype.getBounds) { + google.maps.Polygon.prototype.getBounds = function(latLng) { + var bounds = new google.maps.LatLngBounds(); + var paths = this.getPaths(); + var path; + + for (var p = 0; p < paths.getLength(); p++) { + path = paths.getAt(p); + for (var i = 0; i < path.getLength(); i++) { + bounds.extend(path.getAt(i)); + } + } + + return bounds; + }; + } + + if (!google.maps.Polygon.prototype.containsLatLng) { + // Polygon containsLatLng - method to determine if a latLng is within a polygon + google.maps.Polygon.prototype.containsLatLng = function(latLng) { + // Exclude points outside of bounds as there is no way they are in the poly + var bounds = this.getBounds(); + + if (bounds !== null && !bounds.contains(latLng)) { + return false; + } + + // Raycast point in polygon method + var inPoly = false; + + var numPaths = this.getPaths().getLength(); + for (var p = 0; p < numPaths; p++) { + var path = this.getPaths().getAt(p); + var numPoints = path.getLength(); + var j = numPoints - 1; + + for (var i = 0; i < numPoints; i++) { + var vertex1 = path.getAt(i); + var vertex2 = path.getAt(j); + + if (vertex1.lng() < latLng.lng() && vertex2.lng() >= latLng.lng() || vertex2.lng() < latLng.lng() && vertex1.lng() >= latLng.lng()) { + if (vertex1.lat() + (latLng.lng() - vertex1.lng()) / (vertex2.lng() - vertex1.lng()) * (vertex2.lat() - vertex1.lat()) < latLng.lat()) { + inPoly = !inPoly; + } + } + + j = i; + } + } + + return inPoly; + }; + } + + if (!google.maps.Circle.prototype.containsLatLng) { + google.maps.Circle.prototype.containsLatLng = function(latLng) { + if (google.maps.geometry) { + return google.maps.geometry.spherical.computeDistanceBetween(this.getCenter(), latLng) <= this.getRadius(); + } + else { + return true; + } + }; + } + + google.maps.Rectangle.prototype.containsLatLng = function(latLng) { + return this.getBounds().contains(latLng); + }; + + google.maps.LatLngBounds.prototype.containsLatLng = function(latLng) { + return this.contains(latLng); + }; + + google.maps.Marker.prototype.setFences = function(fences) { + this.fences = fences; + }; + + google.maps.Marker.prototype.addFence = function(fence) { + this.fences.push(fence); + }; + + google.maps.Marker.prototype.getId = function() { + return this['__gm_id']; + }; +} + +//========================== +// Array indexOf +// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { + ""use strict""; + if (this == null) { + throw new TypeError(); + } + var t = Object(this); + var len = t.length >>> 0; + if (len === 0) { + return -1; + } + var n = 0; + if (arguments.length > 1) { + n = Number(arguments[1]); + if (n != n) { // shortcut for verifying if it's NaN + n = 0; + } else if (n != 0 && n != Infinity && n != -Infinity) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (; k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + } +} + +return GMaps; +})); +"; + #endregion + } +} diff --git a/PlatformSamples/AspNetCoreMvc/Startup.cs b/PlatformSamples/AspNetCoreMvc/Startup.cs index babc056..c71c9a1 100644 --- a/PlatformSamples/AspNetCoreMvc/Startup.cs +++ b/PlatformSamples/AspNetCoreMvc/Startup.cs @@ -39,7 +39,8 @@ public void Configure (IApplicationBuilder app, IHostingEnvironment env) app.UseOoui (); - Xamarin.Forms.Forms.Init (); + Xamarin.Forms.Forms.Init (); + Xamarin.FormsMaps.Init("YOUR_GOOGLE_MAPS_API_KEY_HERE"); app.UseMvc (routes => { routes.MapRoute ( @@ -48,4 +49,4 @@ public void Configure (IApplicationBuilder app, IHostingEnvironment env) }); } } -} \ No newline at end of file +} diff --git a/Samples/MapSample.cs b/Samples/MapSample.cs new file mode 100644 index 0000000..72b9cf8 --- /dev/null +++ b/Samples/MapSample.cs @@ -0,0 +1,92 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Ooui; + +namespace Samples +{ + public class MapSample : ISample + { + public string Title => "Map Sample"; + + GoogleMap MakeMap () + { + var map = new GoogleMap (mapType: GoogleMap.MapType.HYBRID,firstMapControlOnPage: true) { + APIKey = "YOUR_GOOGLE_MAPS_API_KEY_HERE", + }; + + + //Add Marker from code on creating of view + map.AddMarker(new GoogleMap.MapMarker() + { + lat = 51.511884, + lng = -0.195421, + title = "Testing Markers on Load", + infoWindow = new GoogleMap.MapInfoWindow() + { + content = "This is the first pin placement (I think it is at Kensington Palace)" + } + }); + + ThreadPool.QueueUserWorkItem((state) => + { + //Add Marker from code after view has been loaded + Thread.Sleep(5000); + map.AddMarker(new GoogleMap.MapMarker + { + lat = 51.5073346, + lng = -0.1276831, + title = "Testing Markers", + infoWindow = new GoogleMap.MapInfoWindow + { + content = "This is a random pin placement" + } + }); + }); + ThreadPool.QueueUserWorkItem((state) => + { + //Change map center location after ten seconds and add a marker there + Thread.Sleep(10000); + + var pos = new GoogleMap.Position + { + latitude = 40.6892, + longitude = 74.0445, + }; + map.CenterOn(pos); + map.AddMarker(new GoogleMap.MapMarker + { + lat = pos.latitude, + lng = pos.longitude, + title = "Kyrgyzstan", + infoWindow = new GoogleMap.MapInfoWindow + { + content = "This should be in Kyrgyzstan" + } + }); + ThreadPool.QueueUserWorkItem(async (state2) => + { + await Task.Delay(5000); + + var pos2 = await map.GetCenter(); + }); + }); + return map; + } + + public void Publish () + { + var b = MakeMap (); + + UI.Publish ("/shared-map", b); + UI.Publish ("/map", MakeMap); + } + + public Element CreateElement () + { + return MakeMap (); + } + } +} + + diff --git a/Samples/Maps/MapsPage.xaml b/Samples/Maps/MapsPage.xaml new file mode 100644 index 0000000..aa34ef4 --- /dev/null +++ b/Samples/Maps/MapsPage.xaml @@ -0,0 +1,8 @@ + + + + + + diff --git a/Samples/Maps/MapsPage.xaml.cs b/Samples/Maps/MapsPage.xaml.cs new file mode 100644 index 0000000..a60feeb --- /dev/null +++ b/Samples/Maps/MapsPage.xaml.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Xamarin.Forms; +using Xamarin.Forms.Maps; +using Xamarin.Forms.Xaml; + +namespace Samples.Maps +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class MapsPage : ContentPage + { + public MapsPage () + { + InitializeComponent (); + + PrepareMap(); + } + + private Map _map = null; + private void PrepareMap() + { + _map = new Map(MapSpan.FromCenterAndRadius(new Position(40.6892494, -74.0466891), Distance.FromMeters(100))) + { + MapType = MapType.Hybrid, + WidthRequest = 320, + HeightRequest = 200, + }; + _map.Pins.Clear(); + _map.Pins.Add(new Pin() + { + Position = new Position(40.6892494, -74.0466891), + Label = "Statue of Liberty", + Address = "This is the Statue of Liberty" + }); + MapHolder.Children.Add(_map); + } + } +} diff --git a/Samples/Samples.csproj b/Samples/Samples.csproj index 1492bbd..8849844 100644 --- a/Samples/Samples.csproj +++ b/Samples/Samples.csproj @@ -5,12 +5,14 @@ + + @@ -35,6 +37,9 @@ Code + + Code + @@ -50,6 +55,9 @@ MSBuild:UpdateDesignTimeXaml + + MSBuild:UpdateDesignTimeXaml + MSBuild:Compile @@ -66,6 +74,7 @@ + diff --git a/Samples/XamMapsSample.cs b/Samples/XamMapsSample.cs new file mode 100644 index 0000000..d40173a --- /dev/null +++ b/Samples/XamMapsSample.cs @@ -0,0 +1,60 @@ +using Ooui; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using Xamarin.Forms; +using Xamarin.Forms.Maps; + +namespace Samples +{ + public class XamMapsSample : ISample + { + public string Title => "Xamarin.Forms Maps"; + + public Ooui.Element CreateElement() + { + var page = new Samples.Maps.MapsPage(); + ThreadPool.QueueUserWorkItem((state) => + { + Thread.Sleep(25000); + var map = ((page.Content as StackLayout).Children[1] as Map); + var eiffelTower = new Position(48.858093, 2.294694); + Pin pin = new Pin() + { + Position = eiffelTower, + Label = "New Pin", + Address = "Somewhere", + }; + map.Pins.Add(pin); + + Thread.Sleep(10000); + + map.MoveToRegion(MapSpan.FromCenterAndRadius(eiffelTower, Distance.FromMeters(100))); + + Thread.Sleep(10000); + + map.Pins.Remove(pin); + + Thread.Sleep(10000); + + map.Pins.Add(pin); + + Thread.Sleep(10000); + + map.Pins.Clear(); + + Thread.Sleep(10000); + + map.MapType = MapType.Street; + + }); + return page.GetOouiElement(); + } + + public void Publish() + { + UI.Publish("/xammaps", CreateElement); + } + } +}