GeofenceEditor.xaml.cs 14 KB

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