Geolocalize a device and store coordinates on Webserver


Introduction

In this article we'll see how to make a simple geolocalizing Windows Phone App, and how to store the coordinates it acquires on a remote Web server, for further examinations. To make the Windows Phone app we'll use C#, while the Web side will be realized in PHP + MySQL, in order to make it usable on every system.

Prerequisites

To use the code presented in the article, you'll need:

  • Windows 8.1
  • Visual Studio with Windows Phone 8.1 SDK
  • A local or hosted web server, with Apache with PHP support enabled, plus MySQL database (phpMyAdmin could be useful too). Alternatively, the web server part could be written in ASP/ASP.NET.

Configuring each one of the above prerequisites goes beyond the scope of the article, so i will not address this point, assuming that everything was previously successfully configured and running.
If you need a brief reference on how to install an Apache plus PHP/MySQL server, you could refer to my article Quickly installing LAMP server on Debian or Ubuntu Linux

Analyzing the scenario

Thinking about a demonstrative method to show geolocalization functions, we will remain as simple as possible: keep in mind there are many security aspects that we won't see here (but maybe in a next article), so what you'll read is intended to give a general insight, or a starting point to delevop further solutions. What we'll consider here starts from a simple concept: we have a device (in my case a smartphone) with GPS sensor and network capabilities, that must communicate data through Internet. On the other side, we'll have a program running on a web server, awaiting to be called upon to receive those data ad saving them. In the following, we will realize a C# app capable of acquiring the device geographical position (in terms of latitude/longitude), and sending those data to a specific URI. Server-side, we will realize a PHP script that will read the sent data as GET parameters, proceeding then in saving them in a MySQL table.

Database setup

First thing first, we must create a table apt to store the collected data. The following is the T-SQL script for table creation, that we could run in phpMyAdmin. I've created a new database, named "geolog", in which i'll create the table "entries". Here, we will store all the data our smartphone will send, i.e. the latitude/longitude, the device name (in case we want to track different devices, picking out each of them by name), and user annotations. The remaining fields (IdEntry and TimeStamp) will be automatically compiled by default constraints on columns: IdEntry is an auto-increment field, while TimeStamp will get its default value from the current timestamp occurring at INSERT operation.

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
 
CREATE TABLE  entries (
  IdEntry int(9) NOT NULL AUTO_INCREMENT,
  `TimeStamp` timestamp  NOT NULL  DEFAULT CURRENT_TIMESTAMP,
  Latitude decimal(12,8) NOT NULL DEFAULT  '0.00000000',
  Longitude decimal(12,8) NOT NULL DEFAULT  '0.00000000',
  Device varchar(50) NOT NULL DEFAULT  '',
  Annotation text NOT NULL,
  PRIMARY KEY  (IdEntry),
  KEY Idx_Device (Device),
  KEY Idx_Entry (IdEntry,`TimeStamp`)
) ENGINE=InnoDB  DEFAULT  CHARSET=utf8 AUTO_INCREMENT=1;

Now, on our MySQL instance, we should see our empty table, and we are ready to jot down some PHP to achieve the server-side part: having a script which will receive data from the external world, writing them in our table, and a second script to query those data. Let's start from creating a PHP page, named index.php.

Storing data with PHP

Index.php must perform a very simple task: receiving a given GET request, it must read every GET parameter to execute then a T-SQL INSERT operation on "entries" table, in order to memorize the passed values. Again, please note that i haven't implemented any security practice (apart from the use of prepared statements, which corresponds to parametrized queries in ASP.NET): if you wish to use those pages in a real scenario, you'd better to secure them up first.

The code is minimal and pretty self-explanatory: first we'll open a connection towards our database, proceeding then in reading the GET parameters, and executing our INSERT, to finally close our connection. That's all what it takes for now.

