WeatherInformation WP8: more TODOs/doubts
[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                 // TODO: Should I call UpdateMap even if there are not results?
90                 // I could use country and city default values in that case...
91                 // Problem this method: requires GeoCoordinate and I wouldn't have it here...
92                 // Somehow this method should take them...
93                 // TODO: if user changed the page, where is this going to appear?
94                 MessageBox.Show(
95                     AppResources.NoticeErrorLocationAutodetection,
96                     AppResources.UnavailableAutomaticCurrentLocationMessageBox,
97                     MessageBoxButton.OK);
98             }
99             else
100             {
101                 if (eventData.Result.Count > 0)
102                 {
103                     // TODO: Should I call UpdateMap even if there are not results?
104                     // I could use country and city default values in that case...
105                     // Problem this method: requires GeoCoordinate and I wouldn't have it here...
106                     // Somehow this method should take them...
107                     MapAddress address = eventData.Result[0].Information.Address;
108                     GeoCoordinate currentGeoCoordinate = eventData.Result[0].GeoCoordinate;
109
110                     UpdateMap(currentGeoCoordinate, address.City, address.Country);
111                 }
112             }
113         }
114
115         private void UpdateMap(GeoCoordinate geoCoordinate, string city, string country)
116         {
117             // Create a small circle to mark the current location.
118             Ellipse myCircle = new Ellipse();
119             myCircle.Fill = new SolidColorBrush(Colors.Blue);
120             myCircle.Height = 20;
121             myCircle.Width = 20;
122             myCircle.Opacity = 50;
123
124             // Create a MapOverlay to contain the circle.
125             MapOverlay myLocationOverlay = new MapOverlay();
126             myLocationOverlay.Content = myCircle;
127             myLocationOverlay.PositionOrigin = new Point(0.5, 0.5);
128             myLocationOverlay.GeoCoordinate = geoCoordinate;
129
130             // Create a MapLayer to contain the MapOverlay.
131             MapLayer myLocationLayer = new MapLayer();
132             myLocationLayer.Add(myLocationOverlay);
133
134             this.mapWeatherInformation.Layers.Clear();
135
136             // TODO: problems? user could press save location and if she is fast enough she
137             // could not realize the location changed.
138             // But well... She will realize later... So.. Is this really a problem?
139             this.mapWeatherInformation.Center = geoCoordinate;
140             this.mapWeatherInformation.ZoomLevel = 13;
141
142             if (string.IsNullOrEmpty(city))
143             {
144                 city = AppResources.DefaultCity;
145             }
146             if (string.IsNullOrEmpty(country))
147             {
148                 country = AppResources.DefaultCountry;
149             }
150             this.LocationTextCityCountry.Text = String.Format(CultureInfo.InvariantCulture, "{0}, {1}", city, country);
151             _city = city;
152             _country = country;
153             // Add the MapLayer to the Map.
154             this.mapWeatherInformation.Layers.Add(myLocationLayer);
155         }
156
157         private static class CoordinateHelper
158         {
159             public static GeoCoordinate ConvertGeocoordinate(Geocoordinate geocoordinate)
160             {
161                 return new GeoCoordinate
162                     (
163                     geocoordinate.Latitude,
164                     geocoordinate.Longitude,
165                     geocoordinate.Altitude ?? Double.NaN,
166                     geocoordinate.Accuracy,
167                     geocoordinate.AltitudeAccuracy ?? Double.NaN,
168                     geocoordinate.Speed ?? Double.NaN,
169                     geocoordinate.Heading ?? Double.NaN
170                     );
171             }
172         }
173
174         // TODO: check data before storing :(
175         // http://stackoverflow.com/questions/4521435/what-specific-values-can-a-c-sharp-double-represent-that-a-sql-server-float-can
176         private void StoreLocation(GeoCoordinate geocoordinate)
177         {
178             Location locationItem = null;
179             using (var db = new LocationDataContext(LocationDataContext.DBConnectionString))
180             {
181                 // Define the query to gather all of the to-do items.
182                 // var toDoItemsInDB = from Location location in _locationDB.Locations where location.IsSelected select location;
183                 locationItem = db.Locations.Where(location => location.IsSelected).FirstOrDefault();
184
185                 if (locationItem != null)
186                 {
187                     locationItem.Latitude = geocoordinate.Latitude;
188                     locationItem.Longitude = geocoordinate.Longitude;
189                     locationItem.Altitude = geocoordinate.Altitude;
190                     locationItem.City = _city ?? "";
191                     locationItem.Country = _country ?? "";
192                     locationItem.HorizontalAccuracy = geocoordinate.HorizontalAccuracy;
193                     locationItem.VerticalAccuracy = geocoordinate.VerticalAccuracy;
194                     locationItem.Speed = geocoordinate.Speed;
195                     locationItem.Course = geocoordinate.Course;
196                     locationItem.IsSelected = true;
197                     locationItem.LastRemoteDataUpdate = null;
198                 }
199                 else
200                 {
201                     locationItem = new Location()
202                     {
203                         Latitude = geocoordinate.Latitude,
204                         Longitude = geocoordinate.Longitude,
205                         Altitude = geocoordinate.Altitude,
206                         City = _city ?? "",
207                         Country = _country ?? "",
208                         HorizontalAccuracy = geocoordinate.HorizontalAccuracy,
209                         VerticalAccuracy = geocoordinate.VerticalAccuracy,
210                         Speed = geocoordinate.Speed,
211                         Course = geocoordinate.Course,
212                         IsSelected = true,
213                         LastRemoteDataUpdate = null,
214                     };
215
216                     // Add a location item to the local database.
217                     db.Locations.InsertOnSubmit(locationItem);
218                 }
219
220                 db.SubmitChanges();
221             }
222         }
223
224         private GeoCoordinate ConvertLocation(Location locationItem)
225         {
226             return new GeoCoordinate
227                 (
228                 locationItem.Latitude,
229                 locationItem.Longitude,
230                 locationItem.Altitude,
231                 locationItem.HorizontalAccuracy,
232                 locationItem.VerticalAccuracy,
233                 locationItem.Speed,
234                 locationItem.Course
235                 );
236         }
237
238         private void mapWeatherInformation_Tap(object sender, System.Windows.Input.GestureEventArgs e)
239         {
240             var point = e.GetPosition(this.mapWeatherInformation);
241             GeoCoordinate geocoordinate = this.mapWeatherInformation.ConvertViewportPointToGeoCoordinate(point);
242             ReverseGeocodeAndUpdateMap(geocoordinate);
243         }
244
245         private async void GetCurrentLocationButton_Click(object sender, RoutedEventArgs e)
246         {
247             try
248             {
249                 await this.GetCurrentLocationAndUpdateMap();
250             }
251             catch (Exception ex)
252             {
253                 // TODO: make sure when exception in GetCurrentLocationAndUpdateMap we catch it here.
254                 // TODO: if user changed the page, where is this going to appear?
255                 MessageBox.Show(
256                     AppResources.NoticeErrorLocationAutodetection,
257                     AppResources.UnavailableAutomaticCurrentLocationMessageBox,
258                     MessageBoxButton.OK);
259             }
260         }
261
262         private void SaveLocationButton_Click(object sender, RoutedEventArgs e)
263         {
264             // TODO: Could there some problem if user clicks button and thread is in this very moment updating map?
265             var geoCoordinate = this.mapWeatherInformation.Center;
266
267             StoreLocation(geoCoordinate);
268         }
269
270         private void ZoomOutButton_Click(object sender, RoutedEventArgs e)
271         {
272             this.mapWeatherInformation.ZoomLevel = this.mapWeatherInformation.ZoomLevel - 1;
273         }
274
275         private void ZoomInButton_Click(object sender, RoutedEventArgs e)
276         {
277             this.mapWeatherInformation.ZoomLevel = this.mapWeatherInformation.ZoomLevel + 1;
278         }
279     }
280 }