Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PHP

The World's Fastest Server Tracking (Application Challenge Submission)

5.00/5 (3 votes)
21 Mar 2009CPOL5 min read 22.5K   286  
Using PHP-SOAP to access the world's fastest server web service and plot each trip using the Google maps API
Image 1

Introduction

This web application is an entry in the joint challenge put out by The Code Project and Hostmysite.com. As an avid Motorcycle enthusiast and web application developer, this contest is right up my alley. The application I have developed is also somewhat inspired by a Baja race tracking application I wrote in the summer of 2008. Throughout the article, terms like OOP, SOAP, and SJAX will be explained as they relate to the application. For more general information regarding these terms, see PHP.NET, Google.com, and other articles found here on The Code Project.

Background

For anyone reading and asking themselves, what is this challenge you speak of? More information can be found here: World's Fastest Server Challenge.
Still confused, checkout: The World's Fastest Server.

This application uses the World's Fastest Server web service, the Google Maps API, and PHP with SOAP to plot its course, latest position, and accell data on a Google Map.

Using the Code

PHP has no rules. I find myself saying that very sentence time and time again in conversations comparing programming languages and best practices. It is both a blessing and curse to use a language as "loose" as PHP. Hopefully when you are using this code, you will find my method of OOP highly reusable for any application regardless of what you are attempting to do.

To run this application, your PHP server must have SOAP available. http://us.php.net/manual/en/book.soap.php

The basic directory structure of this application:

  • index.php (UI)
  • js/
    • ajax.js (my ajax library)
    • map.js (loads the initial map)
    • scripts.js (accepts calls from the UI)
  • includes/
    • config.ini (stores environmental info)
    • application.php (first file processed on PHP requests)
    • classes/
      • global_class.php (members of this class may be used anywhere)
      • calls.php (members of this class are specific to calling the web service)
    • output/ (these files are accepted and respond to HTTP requests with plain text)
      • accel.php
      • latlng.php
      • polyline.php

Using a PHP configuration file format makes setting up your application easy for anyone. This is the only file that requires editing for the app to run in another environment.

plain
//config.ini
[service]
wsdl="http://www.theworldsfastestserver.com/webservice/bikedata.cfc?wsdl"

[environment]
;a windows path would generally start with c:\
;a linux path would generally start with /var/www/html
doc_root="/path/from/server/root"
web_root="http://mydomain.tld/path/to/app"

[google]
;get your key here http://code.google.com/apis/maps/signup.html
apikey="your own google api key goes here"

Points of Interest

If one file was more important than the others, it would have to be application.php. Within it, you will find the use of __autoload(), what could very well be the most helpful thing I have learned for using OOP (object oriented programming). Using __autoload() prevents the need to keep track of what can become long lists of include files. When creating an instance of the class that is not found before an error is generated __autoload() will run. All that is required is the class names match the file names. You can see this in action within any of the files in the output directory.

JavaScript
//application.php

#set display_errors on for testing off for production
ini_set('display_error','On');
error_reporting(E_ALL ^ E_STRICT);

#parse custom ini file
$config = parse_ini_file('config.ini');

#define constants from ini file
define(WSDL,$config['wsdl']);
define(WEB_ROOT,$config['web_root']);
define(DOC_ROOT,$config['doc_root']);
define(GOOGLE_API_KEY,$config['apikey']);

#auto load all classes
require_once('classes/global_class.php');
$global_class = new global_class();
function __autoload($class_name) {
    require_once (DOC_ROOT.'/includes/classes/'.$class_name.'.php');
}

index.php is the user interface for the application. This is the only page end users see directly. You will see the index.php file contains mostly HTML markup. I have not styled it in an effort to keep things focused on the actual application and not how pretty it is. Of course in commercial use, a CSS file could be added very easily and liven things up nicely.

JavaScript
//index.php
//I am only mentioning the important part of the index.php file here. 
//The complete code is in the zip file available for download.

//always process the application file first
require_once('includes/application.php');

//create and instance of the calls class.
//Because of _autoload() I do not have to include the class file 
//but calls.php needs to exist in my includes/classes folder
$calls = new calls();

//Don't forget you need a Google maps API key but we stored that in our config.ini file
<script src="http://maps.google.com/maps?
    file=api&v=2&sensor=true&key=<?=GOOGLE_API_KEY?>" type="text/javascript"></script>

//load the javascript files
<script type="text/javascript" src="http://www.codeproject.com/fast/js/ajax.js">
</script>
<script type="text/javascript" 
	src="http://www.codeproject.com/fast/js/scripts.js"></script>
<script type="text/javascript" src="http://www.codeproject.com/fast/js/map.js"></script>

//GetTripIDs is a member of the calls class. 
//It will use the web server to create a drop down box of trips
$calls->GetTripIDs();

//javascript needs this dom objects I am using output to display errors
<span id="output"></span>