01.<?php
02.   //-- Connecting to "geolog" database, on "localhost" server: replace <USER> and <PASSWORD> variables with the ones which corresponds to your MySQL installation
03.   $mysqli = new mysqli('localhost', '<USER>',  '<PASSWORD>',  'geolog');
04.   if ($mysqli->connect_errno) {
05.      echo "Failed to connect to MySQL: ("  . $mysqli->connect_errno . ") " .  $mysqli->connect_error;
06.      exit();
07.   }
08. 
09.   //-- Preparing parametrized INSERT
10.   if (!($stmt  = $mysqli->prepare("INSERT INTO entries(Latitude, Longitude, Device, Annotation) VALUES (?, ?, ?, ?)"))) {
11.       echo "Prepare failed: (" . $mysqli->errno . ") " .  $mysqli->error;
12.   }
13. 
14.   //-- Acquire GET parameters   
15.   $latitude   = $_GET['lt'];
16.   $longitude  = $_GET['ln'];
17.   $device     = $_GET['d'];
18.   $annotation = $_GET['n'];
19.    
20.   //-- Bind GET parameters to prepared statement's variables
21.   if (!$stmt->bind_param("ddss", $latitude,  $longitude,  $device, $annotation)) {
22.      echo "Binding parameters failed: ("  . $stmt->errno . ") " .  $stmt->error;
23.   }
24. 
25.   //-- Execute INSERT query
26.   if (!$stmt->execute()) {
27.       echo "Execute failed: (" . $stmt->errno . ") " .  $stmt->error;
28.   }   
29. 
30.   //-- Closing connection / cleanup
31.   $stmt->close();
32.   mysqli_close($mysqli);
33.?>

What it means in practice, is that if we open a browser, calling a proper formed URL, we'll end up writing in our database. Let's see an example, assuming our webserver is running on localhost.

The /test subdirectory you see in the address bar is a virtual directory I've created on Apache to host the web application: it could be anything of your choice. Please refer to the Apache's online documentation for further information on VirtualHosts configuration.

Send geocoordinates to Web server

Now it's the time to write our Windows Phone 8.1 app. In the source code you can find the project labeled as «FollowMe». We will have two pages in it: one dedicated to the check-in part, while the second one will be used for the app's settings, such as the URI to query to store data. More on this later. Our app must do what we've done manually some lines above: calculate where the device is, and calling a proper formed URL (where our web application awaits).

Lets start with the settings page: it will contain a TextBox to specify the URI to be queried and a ComboBox to indicate the Culture to be used when sending coordinates. We could write it as the following:

01.<Page
02.    x:Class="FollowMe.Settings"
03.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
04.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
05.    xmlns:local="using:FollowMe"
06.    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
07.    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
08.    mc:Ignorable="d"
09.    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
10.    Loaded="Page_Loaded">
11.    <Page.BottomAppBar>
12.        <CommandBar>
13.            <AppBarButton Icon="Accept" Label="Save" Click="AppBarButton_Click"/>
14.            <AppBarButton Icon="Cancel" Label="Cancel" Click="CancelButton_Click"/>
15.        </CommandBar>
16.    </Page.BottomAppBar>
17. 
18.    <Grid x:Name="LayoutRoot">
19. 
20.        <Grid.ChildrenTransitions>
21.            <TransitionCollection>
22.                <EntranceThemeTransition/>
23.            </TransitionCollection>
24.        </Grid.ChildrenTransitions>
25. 
26.        <Grid.RowDefinitions>
27.            <RowDefinition Height="Auto"/>
28.            <RowDefinition Height="*"/>
29.        </Grid.RowDefinitions>
30. 
31.        <!-- Title Panel -->
32.        <StackPanel Grid.Row="0" Margin="19,0,0,0">
33.            <TextBlock Text="FollowMe" Style="{ThemeResource TitleTextBlockStyle}" Margin="0,12,0,0"/>
34.            <TextBlock Text="Settings" Margin="0,-6.5,0,26.5" Style="{ThemeResource HeaderTextBlockStyle}" CharacterSpacing="{ThemeResource PivotHeaderItemCharacterSpacing}"/>
35.        </StackPanel>
36. 
37.        <TextBlock HorizontalAlignment="Left" Margin="19,9.833,0,0" Grid.Row="1" TextWrapping="Wrap" Text="Web service URI" VerticalAlignment="Top" FontFamily="Segoe WP" FontSize="18"/>
38.        <TextBox x:Name="txtWebUri" HorizontalAlignment="Left" Margin="19,38.833,0,0" Grid.Row="1" TextWrapping="Wrap" VerticalAlignment="Top" Width="362" PlaceholderText="http://localhost/"/>
39.        <TextBlock HorizontalAlignment="Left" Margin="19,103.833,0,0" Grid.Row="1" TextWrapping="Wrap" Text="Culture for coordinates" VerticalAlignment="Top" FontFamily="Segoe WP" FontSize="18"/>
40.        <ComboBox x:Name="cmbLang" HorizontalAlignment="Left" Margin="19,122.833,0,0" Grid.Row="1" VerticalAlignment="Top" Width="182">
41.            <x:String>it-IT</x:String>
42.            <x:String>en-US</x:String>
43.        </ComboBox>
44.    </Grid>
45.</Page>

