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
14 namespace WeatherInformation
15 {
16     public partial class MapPage : PhoneApplicationPage, ReverseGeoCode.IReverseGeoCode
17     {
18         private bool _isNewPageInstance;
19         private ReverseGeoCode _reverseGeoCodeOnProgress;
20         // TODO: how big is a MapLayer object?
21         private MapOverlay _locationOverlay;
22
23         public MapPage()
24         {
25             InitializeComponent();
26
27             _isNewPageInstance = true;
28         }
29
30         protected override void OnNavigatedTo(NavigationEventArgs e)
31         {
32             var geolocator = new Geolocator();
33             if (geolocator.LocationStatus != PositionStatus.Ready)
34             {
35                 GetCurrentLocationButton.IsEnabled = false;
36             }
37
38             GeoCoordinate restoreReverseOnProgress = null;
39             Location restoreLocation = null;
40             if (_isNewPageInstance)
41             {
42                 if (State.Count > 0)
43                 {
44                     restoreReverseOnProgress = (GeoCoordinate)State["ReverseGeoCoorDinateOnProgress"];
45                     restoreLocation = (Location)State["CurrentChosenMapLocation"];
46                 }
47             }
48             // Set _isNewPageInstance to false. If the user navigates back to this page
49             // and it has remained in memory, this value will continue to be false.
50             _isNewPageInstance = false;
51
52             Location location;
53             if (restoreLocation != null)
54             {
55                 location = restoreLocation;
56             }
57             else if (_locationOverlay != null)
58             {
59                 location = CoordinateHelper.GeoCoordinateToLocation(
60                     _locationOverlay.GeoCoordinate,
61                     LocationTextCity.Text,
62                     LocationTextCountry.Text);
63             }
64             else
65             {
66                 using (var db = new LocationDataContext(LocationDataContext.DBConnectionString))
67                 {
68                     // Define the query to gather all of the to-do items.
69                     // var toDoItemsInDB = from Location location in _locationDB.Locations where location.IsSelected select location;
70                     location = db.Locations.Where(locationItem => locationItem.IsSelected).FirstOrDefault();
71                 }
72             }
73
74
75             if (location != null)
76             {
77                 UpdateMap(CoordinateHelper.LocationToGeoCoordinate(location),
78                           location.City, location.Country);
79             }
80
81             if (restoreReverseOnProgress != null)
82             {
83                 ShowProgressBar();
84                 ReverseGeocodeAndUpdateMap(restoreReverseOnProgress);
85             }
86             else
87             {
88                 RemoveProgressBar();
89             }
90         }
91
92         protected override void OnNavigatedFrom(NavigationEventArgs e)
93         {
94             if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
95             {
96                 // TODO: Could Center be null?
97                 State["CurrentChosenMapLocation"] = CoordinateHelper.GeoCoordinateToLocation(
98                     mapWeatherInformation.Center,
99                     LocationTextCity.Text,
100                     LocationTextCountry.Text);
101
102                 if (_reverseGeoCodeOnProgress != null)
103                 {
104                     State["ReverseGeoCoorDinateOnProgress"] = _reverseGeoCodeOnProgress.CoorDinate;
105                 }  
106             }
107         }
108
109         public void OnCompletedReverseGeoCode(GeoCoordinate geoCoordinate, string city, string country)
110         {
111             UpdateMap(geoCoordinate, city, country);
112             RemoveProgressBar();
113             _reverseGeoCodeOnProgress = null;
114         }
115
116         private async Task GetCurrentLocationAndUpdateMap()
117         {
118             var geolocator = new Geolocator();
119             geolocator.DesiredAccuracyInMeters = 50;
120             geolocator.ReportInterval = 1000;
121
122             var geoposition = await geolocator.GetGeopositionAsync(
123                 maximumAge: TimeSpan.FromSeconds(1),
124                 timeout: TimeSpan.FromSeconds(10)
125                 );
126             // TODO: check if the following is true:
127             // Without ConfigureAwait(false) await returns data on the calling thread. (AFAIK for this Context)
128             // otherwise I should call: Dispatcher.BeginInvoke(() => ReverseGeocodeAndUpdateMap(currentGeoCoordinate));
129
130             // TODO: What is going to happend when Timeout? Exception or geposition will be null value.
131             //       Should I check for null value in case of Timeout?
132             var currentGeoCoordinate = CoordinateHelper.ConvertGeocoordinate(geoposition.Coordinate);
133             ShowProgressBar();
134             ReverseGeocodeAndUpdateMap(currentGeoCoordinate);
135         }
136
137         private void ReverseGeocodeAndUpdateMap(GeoCoordinate geoCoordinate)
138         {
139             var reverseGeoCode = new ReverseGeoCode()
140             {
141                 Page = this,
142                 CoorDinate = geoCoordinate
143             };
144
145             if (_reverseGeoCodeOnProgress != null)
146             {
147                 // GC may release old object.
148                 _reverseGeoCodeOnProgress.Page = null;
149             }
150
151             _reverseGeoCodeOnProgress = reverseGeoCode;
152             _reverseGeoCodeOnProgress.DoReverseGeocode(geoCoordinate);
153         }
154
155         private void UpdateMap(GeoCoordinate geoCoordinate, string city, string country)
156         {
157             // Create a small circle to mark the current location.
158             Ellipse myCircle = new Ellipse();
159             myCircle.Fill = new SolidColorBrush(Colors.Blue);
160             myCircle.Height = 20;
161             myCircle.Width = 20;
162             myCircle.Opacity = 50;
163
164             // Create a MapOverlay to contain the circle.
165             _locationOverlay = new MapOverlay();
166             _locationOverlay.Content = myCircle;
167             _locationOverlay.PositionOrigin = new Point(0.5, 0.5);
168             _locationOverlay.GeoCoordinate = geoCoordinate;
169
170             // Create a MapLayer to contain the MapOverlay.
171             MapLayer myLocationLayer = new MapLayer();
172             myLocationLayer.Add(_locationOverlay);
173
174             mapWeatherInformation.Layers.Clear();
175
176             // TODO: problems? user could press save location and if she is fast enough she
177             // could not realize the location changed.
178             // But well... She will realize later... So.. Is this really a problem?
179             mapWeatherInformation.Center = geoCoordinate;
180             mapWeatherInformation.ZoomLevel = 13;
181
182             LocationTextCity.Text = city;
183             LocationTextCountry.Text = country;
184             // Add the MapLayer to the Map.
185             mapWeatherInformation.Layers.Add(myLocationLayer);
186         }
187
188         private static class CoordinateHelper
189         {
190             public static GeoCoordinate ConvertGeocoordinate(Geocoordinate geocoordinate)
191             {
192                 return new GeoCoordinate
193                     (
194                     geocoordinate.Latitude,
195                     geocoordinate.Longitude,
196                     geocoordinate.Altitude ?? Double.NaN,
197                     geocoordinate.Accuracy,
198                     geocoordinate.AltitudeAccuracy ?? Double.NaN,
199                     geocoordinate.Speed ?? Double.NaN,
200                     geocoordinate.Heading ?? Double.NaN
201                     );
202             }
203
204             public static GeoCoordinate LocationToGeoCoordinate(Location locationItem)
205             {
206                 return new GeoCoordinate
207                     (
208                     locationItem.Latitude,
209                     locationItem.Longitude,
210                     locationItem.Altitude,
211                     locationItem.HorizontalAccuracy,
212                     locationItem.VerticalAccuracy,
213                     locationItem.Speed,
214                     locationItem.Course
215                     );
216             }
217
218             public static Location GeoCoordinateToLocation(GeoCoordinate geoCoordinate, string city, string country)
219             {
220                 return new Location()
221                 {
222                     Latitude = geoCoordinate.Latitude,
223                     Longitude = geoCoordinate.Longitude,
224                     Altitude = geoCoordinate.Altitude,
225                     City = city,
226                     Country = country,
227                     HorizontalAccuracy = geoCoordinate.HorizontalAccuracy,
228                     VerticalAccuracy = geoCoordinate.VerticalAccuracy,
229                     Speed = geoCoordinate.Speed,
230                     Course = geoCoordinate.Course,
231                     IsSelected = true,
232                     LastRemoteDataUpdate = null,
233                 };
234             }
235         }
236
237         // TODO: check data before storing :(
238         // http://stackoverflow.com/questions/4521435/what-specific-values-can-a-c-sharp-double-represent-that-a-sql-server-float-can
239         private void StoreLocation(GeoCoordinate geocoordinate, string city, string country)
240         {
241             Location locationItem = null;
242             using (var db = new LocationDataContext(LocationDataContext.DBConnectionString))
243             {
244                 // Define the query to gather all of the to-do items.
245                 // var toDoItemsInDB = from Location location in _locationDB.Locations where location.IsSelected select location;
246                 locationItem = db.Locations.Where(location => location.IsSelected).FirstOrDefault();
247
248                 if (locationItem != null)
249                 {
250                     locationItem.Latitude = geocoordinate.Latitude;
251                     locationItem.Longitude = geocoordinate.Longitude;
252                     locationItem.Altitude = geocoordinate.Altitude;
253                     locationItem.City = city;
254                     locationItem.Country = country;
255                     locationItem.HorizontalAccuracy = geocoordinate.HorizontalAccuracy;
256                     locationItem.VerticalAccuracy = geocoordinate.VerticalAccuracy;
257                     locationItem.Speed = geocoordinate.Speed;
258                     locationItem.Course = geocoordinate.Course;
259                     locationItem.IsSelected = true;
260                     locationItem.LastRemoteDataUpdate = null;
261                 }
262                 else
263                 {
264                     locationItem = new Location()
265                     {
266                         Latitude = geocoordinate.Latitude,
267                         Longitude = geocoordinate.Longitude,
268                         Altitude = geocoordinate.Altitude,
269                         City = city,
270                         Country = country,
271                         HorizontalAccuracy = geocoordinate.HorizontalAccuracy,
272                         VerticalAccuracy = geocoordinate.VerticalAccuracy,
273                         Speed = geocoordinate.Speed,
274                         Course = geocoordinate.Course,
275                         IsSelected = true,
276                         LastRemoteDataUpdate = null,
277                     };
278
279                     // Add a location item to the local database.
280                     db.Locations.InsertOnSubmit(locationItem);
281                 }
282
283                 db.SubmitChanges();
284             }
285         }
286
287         private void mapWeatherInformation_Tap(object sender, System.Windows.Input.GestureEventArgs e)
288         {
289             // TODO: if exception from here application will crash (I guess)
290             var point = e.GetPosition(this.mapWeatherInformation);
291             GeoCoordinate geocoordinate = this.mapWeatherInformation.ConvertViewportPointToGeoCoordinate(point);
292             ShowProgressBar();
293             ReverseGeocodeAndUpdateMap(geocoordinate);
294         }
295
296         private async void GetCurrentLocationButton_Click(object sender, RoutedEventArgs e)
297         {
298             //Geolocator geolocator = new Geolocator();
299             //geolocator.DesiredAccuracyInMeters = 50;
300             //if (geolocator.LocationStatus != PositionStatus.Ready)
301             //{
302             //    // TODO: to use ToastPrompt from the Coding4Fun Toolkit (using NuGet)
303             //    return;
304             //}
305             
306             // TODO: if exception from here application will crash (I guess)
307             await this.GetCurrentLocationAndUpdateMap();
308         }
309
310         private void SaveLocationButton_Click(object sender, RoutedEventArgs e)
311         {
312             // TODO: Could Center be null?
313             StoreLocation(this.mapWeatherInformation.Center, this.LocationTextCity.Text, this.LocationTextCountry.Text);
314         }
315
316         private void ShowProgressBar()
317         {
318             GetCurrentLocationButton.IsEnabled = false;
319             GetCurrentLocationButton.Visibility = Visibility.Collapsed;
320             SaveLocationButton.IsEnabled = false;
321             SaveLocationButton.Visibility = Visibility.Collapsed;
322             ProgressBarRemoteData.IsEnabled = true;
323             ProgressBarRemoteData.Visibility = Visibility.Visible;
324         }
325
326         private void RemoveProgressBar()
327         {
328             GetCurrentLocationButton.IsEnabled = true;
329             GetCurrentLocationButton.Visibility = Visibility.Visible;
330             SaveLocationButton.IsEnabled = true;
331             SaveLocationButton.Visibility = Visibility.Visible;
332             ProgressBarRemoteData.IsEnabled = false;
333             ProgressBarRemoteData.Visibility = Visibility.Collapsed;
334         }
335
336
337
338         private void ZoomOutButton_Click(object sender, RoutedEventArgs e)
339         {
340             this.mapWeatherInformation.ZoomLevel = this.mapWeatherInformation.ZoomLevel - 1;
341         }
342
343         private void ZoomInButton_Click(object sender, RoutedEventArgs e)
344         {
345             this.mapWeatherInformation.ZoomLevel = this.mapWeatherInformation.ZoomLevel + 1;
346         }
347     }
348 }