//accelerometer data will appear here
<div id="accel"></div>

//the map will appear here
<div id="latlng"></div>

global_class.php is used for methods that may be called from anywhere. For this application, I have needed two functions here.

JavaScript
//global_class.php

class global_class {

	//utctotime converts the utc formatted timestamp from the 
	//bike's web service into a more human readable data
	function utctotime($utc,$format)
		{
			setlocale(LC_TIME, 'en_US');
			$da = explode("T",$utc);
			$un = strtotime($da[0]);
			$time = date($format,$un);
			return $time;
		}

	//client creates an instance of a SoapClient object
	function client($wsdl)
		{
			$client = new SoapClient($wsdl);
			return $client;
		}
}

calls.php contains the function we saw used above GetTripIDs(). GetTripIDs() will first create the instance of a SoapClient object then call the web service function GetTripIDs to return all trips. Now you can see where the select box comes from. You can also see I am using an onchange event handler to trigger a JavaScript function waypoints().

JavaScript
//calls.php

function GetTripIDs()
	{
		$client = $this->client(WSDL);
		$return = $client->GetTripIDs();
		if(is_soap_fault($return))
			return trigger_error("SOAP Fault: 
				(faultcode: {$result->faultcode}, 
				faultstring: {$result->faultstring})", 
				E_USER_ERROR);
		else
			{
				$options = '<option value="">
						Select a trip</option>';
				foreach($return->data as $key => $val)
					{
					    $options .=  
					    '<option value="'.$val[0].'">'.
						$val[2].'</option>';
					}
				$form = '<select onchange="waypoints(\'latlng\',
					this.value)" name="tripId">
						'.$options.'
						 </select>';
				return $form;
			}
	}

This is where things start to get more interesting. I use ajax.js as a library for ajax. In this application, you will see the getFile() JavaScript function used multiple times. You also may have scratched your head a little in the beginning when I said SJAX. That is right it is not always useful to use asynchronous calls. Sometimes like in our case, the call needs to be synchronous. A common mistake is to think of asynchronous as only the separation from the initial page load and subsequent calls. While asynchronous JavaScript does mean that, it also means a separate thread is spun off if invoked during a list of other JavaScript operations. For example if I invoke something asynchronous during a for loop, I can no longer rely on anything returning from that call as a condition of my loop. Instead the two operations will be running at the same time when I really need the loop to wait for my call to return with information.

Within ajax.js, you will see getFile() and get().

JavaScript
//getFile() is synchronous because of the line open("POST",url,false); notice the false.
//get() is asynchronous because of the line open("POST",url,true); notice the true.

getFile() returns the text from the HTTP request in a synchronous manner while get() outputs the text to a dom object asynchronously. I have to use the synchronous option because I cannot have my waypoint() function continuing before the HTTP request finishes by returning information. In the first call to getFile, the latlng.php file will be processed. It will return the last latitude and longitude decimal value obtained from the GetWaypointsForTrip() web service call for the trip selected. When waypoints() gets to the //get accel data line, I use getFile() again but this time to run accel.php. The response from this call will be a CSV string of all the latitude and longitude decimal values. The CVS data is then parsed and turned into an array of Glatlng() objects per the Google maps API to create the polyline.

JavaScript
//scripts.js

//this is the waypoints function called from the onchange event of our select box
//from the select box the first parameter file is defined as latlng and the second 
//is this.value (the value of the field after it was changed).  
//latlng will be used by getFile as latlng.php found in the output directory.

function waypoints(file,id)
	{
		var latlng = getFile(file,id,null);
		if(latlng != 0 && latlng.match(",") != null)
			{
				var ll = latlng.split(",");
				var lat = ll[0];
				var lng = ll[1];

				//clear map overlays
				map.clearOverlays();
				document.getElementById('accel').innerHTML = '';

                //set map center
				map.setCenter(new GLatLng(lat, lng), 16);

				var point = new GLatLng(lat, lng);
				var marker = new GMarker(point);

				//get accel data
				var html = getFile('accel',id,null);

				//output accel data
				document.getElementById('accel').innerHTML = html;

				GEvent.addListener(marker, "click", function() {
				marker.openInfoWindowHtml(html);
				});

				map.addOverlay(marker);

                //add polyline to map
				var linearray = polyline(id);
				if(linearray!=0)
				map.addOverlay( new GPolyline
					( linearray, '#66FF00', 2, 1 ) );

				//clear any previous data from the 
				//output dom object.
				document.getElementById('output').innerHTML = '';
			}
		else
			{
				//write any errors to the output dom object
                var message = 'No waypoints for this trip';
				document.getElementById('output').innerHTML = 
								message;
			}
	}

I hope that you download this application and modify it to your hearts content. By doing so, you will re enforce what you have read within this article and certainly learn more than what can be accomplished by reading alone. You are free to use any of this code however you like. Enjoy.

History

  • 20th March, 2009: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)