Please note I've added a CommandBar to manage two AppBarButton, one to save our changes, and the second to cancel them (and simply close the page). 
The code-behind our page will be:

01.using FollowMe.Common;
02.using System;
03.using Windows.Storage;
04.using Windows.UI.Xaml;
05.using Windows.UI.Xaml.Controls;
06.using Windows.UI.Xaml.Navigation;
07. 
08.namespace FollowMe
09.{
10. 
11.    public sealed  partial class  Settings : Page
12.    {
13.        private NavigationHelper navigationHelper;
14.        private ObservableDictionary defaultViewModel = new ObservableDictionary();
15. 
16.        public Settings()
17.        {
18.            this.InitializeComponent();
19. 
20.            this.navigationHelper = new  NavigationHelper(this);
21.            this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
22.            this.navigationHelper.SaveState += this.NavigationHelper_SaveState;
23.        }
24. 
25.        private void  NavigationHelper_SaveState(object sender, SaveStateEventArgs e)
26.        {
27.        }
28. 
29.        private void  NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
30.        {
31.        }
32. 
33.        public NavigationHelper NavigationHelper
34.        {
35.            get { return this.navigationHelper; }
36.        }
37. 
38.        public ObservableDictionary DefaultViewModel
39.        {
40.            get { return this.defaultViewModel; }
41.        }
42. 
43. 
44.         #region NavigationHelper registration
45. 
46.        protected override  void OnNavigatedTo(NavigationEventArgs e)
47.        {
48.            this.navigationHelper.OnNavigatedTo(e);
49.        }
50. 
51.        protected override  void OnNavigatedFrom(NavigationEventArgs e)
52.        {
53.            this.navigationHelper.OnNavigatedFrom(e);
54.        }
55. 
56.        #endregion
57. 
58.        private void  AppBarButton_Click(object sender, RoutedEventArgs e)
59.        {
60.            var ap = ApplicationData.Current.LocalSettings;
61.            ap.Values["WebURI"] = txtWebUri.Text;
62.            ap.Values["Language"] = cmbLang.SelectedValue.ToString();
63. 
64.            Frame.Navigate(typeof(MainPage));
65.        }
66. 
67.        private void  Page_Loaded(object  sender, RoutedEventArgs e)
68.        {
69.            var ap = ApplicationData.Current.LocalSettings;
70.            try
71.            {
72.                txtWebUri.Text = ap.Values["WebURI"].ToString();
73.                cmbLang.SelectedValue = ap.Values["Language"].ToString();
74.            }
75.            catch {
76.            }
77.        }
78. 
79.        private void  CancelButton_Click(object sender, RoutedEventArgs e)
80.        {
81.            Frame.Navigate(typeof(MainPage));
82.        }
83.    }
84.}

