Getting the user's location (Intermediate)
In this section, we will detect geolocation support, use the Geolocation API to obtain the user's coordinates (if we can), save the coordinates to MySQL via our PHP callback, and handle any errors that might occur.
Getting ready
Make sure you've set up the required files in the previous section: index.php
to serve the main page, callback.php
to serve as our callback, and feed.php
to serve as our KML feed. In this section, we will be concentrating on index.php
and callback.php
.
How to do it...
Perform the following steps for getting the user's location:
First, set up
lib.php
as discussed here. This will be the file that handles connections to the database for all components in the system.Fill in the blanks at the top of the page with your own database details. Note that for simplicity, I have used the built-in MySQL functions. For a fully fledged PHP application, I recommend using the built-in PDO library.
<?php $server = ''; // Enter your database server here $username = ''; // Enter your database username here $password = ''; // Enter your database password here $database = ''; // Enter your database name here // Connect to the database if (mysql_connect( $server, $username, $password )) { mysql_select_db( $database ); } else { header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); echo "Could not connect to the database."; exit; }
Then, set up
callback.php
. This will accept longitude and latitude data from our location detection page via an HTTPPOST
request, and save it to our database:<?php // Load our common library file, and fail if it isn't present require_once('lib.php'); // Check for the existence of longitude and latitude in our POST request // variables; if they're present, continue attempting to save if (isset($_POST['longitude']) && isset($_POST['latitude'])) { // Cast variables to float (never accept unsanitized input!) $longitude = (float) $_POST['longitude']; $latitude = (float) $_POST['latitude']; // For now, let's hard-code the user identifier to "1" - we can // use PHP sessions and authentication to set this differently later // on $user = 1; // Set the timestamp from the current system time $time = time(); // Put our query together: $query = "insert into points set 'longitude' = {$longitude}, 'latitude' = {$latitude}, 'user_id' = {$user}, 'time' = {$time}"; // Run the query, and return an error if it fails if (!($result = mysql_query($query))) { header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); echo "Could not save point."; exit; } }
Finally, set up
index.php
. This is the page that users will access directly:<!doctype html> <html> <head> <title> Location detector </title> <!-- We're using jQuery to simplify our JavaScript DOM- handling code --> <script src="//code.jquery.com/ jquery-1.9.1.min.js"></script> <script language="javascript"> // This function is called when the Geolocation API successfully // retrieves the user's location function savePosition(point) { // Send the retrieved coordinates to callback.php via a POST // request, and then set the page content to "Location saved" // once this process is complete (or "We couldn't save your // location" if it failed for some reason) $.ajax({ url: 'callback.php', type: 'POST', data: { latitude: point.coords.latitude, longitude: point.coords.longitude }, statusCode: { 500: function() { $('#locationpane').html ('<p>We couldn\'t save your location.</p>'); } } }).done(function() { $('#locationpane').html ('<p>Location saved.</p>'); }).fail(function() { $('#locationpane').html ('<p>We couldn\'t save your location.</p>'); }); } // This function is called when there is a problem retrieving // the user's location (but the Geolocation API is supported in // his or her browser) function errorPosition(error) { switch(error.code) { // Error code 1: permission to access the user's location // was denied case 1: $('#locationpane').html ('<p>No location was retrieved.</p>'); break; // Error code 2: the user's location could not be determined case 2: $('#locationpane').html ('<p>We couldn\'t find you.</p>'); break; // Error code 3: the Geolocation API timed out case 3: $('#locationpane').html ('<p>We took too long trying to find your location.</p>'); break; } } </script> </head> <body> <div id="locationpane"> <p> Waiting for location ... </p> </div> <!-- We're including the Geolocation API code at the bottom of the page so that page content will have loaded first --> <script language="javascript"> // First, check if geolocation support is available if (navigator.geolocation) { // If it is, attempt to get the current position. Instantiate // the savePosition function if the operation was successful, or // errorPosition if it was not. navigator.geolocation.getCurrentPosition (savePosition, errorPosition); } else { // If the browser doesn't support the Geolocation API, tell the user. $('#locationpane').html ('<p>No geolocation support is available.</p>'); } </script> </body> </html>
How it works...
There are two large JavaScript blocks in index.php
, which together interact with the Geolocation API. In the body of the page, we've included a simple div
element with ID locationpane
, which we'll use to give feedback to the user. Every time we give feedback, we do so by changing the HTML contents of locationpane
to contain a paragraph with a different message.
In the header of the page, there are two functions: savePosition
and errorPosition
. savePosition
will be called by the Geolocation API when a location is determined and errorPosition
will be called when there has been an error determining the location.
savePosition
takes a single Position
object as its first parameter. This has the following properties:
coords
: An object encapsulating the location's coordinates, which in turn contains the following attributes:latitude
: This is the user's latitude in degrees. This is a double value.longitude
: This is the user's longitude in degrees. This is a double value.accuracy
: This is the margin of error, in meters. This can be a double or a null value.altitude
: This is the number of meters above the mathematically defined surface of the Earth. This can be a double or a null value.altitudeAccuracy
: This is the margin of error for the altitude, in meters. This can be double or a null value.heading
: This is specified in degrees, clockwise relative to true north. This can be a double or a null value.speed
: This is meters per second. This can be a double or a null value.
timestamp
(DOMTimeStamp
): This is the timestamp that the location was retrieved.
Note that on some systems in certain contexts, the location won't be determined at the time of request; instead, a cached version will be returned. This is why the timestamp is important. However, we will discard it here.
savePosition
uses jQuery's AJAX function to take the latitude and longitude from the coords
object and sends it to callback.php
. It then checks the HTTP response code; if callback.php
has returned an error 500, it tells the user that his/her location could not be saved. (More on this in a moment.)
Meanwhile, if there was an error determining the user's location with the Geolocation API, errorPosition
is called. This takes a PositionError
object as its parameter, which has the following properties:
code
(short): A numeric error codemessage
(DOMstring): An internal error message
Rather than output message, which isn't intended for end users, errorPosition
looks at the error code to determine what kind of feedback to provide to the user:
Error code 1: The user denied the application's request to track his/her location
Error code 2: The user's location could not be determined
Error code 3: The Geolocation API timed out
At the bottom of the page is the code that actually runs the Geolocation API.
Before accessing the JavaScript API functions, it's important to check to make sure that the Geolocation API is supported in the current browser. To do this, you can simply check to make sure the navigator.geolocation
object exists:
if (navigator.geolocation) { /* The Geolocation API is supported */ }
If it doesn't, we should give the user feedback to explain that his/her location cannot be determined. We could also attempt to retrieve the user's location using server-side technologies such as IP geolocation, but this is much less accurate and out of the scope of this book.
Once we're sure, we can use the Geolocation API, we can call navigator.geolocation.getCurrentPosition
, with references to the success and failure functions as its parameters:
navigator.geolocation.getCurrentPosition(savePosition, errorPosition);
It's worth mentioning here that a third parameter is available, which takes a PositionOptions
object. This may contain the following properties:
enableHighAccuracy
: This is a Boolean value. It enables high accuracy mode (default: off
).timeout
: This is a Boolean value. This is the threshold beyond which the API times out (in milliseconds; the default is no limit).maximumAge: This is a long value. The maximum age of a cached location that we'll accept, in milliseconds (
default: 0
).
If we enable high accuracy mode, mobile devices with GPS units will attempt to use it to get the best possible location information (if their owner has allowed it); otherwise, they may default to using trilateration to determine the location. However, because not all devices have these units and because GPS signals are not always available, requesting the current position with high accuracy is more likely to fail.
While high accuracy, location detection will not automatically fall back to the standard method, you can achieve this yourself, if you like. First, call getCurrentPosition
with highAccuracy
set to true
and with a reference to a new error handling function:
navigator.geolocation.getCurrentPosition(savePosition, highAccuracyErrorPosition, {enableHighAccuracy: true});
All this new error handler, highAccuracyErrorPosition
, does is call getCurrentPosition
with highAccuracy
set to false
:
function highAccuracyErrorPosition(error) { navigator.geolocation.getCurrentPosition(savePosition, errorPosition, {enableHighAccuracy: false}); }
The result is that the browser attempts to use high-accuracy location detection, and falls back to the standard method if it is not available due to some reason. Should the user decline authorization for location information, this continues to be respected down the chain.
The callback script, callback.php
, first loads the database functionality from lib.php
and ensures that it can connect. If connection fails for some reason, it returns an HTTP error 500 (Internal Server Error), which tells index.php
to display an error to the user, as previously described.
If callback.php
is connected to the database successfully, it then sanitizes the input variables, latitude
and longitude
. It's important to make sure both are cast to floating point variables, to minimize the risk of SQL injection attacks. The script also retrieves the current UNIX epoch timestamp (represented as the number of seconds since 00:00 on January 1, 1970).
The script makes it possible to store location information for an unlimited number of users. However, because authentication and user handling are not within the scope for this book, we've hardcoded the user's unique ID to 1
. If you had a separate MySQL user table, for example, you would set this value to the ID of the currently logged-in user. This ID would be saved in the current browser session at the point of login. callback.php
would use the version saved in the session rather than sent to it explicitly via a GET
or POST
variable, to prevent third parties from maliciously saving location information to a user's account.
Finally, callback.php
attempts to save this data to the MySQL table we created in the previous section, using a standard MySQL insert call:
$query = "insert into points set 'longitude' = {$longitude}, 'latitude' = {$latitude}, 'user_id' = {$user}, 'time' = {$time}";
Once again, if an error occurs, the script returns an HTTP 500 error so that the JavaScript on index.php
can let the user know in a friendly way.
Otherwise, we can reasonably assume that the data was saved in our MySQL table. Because we saved it with timestamp information, and because we are also saving the user's unique ID in the same table row, we will be able to easily retrieve any individual user's locations in chronological order later on.
PHP's default HTTP response code is 200: OK
. This tells the jQuery call in index.php
that the positioning data was saved without any problems. In turn, index.php
lets the user know that his/her location was saved.