GeofenceEditor.xaml.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. using System;
  2. using System.Collections.ObjectModel;
  3. using System.Linq;
  4. using System.Net.Http;
  5. using System.Threading.Tasks;
  6. using System.Windows;
  7. using System.Windows.Input;
  8. using System.Windows.Media;
  9. using Geocoding;
  10. using Geocoding.Google;
  11. using InABox.Clients;
  12. using InABox.Core;
  13. using Nominatim.API.Geocoders;
  14. using Nominatim.API.Models;
  15. using Nominatim.API.Web;
  16. using Syncfusion.UI.Xaml.Maps;
  17. using Address = InABox.Core.Address;
  18. using Point = System.Windows.Point;
  19. namespace InABox.Wpf.DynamicGrid;
  20. public partial class GeofenceEditor : Window
  21. {
  22. public Address Address { get; private set; }
  23. GeoFenceDefinition _definition = null;
  24. private ImageryLayer ImageryLayer;
  25. public MapPolygon Polygon;
  26. public GeofenceEditor(Address address, bool canEdit)
  27. {
  28. Address = address;
  29. _definition = Serialization.Deserialize<GeoFenceDefinition>(address.Geofence) ?? new GeoFenceDefinition();
  30. InitializeComponent();
  31. ImageryLayer = string.IsNullOrWhiteSpace(CoreUtils.GoogleAPIKey)
  32. ? new ImageryLayer()
  33. : new GoogleImageryLayer() { Type = GoogleImageryLayerType.Satellite };
  34. ImageryLayer.Radius = 10;
  35. Map.Layers.Add(ImageryLayer);
  36. Polygon = new MapPolygon()
  37. {
  38. Fill = new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 },
  39. Stroke = new SolidColorBrush(Colors.Firebrick),
  40. StrokeThickness = 0.75,
  41. };
  42. var layer = new SubShapeFileLayer() { MapElements = new ObservableCollection<MapElement>([Polygon]) };
  43. ImageryLayer.SubShapeFileLayers = new ObservableCollection<SubShapeFileLayer>([layer]);
  44. SetGeometry.Visibility = canEdit ? Visibility.Visible : Visibility.Collapsed;
  45. SetRadius.Visibility = canEdit ? Visibility.Visible : Visibility.Collapsed;
  46. SearchBar.Visibility = canEdit ? Visibility.Visible : Visibility.Collapsed;
  47. Street.Text = address.Street;
  48. City.Text = address.City;
  49. State.Text = address.State;
  50. PostCode.Text = Address.PostCode;
  51. Title = $"Geofence Editor ({(string.IsNullOrWhiteSpace(CoreUtils.GoogleAPIKey) ? "OSM" : "Google")})";
  52. if (canEdit && string.IsNullOrWhiteSpace(Address.Geofence))
  53. Task.Run(CheckAddress);
  54. else
  55. SetupMap();
  56. }
  57. private class HttpClientFactory : IHttpClientFactory
  58. {
  59. private static HttpClient? _client;
  60. public HttpClient CreateClient(string name)
  61. {
  62. _client ??= new HttpClient();
  63. return _client;
  64. }
  65. }
  66. private static HttpClientFactory? _httpClientFactory = null;
  67. private async Task CheckAddress()
  68. {
  69. if (!string.IsNullOrWhiteSpace(CoreUtils.GoogleAPIKey))
  70. {
  71. IGeocoder geocoder = new GoogleGeocoder(CoreUtils.GoogleAPIKey);
  72. try
  73. {
  74. var matches =
  75. await geocoder.GeocodeAsync(
  76. $"{Address.Street}, {Address.City} {Address.State} {Address.PostCode} Australia");
  77. var match = matches.FirstOrDefault();
  78. if (match != null)
  79. {
  80. Address.Location.Longitude = match.Coordinates.Longitude;
  81. Address.Location.Latitude = match.Coordinates.Latitude;
  82. if (match is GoogleAddress gAdd)
  83. {
  84. _definition.Coordinates.Clear();
  85. _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.NorthEast.Latitude,
  86. gAdd.Bounds.NorthEast.Longitude));
  87. _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.NorthEast.Latitude,
  88. gAdd.Bounds.SouthWest.Longitude));
  89. _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.SouthWest.Latitude,
  90. gAdd.Bounds.SouthWest.Longitude));
  91. _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.SouthWest.Latitude,
  92. gAdd.Bounds.NorthEast.Longitude));
  93. _definition.Coordinates.Add(new GeoPoint(gAdd.Bounds.NorthEast.Latitude,
  94. gAdd.Bounds.NorthEast.Longitude));
  95. }
  96. else
  97. SquareFence(20.0);
  98. Address.Geofence = Serialization.Serialize(_definition);
  99. }
  100. else
  101. {
  102. Address.Location.Longitude = 0.0;
  103. Address.Location.Latitude = 0.0;
  104. Address.Geofence = string.Empty;
  105. }
  106. }
  107. catch (Exception e)
  108. {
  109. Logger.Send(LogType.Error, ClientFactory.UserID, $"{e.Message}\n{e.StackTrace}");
  110. }
  111. }
  112. else
  113. {
  114. _httpClientFactory ??= new HttpClientFactory();
  115. NominatimWebInterface intf = new NominatimWebInterface(_httpClientFactory);
  116. var searcher = new ForwardGeocoder(intf);
  117. var request = new ForwardGeocodeRequest()
  118. {
  119. StreetAddress = Address.Street,
  120. City = Address.City,
  121. // County="",
  122. State = Address.State,
  123. PostalCode = Address.PostCode,
  124. Country = "AU",
  125. //queryString = $"{Address.Street}, {Address.City}, {Address.State}, {Address.PostCode} Australia",
  126. //BreakdownAddressElements = true,
  127. ShowExtraTags = true,
  128. ShowAlternativeNames = true,
  129. ShowGeoJSON = true,
  130. };
  131. try
  132. {
  133. var matches = await searcher.Geocode(request);
  134. var match = matches.FirstOrDefault();
  135. if (match != null)
  136. {
  137. Address.Location.Longitude = match.Longitude;
  138. Address.Location.Latitude = match.Latitude;
  139. if (match.BoundingBox.HasValue)
  140. {
  141. var bbox = match.BoundingBox.Value;
  142. _definition.Coordinates.Clear();
  143. _definition.Coordinates.Add(new GeoPoint(bbox.minLatitude, bbox.minLongitude));
  144. _definition.Coordinates.Add(new GeoPoint(bbox.minLatitude, bbox.maxLongitude));
  145. _definition.Coordinates.Add(new GeoPoint(bbox.maxLatitude, bbox.maxLongitude));
  146. _definition.Coordinates.Add(new GeoPoint(bbox.maxLatitude, bbox.minLongitude));
  147. _definition.Coordinates.Add(new GeoPoint(bbox.minLatitude, bbox.minLongitude));
  148. }
  149. else
  150. SquareFence(20.0);
  151. Address.Geofence = Serialization.Serialize(_definition);
  152. }
  153. else
  154. {
  155. Address.Location.Longitude = 0.0;
  156. Address.Location.Latitude = 0.0;
  157. Address.Geofence = string.Empty;
  158. }
  159. }
  160. catch (Exception e)
  161. {
  162. Logger.Send(LogType.Error, ClientFactory.UserID, $"{e.Message}\n{e.StackTrace}");
  163. }
  164. }
  165. Dispatcher.BeginInvoke(SetupMap);
  166. }
  167. private void SquareFence(double side)
  168. {
  169. _definition.Coordinates.Clear();
  170. _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(0-(side/2.0),0-(side/2.0)));
  171. _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(0-(side/2.0),side/2.0));
  172. _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(side/2.0,side/2.0));
  173. _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(side/2.0,0-(side/2.0)));
  174. _definition.Coordinates.Add(new GeoPoint(Address.Location.Latitude, Address.Location.Longitude).Move(0-(side/2.0),0-(side/2.0)));
  175. }
  176. private void SetupMap()
  177. {
  178. CenterMap();
  179. RadiusSlider.ValueChanged -= RadiusSliderChanged;
  180. RadiusSlider.Value = 20.0;
  181. if (_definition.Coordinates.Count < 4)
  182. SquareFence(20.0);
  183. RadiusSlider.ValueChanged += RadiusSliderChanged;
  184. UpdateMap();
  185. }
  186. private void CenterMap()
  187. {
  188. ImageryLayer.Center = new Point(Address.Location.Latitude, Address.Location.Longitude);
  189. ImageryLayer.Markers = new CoreObservableCollection<MapMarker>([new MapMarker()
  190. {
  191. Latitude = $"{Address.Location.Latitude:F15}",
  192. Longitude = $"{Address.Location.Longitude:F15}"
  193. }]);
  194. }
  195. private void RadiusSliderChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
  196. {
  197. RecalculateSquareFence();
  198. UpdateMap();
  199. }
  200. private void RecalculateSquareFence()
  201. {
  202. SquareFence(RadiusSlider.Value);
  203. Address.Geofence = Serialization.Serialize(_definition);
  204. }
  205. private void UpdateMap()
  206. {
  207. Polygon.Points = new ObservableCollection<Point>(_definition.Coordinates.Select(p => new Point(p.Latitude, p.Longitude)));
  208. }
  209. private void ZoomIn_OnClick(object sender, RoutedEventArgs e)
  210. {
  211. Map.MaxZoom = Math.Min(20, Map.ZoomLevel + 1);
  212. Map.ZoomLevel = Math.Min(20, Map.ZoomLevel + 1);
  213. UpdateMap();
  214. }
  215. private void ZoomOut_OnClick(object sender, RoutedEventArgs e)
  216. {
  217. Map.MinZoom= Math.Max(5, Map.ZoomLevel - 1);
  218. Map.ZoomLevel = Math.Max(5, Map.ZoomLevel - 1);
  219. UpdateMap();
  220. }
  221. private enum GeoFenceAction
  222. {
  223. None,
  224. Polygon,
  225. Square,
  226. Coordinates
  227. }
  228. private GeoFenceAction _action = GeoFenceAction.None;
  229. private void UpdateButtons(GeoFenceAction action)
  230. {
  231. _action = action;
  232. SetGeometry.IsEnabled = action == GeoFenceAction.None || action == GeoFenceAction.Polygon;
  233. SetGeometry.Background = action == GeoFenceAction.Polygon
  234. ? Brushes.Yellow
  235. : Brushes.Silver;
  236. SetRadius.IsEnabled = action == GeoFenceAction.None || action == GeoFenceAction.Square;
  237. SetRadius.Background = action == GeoFenceAction.Square
  238. ? Brushes.Yellow
  239. : Brushes.Silver;
  240. RadiusSlider.Visibility = action == GeoFenceAction.Square
  241. ? Visibility.Visible
  242. : Visibility.Collapsed;
  243. SetCoordinates.IsEnabled = action == GeoFenceAction.None || action == GeoFenceAction.Coordinates;
  244. SetCoordinates.Background = action == GeoFenceAction.Coordinates
  245. ? Brushes.Yellow
  246. : Brushes.Silver;
  247. }
  248. private void SetGeometry_OnClick(object sender, RoutedEventArgs e)
  249. {
  250. if (_action == GeoFenceAction.Polygon)
  251. UpdateButtons(GeoFenceAction.None);
  252. else
  253. {
  254. UpdateButtons(GeoFenceAction.Polygon);
  255. _definition.Coordinates.Clear();
  256. UpdateMap();
  257. }
  258. }
  259. private void Map_OnMouseUp(object sender, MouseButtonEventArgs e)
  260. {
  261. var point = Mouse.GetPosition(Map);
  262. var latlon = ImageryLayer.GetLatLonFromPoint(point);
  263. var geopoint = new GeoPoint(latlon.Y, latlon.X);
  264. if (_action == GeoFenceAction.Polygon)
  265. {
  266. if (!_definition.Coordinates.Any())
  267. _definition.Coordinates.Add(geopoint.Copy());
  268. _definition.Coordinates.Insert(_definition.Coordinates.Count - 1, geopoint.Copy());
  269. Address.Geofence = Serialization.Serialize(_definition);
  270. UpdateMap();
  271. e.Handled = true;
  272. }
  273. else if (_action == GeoFenceAction.Coordinates)
  274. {
  275. Address.Location.Latitude = geopoint.Latitude;
  276. Address.Location.Longitude = geopoint.Longitude;
  277. CenterMap();
  278. UpdateMap();
  279. e.Handled = true;
  280. }
  281. }
  282. private void SetRadius_OnClick(object sender, RoutedEventArgs e)
  283. {
  284. if (_action == GeoFenceAction.Square)
  285. UpdateButtons(GeoFenceAction.None);
  286. else
  287. {
  288. UpdateButtons(GeoFenceAction.Square);
  289. RadiusSlider.ValueChanged -= RadiusSliderChanged;
  290. RadiusSlider.Value = 20.0;
  291. SquareFence(20.0);
  292. RadiusSlider.ValueChanged += RadiusSliderChanged;
  293. RecalculateSquareFence();
  294. UpdateMap();
  295. }
  296. }
  297. private void SearchAddress_Click(object sender, RoutedEventArgs e)
  298. {
  299. Task.Run(CheckAddress);
  300. }
  301. private void SetCoordinates_OnClick(object sender, RoutedEventArgs e)
  302. {
  303. if (_action == GeoFenceAction.Coordinates)
  304. UpdateButtons(GeoFenceAction.None);
  305. else
  306. UpdateButtons(GeoFenceAction.Coordinates);
  307. }
  308. }