Very simply, all the Settings page logic revolver around the Click event of the two AppBarButton: the cancel button simply navigates back to MainPage, while the save button will write in the LocalSettings of our app (from the namespace Windows.Storage) the parameters we've specified for txtWebUri TextBox and cmbLang ComboBox, then it also navigates back to the MainPage.

The MainPage Page is the core of the app. It is constituted simply by a button, a TextBox for any notes the user may want to indicate, and two TextBlocks in which we'll expose geocoordinates (for debug purposes only, the TextBlocks are not really required). Lets see the XAML for MainPage:

01.<Page
02.    x:Class="FollowMe.MainPage"
03.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
04.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
05.    xmlns:local="using:FollowMe"
06.    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
07.    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
08.    mc:Ignorable="d">
09.    <Page.BottomAppBar>
10.        <CommandBar>
11.            <AppBarButton Icon="Manage" Label="Settings" Click="AppBarButton_Click"/>
12.        </CommandBar>
13.    </Page.BottomAppBar>
14. 
15.    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
16.        <Button Content="Check in" Click="button1_Click"
17.            HorizontalAlignment="Left" Margin="10,381,0,0" Name="button1" VerticalAlignment="Top" Height="117" Width="380" />
18. 
19.        <TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="FollowMe" VerticalAlignment="Top" FontFamily="Segoe WP" FontSize="28" FontWeight="Bold"/>
20.        <TextBox Name="txtNotes" HorizontalAlignment="Left" Margin="10,69,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="380" PlaceholderText="Type any notes here"/>
21. 
22.        <TextBlock HorizontalAlignment="Left" Margin="10,130,0,0" TextWrapping="Wrap" Text="Latitude" VerticalAlignment="Top"/>
23.        <TextBlock HorizontalAlignment="Left" Margin="10,152,0,0" TextWrapping="Wrap" Text="Longitude" VerticalAlignment="Top"/>
24.        <TextBlock x:Name="lblLatitude" HorizontalAlignment="Left" Margin="104,130,0,0" TextWrapping="Wrap" Text="0.00000000" VerticalAlignment="Top"/>
25.        <TextBlock x:Name="lblLongitude" HorizontalAlignment="Left" Margin="104,152,0,0" TextWrapping="Wrap" Text="0.00000000" VerticalAlignment="Top"/>
26. 
27.    </Grid>
28.</Page>

An AppBarButton will allow us to access the Settings page. No other peculiarities are visible here. The only thing that will bring us to consider the code-behind is the presence of a Click event on the sole Button of the page. 

01.using System;
02.using Windows.UI.Xaml;
03.using Windows.UI.Xaml.Controls;
04.using Windows.Devices.Geolocation;
05.using Windows.Web.Http;
06.using Windows.UI.Popups;
07.using System.Globalization;
08.using Windows.Security.ExchangeActiveSyncProvisioning;
09.using Windows.Storage;
10. 
11.namespace FollowMe
12.{
13.    public sealed  partial class  MainPage : Page
14.    {
15.        Geolocator geo = null;
16. 
17.        public MainPage()
18.        {
19.            this.InitializeComponent();
20.        }
21. 
22. 
23.        private async void button1_Click(object sender, RoutedEventArgs e)
24.        {
25.            geo = new  Geolocator();
26.            bool isErr = false;
27.            string errMsg = "";
28. 
29.            button1.IsEnabled = false;
30. 
31.            try
32.            {
33.                Geoposition pos = await geo.GetGeopositionAsync();
34.                double lat = pos.Coordinate.Point.Position.Latitude;
35.                double lon = pos.Coordinate.Point.Position.Longitude;
36. 
37.                lblLatitude.Text = lat.ToString();
38.                lblLongitude.Text = lon.ToString();
39. 
40.                var ap = ApplicationData.Current.LocalSettings;
41. 
42.                CultureInfo cI = new  CultureInfo(ap.Values["Language"].ToString());
43. 
44.                String devName = new  EasClientDeviceInformation().FriendlyName;
45.                 
46.                HttpClient hwc = new  HttpClient();
47.                Uri myAddress = new  Uri(ap.Values["WebURI"].ToString() + "?lt=" +
48.                    lat.ToString(cI.NumberFormat) +
49.                    "&ln=" + lon.ToString(cI.NumberFormat) +
50.                    "&d=" + devName + "&n=" + txtNotes.Text);
51. 
52.                HttpResponseMessage x = await hwc.GetAsync(myAddress);
53.                 
54.            }
55.            catch (Exception ex)
56.            {
57.                isErr = true;
58.                errMsg = ex.Message;
59.            }
60. 
61.            if (isErr)
62.            {
63.                var dialog = new  MessageDialog(errMsg);
64.                await dialog.ShowAsync();
65.            }
66. 
67.            button1.IsEnabled = true;
68.            geo = null;
69.        }
70. 
71.        private void  AppBarButton_Click(object sender, RoutedEventArgs e)
72.        {
73.            Frame.Navigate(typeof(Settings));
74.        }
75. 
76.    }
77.}

