WeatherInformation WP8: map page. Should I save UI state?
[CSharpForFun/.git] / WindowsPhone / WP8 / WeatherInformation / WeatherInformation / MapPage.xaml.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Windows;
5 using System.Windows.Navigation;
6 using Microsoft.Phone.Controls;
7 using Windows.Devices.Geolocation;
8 using System.Device.Location;
9 using System.Windows.Shapes;
10 using System.Windows.Media;
11 using Microsoft.Phone.Maps.Controls;
12 using WeatherInformation.Resources;
13 using System.Globalization;
14 using Microsoft.Phone.Maps.Services;
15 using System.Threading.Tasks;
16 using WeatherInformation.Model;
17
18 namespace WeatherInformation
19 {
20     public partial class MapPage : PhoneApplicationPage
21     {
22         // TODO anything better than these two instance fields? :(
23         private string _city;
24         private string _country;
25
26         public MapPage()
27         {
28             InitializeComponent();
29         }
30
31         protected override void OnNavigatedTo(NavigationEventArgs e)
32         {
33             // TODO: I am not saving the UI state. If location changed but it was not saved
34             // user will have to pick her location again :(
35             Location locationItem = null;
36             using (var db = new LocationDataContext(LocationDataContext.DBConnectionString))
37             {
38                 // Define the query to gather all of the to-do items.
39                 // var toDoItemsInDB = from Location location in _locationDB.Locations where location.IsSelected select location;
40                 locationItem = db.Locations.Where(location => location.IsSelected).FirstOrDefault();
41             }
42             
43             if (locationItem != null)
44             {
45                 GeoCoordinate geoCoordinate = ConvertLocation(locationItem);
46
47                 this.UpdateMap(geoCoordinate, locationItem.City, locationItem.Country);
48             }
49         }
50
51         private async Task GetCurrentLocationAndUpdateMap()
52         {
53             Geolocator geolocator = new Geolocator();
54             geolocator.DesiredAccuracyInMeters = 50;
55
56             Geoposition geoposition = await geolocator.GetGeopositionAsync(
57                 maximumAge: TimeSpan.FromSeconds(10),
58                 timeout: TimeSpan.FromSeconds(10)
59                 );
60             GeoCoordinate currentGeoCoordinate = CoordinateHelper.ConvertGeocoordinate(geoposition.Coordinate);
61
62             ReverseGeocodeAndUpdateMap(currentGeoCoordinate);
63         }
64
65         // TODO: problems updating Map because this method may be called when automatically retrieving
66         //       the current user's location or when user taps on map tyring to choose by herself her location.
67         //       There could be 2 threads updating Map at the same time. Solution: remove the feature
68         //       of getting the current user's location (user must always choose her/his location instead of doing
69         //       it automatically)
70         private void ReverseGeocodeAndUpdateMap(GeoCoordinate currentGeoCoordinate)
71         {
72             ReverseGeocodeQuery currentReverseGeocodeQuery = new ReverseGeocodeQuery();
73             currentReverseGeocodeQuery.GeoCoordinate = currentGeoCoordinate;
74             currentReverseGeocodeQuery.QueryCompleted += QueryCompletedCallback;
75             currentReverseGeocodeQuery.QueryAsync();
76         }
77
78         private void QueryCompletedCallback(object sender, QueryCompletedEventArgs<IList<MapLocation>> eventData)
79         {
80             if (eventData.Cancelled)
81             {
82                 // Be careful!!! If you throw exception from this point your program will finish with "Unhandled Exception".
83                 return;
84             }
85
86             Exception errorException = eventData.Error;
87             if (errorException != null)
88             {
89                 MessageBox.Show(
90                     AppResources.NoticeErrorLocationAutodetection,
91                     AppResources.UnavailableAutomaticCurrentLocationMessageBox,
92                     MessageBoxButton.OK);
93             }
94             else
95             {
96                 if (eventData.Result.Count > 0)
97                 {
98                     MapAddress address = eventData.Result[0].Information.Address;
99                     GeoCoordinate currentGeoCoordinate = eventData.Result[0].GeoCoordinate;
100
101                     UpdateMap(currentGeoCoordinate, address.City, address.Country);
102                 }
103             }
104         }
105
106         private void UpdateMap(GeoCoordinate geoCoordinate, string city, string country)
107         {
108             // Create a small circle to mark the current location.
109             Ellipse myCircle = new Ellipse();
110             myCircle.Fill = new SolidColorBrush(Colors.Blue);
111             myCircle.Height = 20;
112             myCircle.Width = 20;
113             myCircle.Opacity = 50;
114
115             // Create a MapOverlay to contain the circle.
116             MapOverlay myLocationOverlay = new MapOverlay();
117             myLocationOverlay.Content = myCircle;
118             myLocationOverlay.PositionOrigin = new Point(0.5, 0.5);
119             myLocationOverlay.GeoCoordinate = geoCoordinate;
120
121             // Create a MapLayer to contain the MapOverlay.
122             MapLayer myLocationLayer = new MapLayer();
123             myLocationLayer.Add(myLocationOverlay);
124
125             this.mapWeatherInformation.Layers.Clear();
126
127             // TODO: problems? user could press save location and if she is fast enough she
128             // could not realize the location changed.
129             // But well... She will realize later... So.. Is this really a problem?
130             this.mapWeatherInformation.Center = geoCoordinate;
131             this.mapWeatherInformation.ZoomLevel = 13;
132
133             if (string.IsNullOrEmpty(city))
134             {
135                 city = AppResources.DefaultCity;
136             }
137             if (string.IsNullOrEmpty(country))
138             {
139                 country = AppResources.DefaultCountry;
140             }
141             this.LocationTextCityCountry.Text = String.Format(CultureInfo.InvariantCulture, "{0}, {1}", city, country);
142             _city = city;
143             _country = country;
144             // Add the MapLayer to the Map.
145             this.mapWeatherInformation.Layers.Add(myLocationLayer);
146         }
147
148         private static class CoordinateHelper
149         {
150             public static GeoCoordinate ConvertGeocoordinate(Geocoordinate geocoordinate)
151             {
152                 return new GeoCoordinate
153                     (
154                     geocoordinate.Latitude,
155                     geocoordinate.Longitude,
156                     geocoordinate.Altitude ?? Double.NaN,
157                     geocoordinate.Accuracy,
158                     geocoordinate.AltitudeAccuracy ?? Double.NaN,
159                     geocoordinate.Speed ?? Double.NaN,
160                     geocoordinate.Heading ?? Double.NaN
161                     );
162             }
163         }
164
165         // TODO: check data before storing :(
166         // http://stackoverflow.com/questions/4521435/what-specific-values-can-a-c-sharp-double-represent-that-a-sql-server-float-can
167         private void StoreLocation(GeoCoordinate geocoordinate)
168         {
169             Location locationItem = null;
170             using (var db = new LocationDataContext(LocationDataContext.DBConnectionString))
171             {
172                 // Define the query to gather all of the to-do items.
173                 // var toDoItemsInDB = from Location location in _locationDB.Locations where location.IsSelected select location;
174                 locationItem = db.Locations.Where(location => location.IsSelected).FirstOrDefault();
175
176                 if (locationItem != null)
177                 {
178                     locationItem.Latitude = geocoordinate.Latitude;
179                     locationItem.Longitude = geocoordinate.Longitude;
180                     locationItem.Altitude = geocoordinate.Altitude;
181                     locationItem.City = _city ?? "";
182                     locationItem.Country = _country ?? "";
183                     locationItem.HorizontalAccuracy = geocoordinate.HorizontalAccuracy;
184                     locationItem.VerticalAccuracy = geocoordinate.VerticalAccuracy;
185                     locationItem.Speed = geocoordinate.Speed;
186                     locationItem.Course = geocoordinate.Course;
187                     locationItem.IsSelected = true;
188                     locationItem.LastRemoteDataUpdate = null;
189                 }
190                 else
191                 {
192                     locationItem = new Location()
193                     {
194                         Latitude = geocoordinate.Latitude,
195                         Longitude = geocoordinate.Longitude,
196                         Altitude = geocoordinate.Altitude,
197                         City = _city ?? "",
198                         Country = _country ?? "",
199                         HorizontalAccuracy = geocoordinate.HorizontalAccuracy,
200                         VerticalAccuracy = geocoordinate.VerticalAccuracy,
201                         Speed = geocoordinate.Speed,
202                         Course = geocoordinate.Course,
203                         IsSelected = true,
204                         LastRemoteDataUpdate = null,
205                     };
206
207                     // Add a location item to the local database.
208                     db.Locations.InsertOnSubmit(locationItem);
209                 }
210
211                 db.SubmitChanges();
212             }
213         }
214
215         private GeoCoordinate ConvertLocation(Location locationItem)
216         {
217             return new GeoCoordinate
218                 (
219                 locationItem.Latitude,
220                 locationItem.Longitude,
221                 locationItem.Altitude,
222                 locationItem.HorizontalAccuracy,
223                 locationItem.VerticalAccuracy,
224                 locationItem.Speed,
225                 locationItem.Course
226                 );
227         }
228
229         private void mapWeatherInformation_Tap(object sender, System.Windows.Input.GestureEventArgs e)
230         {
231             var point = e.GetPosition(this.mapWeatherInformation);
232             GeoCoordinate geocoordinate = this.mapWeatherInformation.ConvertViewportPointToGeoCoordinate(point);
233             ReverseGeocodeAndUpdateMap(geocoordinate);
234         }
235
236         private async void GetCurrentLocationButton_Click(object sender, RoutedEventArgs e)
237         {
238             try
239             {
240                 await this.GetCurrentLocationAndUpdateMap();
241             }
242             catch (Exception ex)
243             {
244                 // TODO: make sure when exception in GetCurrentLocationAndUpdateMap we catch it here.
245                 MessageBox.Show(
246                     AppResources.NoticeErrorLocationAutodetection,
247                     AppResources.UnavailableAutomaticCurrentLocationMessageBox,
248                     MessageBoxButton.OK);
249             }
250         }
251
252         private void SaveLocationButton_Click(object sender, RoutedEventArgs e)
253         {
254             // TODO: Could there some problem if user clicks button and thread is in this very moment updating map?
255             var geoCoordinate = this.mapWeatherInformation.Center;
256
257             StoreLocation(geoCoordinate);
258         }
259
260         private void ZoomOutButton_Click(object sender, RoutedEventArgs e)
261         {
262             this.mapWeatherInformation.ZoomLevel = this.mapWeatherInformation.ZoomLevel - 1;
263         }
264
265         private void ZoomInButton_Click(object sender, RoutedEventArgs e)
266         {
267             this.mapWeatherInformation.ZoomLevel = this.mapWeatherInformation.ZoomLevel + 1;
268         }
269     }
270 }