using System; using System.Collections.ObjectModel; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Media; using Geocoding; using Geocoding.Google; using InABox.Clients; using InABox.Core; using Nominatim.API.Geocoders; using Nominatim.API.Models; using Nominatim.API.Web; using Syncfusion.UI.Xaml.Maps; using Address = InABox.Core.Address; using Point = System.Windows.Point; namespace InABox.Wpf.DynamicGrid; public partial class GeofenceEditor : Window { public Address Address { get; private set; } GeoFenceDefinition _definition = null; private ImageryLayer ImageryLayer; public MapPolygon Polygon; public GeofenceEditor(Address address, bool canEdit) { Address = address; _definition = Serialization.Deserialize(address.Geofence) ?? new GeoFenceDefinition(); InitializeComponent(); ImageryLayer = string.IsNullOrWhiteSpace(CoreUtils.GoogleAPIKey) ? new ImageryLayer() : new GoogleImageryLayer() { Type = GoogleImageryLayerType.Satellite }; ImageryLayer.Radius = 10; Map.Layers.Add(ImageryLayer); Polygon = new MapPolygon() { Fill = new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 }, Stroke = new SolidColorBrush(Colors.Firebrick), StrokeThickness = 0.75, }; var layer = new SubShapeFileLayer() { MapElements = new ObservableCollection([Polygon]) }; ImageryLayer.SubShapeFileLayers = new ObservableCollection([layer]); SetGeometry.Visibility = canEdit ? Visibility.Visible : Visibility.Collapsed; SetRadius.Visibility = canEdit ? Visibility.Visible : Visibility.Collapsed; SearchBar.Visibility = canEdit ? Visibility.Visible : Visibility.Collapsed; Street.Text = address.Street; City.Text = address.City; State.Text = address.State; PostCode.Text = Address.PostCode; Title = $"Geofence Editor ({(string.IsNullOrWhiteSpace(CoreUtils.GoogleAPIKey) ? "OSM" : "Google")})"; if (canEdit && string.IsNullOrWhiteSpace(Address.Geofence)) Task.Run(CheckAddress); else SetupMap(); } private class HttpClientFactory : IHttpClientFactory { private static HttpClient? _client; public HttpClient CreateClient(string name) { _client ??= new HttpClient(); return _client; } } private static HttpClientFactory? _httpClientFactory = null; private async Task CheckAddress() { if (!string.IsNullOrWhiteSpace(CoreUtils.GoogleAPIKey)) { IGeocoder geocoder = new GoogleGeocoder(CoreUtils.GoogleAPIKey); try { var matches = await geocoder.GeocodeAsync( $"{Address.Street}, {Address.City} {Address.State} {Address.PostCode} Australia"); var match = matches.FirstOrDefault(); if (match != null) { Address.Location.Longitude = match.Coordinates.Longitude; Address.Location.Latitude = match.Coordinates.Latitude; if (match is GoogleAddress gAdd) { _definition.Coordinates.Clear(); _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.NorthEast.Latitude, gAdd.Bounds.NorthEast.Longitude)); _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.NorthEast.Latitude, gAdd.Bounds.SouthWest.Longitude)); _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.SouthWest.Latitude, gAdd.Bounds.SouthWest.Longitude)); _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.SouthWest.Latitude, gAdd.Bounds.NorthEast.Longitude)); _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.NorthEast.Latitude, gAdd.Bounds.NorthEast.Longitude)); } else SquareFence(20.0); Address.Geofence = Serialization.Serialize(_definition); } else { Address.Location.Longitude = 0.0; Address.Location.Latitude = 0.0; Address.Geofence = string.Empty; } } catch (Exception e) { Logger.Send(LogType.Error, ClientFactory.UserID, $"{e.Message}\n{e.StackTrace}"); } } else { _httpClientFactory ??= new HttpClientFactory(); NominatimWebInterface intf = new NominatimWebInterface(_httpClientFactory); var searcher = new ForwardGeocoder(intf); var request = new ForwardGeocodeRequest() { StreetAddress = Address.Street, City = Address.City, // County="", State = Address.State, PostalCode = Address.PostCode, Country = "AU", //queryString = $"{Address.Street}, {Address.City}, {Address.State}, {Address.PostCode} Australia", //BreakdownAddressElements = true, ShowExtraTags = true, ShowAlternativeNames = true, ShowGeoJSON = true, }; try { var matches = await searcher.Geocode(request); var match = matches.FirstOrDefault(); if (match != null) { Address.Location.Longitude = match.Longitude; Address.Location.Latitude = match.Latitude; if (match.BoundingBox.HasValue) { var bbox = match.BoundingBox.Value; _definition.Coordinates.Clear(); _definition.Coordinates.Add(new GeoPoint(bbox.minLatitude, bbox.minLongitude)); _definition.Coordinates.Add(new GeoPoint(bbox.minLatitude, bbox.maxLongitude)); _definition.Coordinates.Add(new GeoPoint(bbox.maxLatitude, bbox.maxLongitude)); _definition.Coordinates.Add(new GeoPoint(bbox.maxLatitude, bbox.minLongitude)); _definition.Coordinates.Add(new GeoPoint(bbox.minLatitude, bbox.minLongitude)); } else SquareFence(20.0); Address.Geofence = Serialization.Serialize(_definition); } else { Address.Location.Longitude = 0.0; Address.Location.Latitude = 0.0; Address.Geofence = string.Empty; } } catch (Exception e) { Logger.Send(LogType.Error, ClientFactory.UserID, $"{e.Message}\n{e.StackTrace}"); } } Dispatcher.BeginInvoke(SetupMap); } private void SquareFence(double side) { _definition.Coordinates.Clear(); _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(0-(side/2.0),0-(side/2.0))); _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(0-(side/2.0),side/2.0)); _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(side/2.0,side/2.0)); _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(side/2.0,0-(side/2.0))); _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(0-(side/2.0),0-(side/2.0))); } private void SetupMap() { CenterMap(); RadiusSlider.ValueChanged -= RadiusSliderChanged; RadiusSlider.Value = 20.0; if (_definition.Coordinates.Count < 4) SquareFence(20.0); RadiusSlider.ValueChanged += RadiusSliderChanged; UpdateMap(); } private void CenterMap() { ImageryLayer.Center = new Point(Address.Location.Latitude, Address.Location.Longitude); ImageryLayer.Markers = new CoreObservableCollection([new MapMarker() { Latitude = $"{Address.Location.Latitude:F15}", Longitude = $"{Address.Location.Longitude:F15}" }]); } private void RadiusSliderChanged(object sender, RoutedPropertyChangedEventArgs e) { RecalculateSquareFence(); UpdateMap(); } private void RecalculateSquareFence() { SquareFence(RadiusSlider.Value); Address.Geofence = Serialization.Serialize(_definition); } private void UpdateMap() { Polygon.Points = new ObservableCollection(_definition.Coordinates.Select(p => new Point(p.Latitude, p.Longitude))); } private void ZoomIn_OnClick(object sender, RoutedEventArgs e) { Map.MaxZoom = Math.Min(20, Map.ZoomLevel + 1); Map.ZoomLevel = Math.Min(20, Map.ZoomLevel + 1); UpdateMap(); } private void ZoomOut_OnClick(object sender, RoutedEventArgs e) { Map.MinZoom= Math.Max(5, Map.ZoomLevel - 1); Map.ZoomLevel = Math.Max(5, Map.ZoomLevel - 1); UpdateMap(); } private enum GeoFenceAction { None, Polygon, Square, Coordinates } private GeoFenceAction _action = GeoFenceAction.None; private void UpdateButtons(GeoFenceAction action) { _action = action; SetGeometry.IsEnabled = action == GeoFenceAction.None || action == GeoFenceAction.Polygon; SetGeometry.Background = action == GeoFenceAction.Polygon ? Brushes.Yellow : Brushes.Silver; SetRadius.IsEnabled = action == GeoFenceAction.None || action == GeoFenceAction.Square; SetRadius.Background = action == GeoFenceAction.Square ? Brushes.Yellow : Brushes.Silver; RadiusSlider.Visibility = action == GeoFenceAction.Square ? Visibility.Visible : Visibility.Collapsed; SetCoordinates.IsEnabled = action == GeoFenceAction.None || action == GeoFenceAction.Coordinates; SetCoordinates.Background = action == GeoFenceAction.Coordinates ? Brushes.Yellow : Brushes.Silver; } private void SetGeometry_OnClick(object sender, RoutedEventArgs e) { if (_action == GeoFenceAction.Polygon) UpdateButtons(GeoFenceAction.None); else { UpdateButtons(GeoFenceAction.Polygon); _definition.Coordinates.Clear(); UpdateMap(); } } private void Map_OnMouseUp(object sender, MouseButtonEventArgs e) { var point = Mouse.GetPosition(Map); var latlon = ImageryLayer.GetLatLonFromPoint(point); var geopoint = new GeoPoint(latlon.Y, latlon.X); if (_action == GeoFenceAction.Polygon) { if (!_definition.Coordinates.Any()) _definition.Coordinates.Add(geopoint.Copy()); _definition.Coordinates.Insert(_definition.Coordinates.Count - 1, geopoint.Copy()); Address.Geofence = Serialization.Serialize(_definition); UpdateMap(); e.Handled = true; } else if (_action == GeoFenceAction.Coordinates) { Address.Location.Latitude = geopoint.Latitude; Address.Location.Longitude = geopoint.Longitude; CenterMap(); UpdateMap(); e.Handled = true; } } private void SetRadius_OnClick(object sender, RoutedEventArgs e) { if (_action == GeoFenceAction.Square) UpdateButtons(GeoFenceAction.None); else { UpdateButtons(GeoFenceAction.Square); RadiusSlider.ValueChanged -= RadiusSliderChanged; RadiusSlider.Value = 20.0; SquareFence(20.0); RadiusSlider.ValueChanged += RadiusSliderChanged; RecalculateSquareFence(); UpdateMap(); } } private void SearchAddress_Click(object sender, RoutedEventArgs e) { Task.Run(CheckAddress); } private void SetCoordinates_OnClick(object sender, RoutedEventArgs e) { if (_action == GeoFenceAction.Coordinates) UpdateButtons(GeoFenceAction.None); else UpdateButtons(GeoFenceAction.Coordinates); } }