In clicking button1, a new instance of Geolocator is set. From it, we try to retrieve asynchronously the device position, to read our current longitude/latitude, writing then to our TextBlocks. With the lines from 25 to 36, we've have all it takes to geolocalize us, the rest of the code is about reaching our webserver. First, we access the Application's LocalSettings to read our URI and Language parameters, initializing a Culture with the value of the latter. This will serve us in determining how to pass our geocoordinates to the webserver (mainly about decimal separator, if comma or dot).

Then, using EasClientDeviceInformation class, we extract our device FriendlyName, or the name of our device (line 44).
Next, we pack up a web request using the HttpClient class, calling asynchronously a previously forget URI (line from 46 to 52), in which we've indicated our coordinates, device name, and any annotations as GET parameters, the way our PHP page expects them.

In case of any error that may occur, a MessageDialog will be emitted.

IMPORTANT: to make everything working, two fundamental things must be checked. The first one, is to have location services active on your device. The second one, is to enable location capability in the app itself. In your project, you must double-click on package.appxmanifest file, selecting the Capabilities tab. Then check Location in the Capabilities list. Without it, your app won't be able to geolocalize you.

That's it: as you saw, is a very simple app, with many improvements that could be made. However, the presented one will suffice for our means.

Examining collected data

As we saw, when our app makes a web request, a new record will be inserted in the remote database. Now it's time to query our database to examining data. The following code will realize a simple HTML page, containing a table with a row per record. It will show the time a record was inserted, the coordinates, the device name, user's annotations (if any). Last, each row will be terminated by an hyperlink to navigate to a page in which we can have a look on a map (together with a graphical mark on the check-in spot). We will name the following page as "geolist.php"
   

01.<html>
02.   <head>
03.      <title>GeoLog</title>      
04.      <!-- OMITTED: CSS part, you'll find it in the complete source code -->
05.   </head>
06. 
07.   <body>
08.      <table>
09.         <tr>
10.            <th>Id</th>
11.            <th>Date</th>
12.            <th>Lat.</th>
13.            <th>Long.</th>
14.            <th>Device</th>
15.            <th>Annotations</th>
16.            <th>Map</th>
17.         </tr>
18.          
19.         <?php
20. 
21.          //-- Connecting to "geolog" database, on "localhost" server
22.          $mysqli = new mysqli('localhost', '<USER>',  '<PASSWORD>',  'geolog');
23.          if ($mysqli->connect_errno) {
24.             echo "Failed to connect to MySQL: ("  . $mysqli->connect_errno . ") " .  $mysqli->connect_error;
25.             exit();
26.          }
27. 
28.          //-- Declaring and executing a SELECT on "entries" table, to retrieve all the records in it
29.          $query = "SELECT * FROM entries ORDER BY IdEntry";
30.          $result = $mysqli->query($query);
31. 
32.          //-- For each retrieved record, we'll add to the DOM a table row, containing the read values
33.          while($row = $result->fetch_array()){ ?>
34.           
35.             <tr>
36.                <td><?php echo $row['IdEntry'];?></td>
37.                <td><?php echo $row['TimeStamp'];?></td>
38.                <td><?php echo $row['Latitude'];?></td>
39.                <td><?php echo $row['Longitude'];?></td>
40.                <td><?php echo $row['Device'];?></td>
41.                <td><?php echo $row['Annotation'];?></td>
42.                <td>[<a href="map.php?lt=<?php echo $row['Latitude'];?>&ln=<?php echo $row['Longitude'];?>&d=<?php echo $row['Device'];?>&n=<?php echo $row['Annotation'];?>">Link</a>]</td>
43.             </tr>
44.          <?php }
45. 
46.          //-- Close connection / cleanup
47.          $result->close();
48.          $mysqli->close();
49.         ?>
50.   </body>

