Table of Contents
Ukana (Part II)
The first part of this project can be found here:
Changelog:
- HTML5 Geolocation Service API used to obtain user’s location.
- Google Maps API used as mapping engine placeholder.
- Routing algorithm now implemented.
Geolocation Service (Javascript)
In order to automatically lock on the user’s position, HTML5’s geolocation service API is used. Since there is a small chance not all users have an HTML5 compliant browser for the service to work, an if-else verification method for compatibility purposes must be implemented.
The navigator.geolocation object is invoked to access the service. Its getCurrentPosition method will provide the program with a latitude and longitude at a given degree of accuracy depending on the available resources. The service will use increasingly accurate methods such as IP-based location, followed by WiFi location and GPS receiver if available. The coords.latitude and cords.longitude objects must then be sent to the mapping service being used – in this case, Google Maps using the LatLng object.
The following $_POST elements are received from a separate destination.php file where the user inputs the coordinates of their desired destination.
if(navigator.geolocation) { navigator.geolocation.getCurrentPosition(function(position) { var pos = new google.maps.LatLng(position.coords.latitude, position.coords.longitude); var sendPosLat = 0; var sendPosLng = 0; var sendDestLat = <?php if ($_POST['destLat']) {echo $_POST['destLat'];} else {echo "0";} ?>; var sendDestLng = <?php if ($_POST['destLng']) {echo $_POST['destLng'];} else {echo "0";} ?>;
If it is the first request to the routeSearch.php page, the script will replace the URL with the corresponding geolocation and destination elements to be called later through php $_GET elements.
if(document.URL == "http://localhost/routeSearch.php") { sendPosLat = pos.lat(); sendPosLng = pos.lng(); window.location.href = "routeSearch.php?lat=" + sendPosLat + "&lng=" + sendPosLng + "&destLat=" + sendDestLat + "&destLng=" + sendDestLng; }
var infowindow = new google.maps.InfoWindow({ map: map, position: pos, content: 'Location found using HTML5.' });
If the browser encounters content errors or does not support geolocation through HTML5 it will prompt the user with a printed error.
map.setCenter(pos); }, function() { handleNoGeolocation(true); }); } else { // Browser doesn't support Geolocation handleNoGeolocation(false); } }
function handleNoGeolocation(errorFlag) { if (errorFlag) { var content = 'Error: The Geolocation service failed.'; } else { var content = 'Error: Your browser doesn\'t support geolocation.'; }
Google Maps API
The API must be initialized using the key obtained through the desired service. In this case, the Google Maps API is used as placeholder.
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js? key=<OBTAINED KEY>&sensor=false"> </script>
The directionsService is optional at this point but the map variable must always be initialized for its use in the script. The options must be specified according to the API documentation such as zoom level and center position.
<script type="text/javascript"> <?php echo "var directionsDisplay;"; echo "var directionsService;"; echo "var map;"; echo "function initialize() { var mapOptions = {"; $connect = mysqli_connect("servername", "user", "password", "database"); $stopName = $_POST["stop"]; $convertQuery = mysqli_query($connect, "SELECT idstop FROM buses.stop WHERE name = \"$stopName\"")#toma la información recibida y la usa para extraer los identificadores de parada or die("Error " . mysqli_error($connect)); $idStop = mysqli_fetch_row($convertQuery);#Toma la información extraída y la guarda en un arreglo $stopQuery = mysqli_query($connect, "SELECT lat, lng FROM buses.stop WHERE idstop = \"$idStop[0]\"") or die("Error " . mysqli_error($connect));#Toma de la base de datos la información de las paradas por medio del identificadores
$coord = mysqli_fetch_row($stopQuery); echo "center: new google.maps.LatLng($coord[0], $coord[1]), zoom: 18, mapTypeId: google.maps.MapTypeId.ROADMAP }; map = new google.maps.Map(document.getElementById(\"map-canvas\"),mapOptions);"; $i = 0; mysqli_data_seek($stopQuery, 0); while($coordrow = mysqli_fetch_row($stopQuery)){ echo "\n";
To add the markers, a marker variable is initialized. Each stop is used recursively to create the corresponding amount of markers in their respective positions obtained through a query to the server.
echo "var marker_$i = new google.maps.Marker({position: new google.maps.LatLng($coordrow[0],$coordrow[1]), map: map});"; echo "\n"; $i++; } echo "} google.maps.event.addDomListener(window, 'load', initialize);"; echo "\n \n"; ?>
A CSS style must be set for the map to be shown in a an html div element.
</script> <style type="text/css"> html { height: 100% } body { height: 100%; margin: 0; padding: 0 } #map-canvas { height: 100% } </style> <div id="map-canvas"></div>
Routing Algorithm
The routing algorithm uses the variables previously stored in the URL and calls them through a $_GET method.
$posLat = $_GET['lat']; $posLng = $_GET['lng']; $destLat = $_GET['destLat']; $destLng = $_GET['destLng'];
Using these variables, a formula to convert degrees to metric distances is used for further use in the script.
$m1 = 11132.954 - (559.882 * cos(2*($posLat))) + (1.175*(cos(4 *($posLat)))); $m2 = ((PI()/180)*6367449*(-cos($posLat))); $n1 = 11132.954 - (559.882 * cos(2*($destLat))) + (1.175*(cos(4 *($destLat)))); $n2 = ((PI()/180)*6367449*(-cos($destLat)));
Taking into account the previous formulas and variables, the server is queried for nearby bus stops relative to the users location. The following query looks for stops in a 1500m radius, however this was chosen arbitrarily and may be modified at will.
$leStopQuery = mysqli_query($connect, "SELECT name, idstop FROM buses.stop WHERE (($posLat - (1500/$m1)) <= lat and ($posLat + (1500/$m1)) >= lat) and (($posLng - (1500/$m2)) <= lng and ($posLng + (1500/$m2)) >= lng)") or die("Error " . mysqli_error($connect)); $leDestQuery = mysqli_query($connect, "SELECT name, idstop FROM buses.stop WHERE (($destLat - (1500/$n1)) <= lat and ($destLat + (1500/$n1)) >= lat) and (($destLng - (1500/$n2)) <= lng and ($destLng + (1500/$n2)) >= lng)") or die("Error " . mysqli_error($connect));
In order to find the bearing of the destination relative to the current location or stop, the following function was used. The function receives the formulas and variables and converts the coordinates to a bearing using trigonometric functions.
function stopDir($connect, $m1, $m2, $posLat, $destLat, $posLng, $destLng) { $dir = atan(($m1*($destLat - $posLat))/($m2*($destLng - $posLng))); if($posLng>$destLng) { $dir=pi()+$dir; } else{ if($dir<0){ $dir=2*pi()+$dir; } } echo "direction = $dir"; $a1 = ((1500 * (sin($dir - (pi()/4)))) / $m1) + $posLat; $a2 = ((1500 * (sin($dir + (pi()/4)))) / $m1) + $posLat; $b1 = ((1500 * (cos($dir - (pi()/4)))) / $m2) + $posLng; $b2 = ((1500 * (cos($dir + (pi()/4)))) / $m2) + $posLng;
Since the mapping coordinate system uses a quadrant-based location, the polar coordinate system must be transformed into a “search quadrant”.
if ($destLat > $posLat) { $lowLat = $posLat; } else { $lowLat = $a1; } if ($destLng > $posLng) { $lowLng = $posLng; } else { $lowLng = $b1; }
Once the quadrant is set, a query to obtain the stops contained within is executed.
$stopsQuery = mysqli_query($connect, "SELECT idstop, lat, lng FROM buses.stop WHERE ((($lowLat < lat) and ($a2 > lat)) and (($lowLng < lng) and ($b2 > lng)))") or die("Error " . mysqli_error($connect)); $distanceArray = array(); $idArray = array(); $i = 0; $disMenor = 100000000000000000; while($aStops = mysqli_fetch_row($stopsQuery)) {
The stop closest to the users location (or stop once the method is called into a stack) is chosen based on the distance obtained through a basic modulus function and ordered using an array bubble.
$distanceArray[$i] = $distancia; $idArray[$i] = $aStops[0]; if($disMenor>$distancia) { $disMenor = $distancia; } $i++; } $j = 0; foreach($distanceArray as $leDis) { if($leDis == $disMenor) { break; } $j++; } $leStop = $idArray[$j]; return($leStop); }
$lStop = stopDir($connect, $m1, $m2, $posLat, $destLat, $posLng, $destLng);
Finally, the routing function is used to find a chain of stops which connect the user's location to the desired destination. The server is queried for this list using the stopDir function.
function routing($laStop, $destLat, $destLng, $connect) { $final = false; $lStopQ = mysqli_query($connect, "SELECT lat, lng FROM buses.stop WHERE idstop = $laStop") or die("Error " . mysqli_error($connect)); $lStopData = mysqli_fetch_row($lStopQ); $m1 = 11132.954 - (559.882 * cos(2*($lStopData[0]))) + (1.175*(cos(4 *($lStopData[1])))); $m2 = ((PI()/180)*6367449*(-cos($lStopData[0]))); if ((($lStopData[0] - (1500/$m1)) <= $destLat and ($lStopData[0] + (1500/$m1)) >= $destLat) and (($lStopData[1] - (1500/$m2)) <= $destLng and ($lStopData[1] + (1500/$m2)) >= $destLng)) { $final = true; } if ($final == true) { echo"\n IDParada= \n $laStop \n "; } else { $dir = atan(($m1*($destLat - $lStopData[0]))/($m2*($destLng - $lStopData[1]))); $newStop = stopDir($connect, $m1, $m2, $lStopData[0], $destLat, $lStopData[1], $destLng); routing($newStop, $destLat, $destLng, $connect); } }
References
- MAPS API documentation: https://developers.google.com/maps/documentation/