WeatherInformation WP8: MapPage improvements
[CSharpForFun/.git] / WindowsPhone / WP8 / WeatherInformation / WeatherInformation / MapPage.xaml.cs
1 using System;
2 using System.Linq;
3 using System.Windows;
4 using System.Windows.Navigation;
5 using Microsoft.Phone.Controls;
6 using Windows.Devices.Geolocation;
7 using System.Device.Location;
8 using System.Windows.Shapes;
9 using System.Windows.Media;
10 using Microsoft.Phone.Maps.Controls;
11 using System.Threading.Tasks;
12 using WeatherInformation.Model;
13 using System.IO.IsolatedStorage;
14
15 namespace WeatherInformation
16 {
17     public partial class MapPage : PhoneApplicationPage, ReverseGeoCode.IReverseGeoCode
18     {
19         private bool _isNewPageInstance;
20         private ReverseGeoCode _reverseOnProgress;
21         private Geolocator _geolocator;
22         // TODO: how big is a MapLayer object?
23         private MapOverlay _locationOverlay;
24         private Location _restoreLocation;
25         private GeoCoordinate _restoreReverseOnProgress;
26
27         public MapPage()
28         {
29             InitializeComponent();
30
31             _isNewPageInstance = true;
32         }
33
34         protected override void OnNavigatedTo(NavigationEventArgs e)
35         {
36             AskForLocationConsent();
37
38             if (_isNewPageInstance)
39             {
40                 if (State.Count > 0)
41                 {
42                     if (State.ContainsKey("ReverseGeoCoorDinateOnProgress"))
43                     {
44                         this._restoreReverseOnProgress = (GeoCoordinate)State["ReverseGeoCoorDinateOnProgress"];
45                     }
46                     this._restoreLocation = (Location)State["CurrentChosenMapLocation"];
47                 }
48
49                 UpdateLocationStatus();
50             }
51             else
52             {
53                 if (this._geolocator != null)
54                 {
55                     this._geolocator.StatusChanged += GeolocationStatusCallback;
56                 }
57             }
58             // Set _isNewPageInstance to false. If the user navigates back to this page
59             // and it has remained in memory, this value will continue to be false.
60             _isNewPageInstance = false;
61
62             RestoreUI();
63         }
64
65         protected override void OnNavigatedFrom(NavigationEventArgs e)
66         {
67             if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
68             {
69                 // TODO: Could Center be null?
70                 State["CurrentChosenMapLocation"] = CoordinateHelper.GeoCoordinateToLocation(
71                     mapWeatherInformation.Center,
72                     LocationTextCity.Text,
73                     LocationTextCountry.Text);
74
75                 if (_reverseOnProgress != null)
76                 {
77                     State["ReverseGeoCoorDinateOnProgress"] = _reverseOnProgress.CoorDinate;
78                 }
79             }
80
81             if (_geolocator != null)
82             {
83                 _geolocator.StatusChanged -= GeolocationStatusCallback;
84             }
85
86             if (_reverseOnProgress != null)
87             {
88                 _reverseOnProgress.Page = null;
89             }
90         }
91
92         public void OnCompletedReverseGeoCode(GeoCoordinate geoCoordinate, string city, string country)
93         {
94             UpdateMap(geoCoordinate, city, country);
95             RemoveProgressBar();
96             _reverseOnProgress = null;
97         }
98
99         private async Task GetCurrentLocationAndUpdateMap(Geolocator geolocator)
100         {
101             try
102             {
103                 var geoposition = await geolocator.GetGeopositionAsync(
104                                     maximumAge: TimeSpan.FromSeconds(1),
105                                     timeout: TimeSpan.FromSeconds(10)
106                                 );
107
108                 var currentGeoCoordinate = CoordinateHelper.ConvertGeocoordinate(geoposition.Coordinate);
109                 ShowProgressBar();
110                 ReverseGeocodeAndUpdateMap(currentGeoCoordinate);
111             }
112             catch (Exception ex)
113             {
114                 // TODO: hopefully using main thread when Exception.
115                 if ((uint)ex.HResult == 0x80004004)
116                 {
117                     // Location is disabled in phone settings.
118                 }
119
120                 // TODO: not sure if when exception I will be using the calling thread calling the await method.
121                 Dispatcher.BeginInvoke(new UpdateLocationButtonDelegate(this.UpdateLocationButton), false);
122             }
123         }
124
125         private void ReverseGeocodeAndUpdateMap(GeoCoordinate geoCoordinate)
126         {
127             var reverseGeoCode = new ReverseGeoCode()
128             {
129                 Page = this,
130                 CoorDinate = geoCoordinate
131             };
132
133             if (_reverseOnProgress != null)
134             {
135                 // GC may release old object.
136                 _reverseOnProgress.Page = null;
137             }
138
139             _reverseOnProgress = reverseGeoCode;
140             _reverseOnProgress.DoReverseGeocode(geoCoordinate);
141         }
142
143         private void UpdateMap(GeoCoordinate geoCoordinate, string city, string country)
144         {
145             // Create a small circle to mark the current location.
146             Ellipse myCircle = new Ellipse();
147             myCircle.Fill = new SolidColorBrush(Colors.Blue);
148             myCircle.Height = 20;
149             myCircle.Width = 20;
150             myCircle.Opacity = 50;
151
152             // Create a MapOverlay to contain the circle.
153             _locationOverlay = new MapOverlay();
154             _locationOverlay.Content = myCircle;
155             _locationOverlay.PositionOrigin = new Point(0.5, 0.5);
156             _locationOverlay.GeoCoordinate = geoCoordinate;
157
158             // Create a MapLayer to contain the MapOverlay.
159             MapLayer myLocationLayer = new MapLayer();
160             myLocationLayer.Add(_locationOverlay);
161
162             mapWeatherInformation.Layers.Clear();
163
164             // TODO: problems? user could press save location and if she is fast enough she
165             // could not realize the location changed.
166             // But well... She will realize later... So.. Is this really a problem?
167             mapWeatherInformation.Center = geoCoordinate;
168             mapWeatherInformation.ZoomLevel = 13;
169
170             LocationTextCity.Text = city;
171             LocationTextCountry.Text = country;
172             // Add the MapLayer to the Map.
173             mapWeatherInformation.Layers.Add(myLocationLayer);
174         }
175
176         private static class CoordinateHelper
177         {
178             public static GeoCoordinate ConvertGeocoordinate(Geocoordinate geocoordinate)
179             {
180                 return new GeoCoordinate
181                     (
182                     geocoordinate.Latitude,
183                     geocoordinate.Longitude,
184                     geocoordinate.Altitude ?? Double.NaN,
185                     geocoordinate.Accuracy,
186                     geocoordinate.AltitudeAccuracy ?? Double.NaN,
187                     geocoordinate.Speed ?? Double.NaN,
188                     geocoordinate.Heading ?? Double.NaN
189                     );
190             }
191
192             public static GeoCoordinate LocationToGeoCoordinate(Location locationItem)
193             {
194                 return new GeoCoordinate
195                     (
196                     locationItem.Latitude,
197                     locationItem.Longitude,
198                     locationItem.Altitude,
199                     locationItem.HorizontalAccuracy,
200                     locationItem.VerticalAccuracy,
201                     locationItem.Speed,
202                     locationItem.Course
203                     );
204             }
205
206             public static Location GeoCoordinateToLocation(GeoCoordinate geoCoordinate, string city, string country)
207             {
208                 return new Location()
209                 {
210                     Latitude = geoCoordinate.Latitude,
211                     Longitude = geoCoordinate.Longitude,
212                     Altitude = geoCoordinate.Altitude,
213                     City = city,
214                     Country = country,
215                     HorizontalAccuracy = geoCoordinate.HorizontalAccuracy,
216                     VerticalAccuracy = geoCoordinate.VerticalAccuracy,
217                     Speed = geoCoordinate.Speed,
218                     Course = geoCoordinate.Course,
219                     IsSelected = true,
220                     LastRemoteDataUpdate = null,
221                 };
222             }
223         }
224
225         // TODO: check data before storing :(
226         // http://stackoverflow.com/questions/4521435/what-specific-values-can-a-c-sharp-double-represent-that-a-sql-server-float-can
227         private void StoreLocation(GeoCoordinate geocoordinate, string city, string country)
228         {
229             using (var db = new LocationDataContext(LocationDataContext.DBConnectionString))
230             {
231                 // Define the query to gather all of the to-do items.
232                 // var toDoItemsInDB = from Location location in _locationDB.Locations where location.IsSelected select location;
233                 var locationItem = db.Locations.Where(location => location.IsSelected).FirstOrDefault();
234
235                 if (locationItem != null)
236                 {
237                     locationItem.Latitude = geocoordinate.Latitude;
238                     locationItem.Longitude = geocoordinate.Longitude;
239                     locationItem.Altitude = geocoordinate.Altitude;
240                     locationItem.City = city;
241                     locationItem.Country = country;
242                     locationItem.HorizontalAccuracy = geocoordinate.HorizontalAccuracy;
243                     locationItem.VerticalAccuracy = geocoordinate.VerticalAccuracy;
244                     locationItem.Speed = geocoordinate.Speed;
245                     locationItem.Course = geocoordinate.Course;
246                     locationItem.IsSelected = true;
247                     locationItem.LastRemoteDataUpdate = null;
248                 }
249                 else
250                 {
251                     locationItem = new Location()
252                     {
253                         Latitude = geocoordinate.Latitude,
254                         Longitude = geocoordinate.Longitude,
255                         Altitude = geocoordinate.Altitude,
256                         City = city,
257                         Country = country,
258                         HorizontalAccuracy = geocoordinate.HorizontalAccuracy,
259                         VerticalAccuracy = geocoordinate.VerticalAccuracy,
260                         Speed = geocoordinate.Speed,
261                         Course = geocoordinate.Course,
262                         IsSelected = true,
263                         LastRemoteDataUpdate = null,
264                     };
265
266                     // Add a location item to the local database.
267                     db.Locations.InsertOnSubmit(locationItem);
268                 }
269
270                 db.SubmitChanges();
271             }
272         }
273
274         private void mapWeatherInformation_Tap(object sender, System.Windows.Input.GestureEventArgs e)
275         {
276             // TODO: if exception from here application will crash (I guess)
277             var point = e.GetPosition(this.mapWeatherInformation);
278             GeoCoordinate geocoordinate = this.mapWeatherInformation.ConvertViewportPointToGeoCoordinate(point);
279             ShowProgressBar();
280             ReverseGeocodeAndUpdateMap(geocoordinate);
281         }
282
283         private async void GetCurrentLocationButton_Click(object sender, RoutedEventArgs e)
284         {
285             if (_geolocator == null)
286             {
287                 // Nothing to do.
288                 return;
289             }
290             
291             // TODO: if exception from here application will crash (I guess)
292             await this.GetCurrentLocationAndUpdateMap(_geolocator);
293         }
294
295         private void SaveLocationButton_Click(object sender, RoutedEventArgs e)
296         {
297             // TODO: Could Center be null?
298             StoreLocation(this.mapWeatherInformation.Center, this.LocationTextCity.Text, this.LocationTextCountry.Text);
299         }
300
301         private async void UpdateLocationStatus()
302         {
303             if ((bool)IsolatedStorageSettings.ApplicationSettings["LocationConsent"] != true)
304             {
305                 // The user has opted out of Location.
306                 GetCurrentLocationButton.IsEnabled = false;
307                 return;
308             }
309
310             _geolocator = new Geolocator();
311             _geolocator.DesiredAccuracyInMeters = 50;
312             _geolocator.ReportInterval = 1000;
313
314             try
315             {
316                 _geolocator.StatusChanged += GeolocationStatusCallback;
317                 var geoposition = await _geolocator.GetGeopositionAsync(
318                                     maximumAge: TimeSpan.FromSeconds(1),
319                                     timeout: TimeSpan.FromSeconds(10)
320                                 );
321             }
322             catch(Exception ex)
323             {
324                 if ((uint)ex.HResult == 0x80004004)
325                 {
326                     // Location is disabled in phone settings.
327                 }
328
329                 // TODO: not sure if when exception I will be using the calling thread calling the await method.
330                 Dispatcher.BeginInvoke(new UpdateLocationButtonDelegate(this.UpdateLocationButton), false);
331             }
332         }
333
334         // TODO: how to do it just with lambda expressions?
335         delegate void UpdateLocationButtonDelegate(bool isEnabled);
336         private void UpdateLocationButton(bool isEnabled)
337         {
338             GetCurrentLocationButton.IsEnabled = isEnabled;
339         }
340
341         private void GeolocationStatusCallback(Geolocator sender, StatusChangedEventArgs eventData)
342         {
343             if (eventData.Status == PositionStatus.Ready)
344             {
345                 Dispatcher.BeginInvoke(new UpdateLocationButtonDelegate(this.UpdateLocationButton), true);
346             }
347             else
348             {
349                 Dispatcher.BeginInvoke(new UpdateLocationButtonDelegate(this.UpdateLocationButton), false);
350             }
351         }
352
353         private void AskForLocationConsent()
354         {
355             if (!IsolatedStorageSettings.ApplicationSettings.Contains("LocationConsent"))
356             {
357                 MessageBoxResult result =
358                     MessageBox.Show("This app accesses your phone's location. Is that ok?",
359                     "Location", MessageBoxButton.OKCancel);
360
361                 if (result == MessageBoxResult.OK)
362                 {
363                     IsolatedStorageSettings.ApplicationSettings["LocationConsent"] = true;
364                 }
365                 else
366                 {
367                     IsolatedStorageSettings.ApplicationSettings["LocationConsent"] = false;
368                 }
369
370                 IsolatedStorageSettings.ApplicationSettings.Save();
371             }
372         }
373
374         private void RestoreUI()
375         {
376             Location location;
377             if (this._restoreLocation != null)
378             {
379                 location = this._restoreLocation;
380             }
381             else if (this._locationOverlay != null)
382             {
383                 location = CoordinateHelper.GeoCoordinateToLocation(
384                     _locationOverlay.GeoCoordinate,
385                     LocationTextCity.Text,
386                     LocationTextCountry.Text);
387             }
388             else
389             {
390                 using (var db = new LocationDataContext(LocationDataContext.DBConnectionString))
391                 {
392                     // Define the query to gather all of the to-do items.
393                     // var toDoItemsInDB = from Location location in _locationDB.Locations where location.IsSelected select location;
394                     location = db.Locations.Where(locationItem => locationItem.IsSelected).FirstOrDefault();
395                 }
396             }
397
398
399             if (location != null)
400             {
401                 UpdateMap(CoordinateHelper.LocationToGeoCoordinate(location),
402                           location.City, location.Country);
403             }
404
405             if (this._restoreReverseOnProgress != null)
406             {
407                 ShowProgressBar();
408                 ReverseGeocodeAndUpdateMap(this._restoreReverseOnProgress);
409             }
410             else
411             {
412                 RemoveProgressBar();
413             }
414         }
415
416         private void ShowProgressBar()
417         {
418             GetCurrentLocationButton.Visibility = Visibility.Collapsed;
419             SaveLocationButton.IsEnabled = false;
420             SaveLocationButton.Visibility = Visibility.Collapsed;
421             ProgressBarRemoteData.IsEnabled = true;
422             ProgressBarRemoteData.Visibility = Visibility.Visible;
423         }
424
425         private void RemoveProgressBar()
426         {
427             GetCurrentLocationButton.Visibility = Visibility.Visible;
428             SaveLocationButton.IsEnabled = true;
429             SaveLocationButton.Visibility = Visibility.Visible;
430             ProgressBarRemoteData.IsEnabled = false;
431             ProgressBarRemoteData.Visibility = Visibility.Collapsed;
432         }
433
434
435
436         private void ZoomOutButton_Click(object sender, RoutedEventArgs e)
437         {
438             this.mapWeatherInformation.ZoomLevel = this.mapWeatherInformation.ZoomLevel - 1;
439         }
440
441         private void ZoomInButton_Click(object sender, RoutedEventArgs e)
442         {
443             this.mapWeatherInformation.ZoomLevel = this.mapWeatherInformation.ZoomLevel + 1;
444         }
445     }
446 }