Omitting the styling part, what we've done here is to establish a connection to our MySQL database (namely, "geolog") to proceed in querying the "entries" table. For reach recordset, we will create a table row with its children: six cells which will expose the read data. Please note our final hyperlink targets a page named "map.php". That page is the one in which we'll render the map relative to the given coordinates. Let's see how his page looks like.

01.<?php
02.   $latitude   = $_GET['lt'];
03.   $longitude  = $_GET['ln'];
04.   $device     = $_GET['d'];
05.   $annotation = $_GET['n'];
06.?>
07. 
08.<html>
09.  <head>
10.    <title>GeoLog</title>
11.    <meta name="viewport"  content="initial-scale=1.0, user-scalable=no">
12.    <meta charset="utf-8">
13.    <style>
14.      html, body, #map {
15.        height: 100%;
16.        margin: 0px;
17.        padding: 0px;
18.        width:100%;
19.      }
20.    </style>
21.    <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true&language=it"></script>
22.    <script>
23.       var initialize = function(){
24.           var latlng = new google.maps.LatLng(<?php echo $latitude,  ",", $longitude;?>);
25.           var options = { zoom: 18,
26.                           center: latlng,
27.                           mapTypeId: google.maps.MapTypeId.ROADMAP
28.                         };
29.           var map = new google.maps.Map(document.getElementById('map'), options);
30.            
31.           var marker = new google.maps.Marker({ position: latlng,
32.                                                 map: map, 
33.                                                 title: '<?php echo $device;?>'  });
34.       }
35.        
36.       window.onload = initialize;
37.    </script>
38.  </head>
39.  <body>
40.     <div id="map"></div>
41.  </body>
42.</html>

To avoid requerying our database, we will use the GET method to pass to our map.php page all the information it needs. The page make use of JavaScript, using some functions from the Google Places's API (developers reference here). The main function work this way: it creates a map from given latitude and longitude, assigning it to a DIV element for being drawn. Then, a Marker is created to add a graphical placeholder on that specific location. Simple as that. Since we need to use GET parameters as JavaScript variables, we'll use a little trick here: JavaScript is executed client-side, on the user's machine; PHP is executed server-side. So, if we add PHP instructions, they will be executed before the JS part, and when the page "arrives" on the user's device, it will be already modified by the remote PHP execution. This way, we can simply echo the GET variables as JavaScript parameters, and they will be written into the code that will be run on client-side, allowing to view the correct map.

Testing the whole package

Having finished coding, we can finally pack up everything and calling for an overall test. First things first, we'll open up our WP app on our device, configuring it towards the URI we'll set up. Next, we'll click on the «Check-in» button to start geolocalization and further web request. In that phase, our app will build the URI to be queried, proceeding in visiting it (and consequently firing up the remote INSERT procedure).

  

Now, we'll open a browser, visiting the geolist.php page, to examine inserted records. Our recent check-in will be shown, and we can click on its link to open the map.php page, which will show graphically our previously saved position.


   

Source Code

The source code used in the article can be downloaded at: https://code.msdn.microsoft.com/Geolocalize-a-device-and-dae0d265

Bibliography

Other languages

The present article is localized in the following languages: