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.
[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:
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.
#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.
require_once('includes/application.php');
$calls = new calls();
<script src="http://maps.google.com/maps?
file=api&v=2&sensor=true&key=<?=GOOGLE_API_KEY?>" type="text/javascript"></script>
<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>
$calls->GetTripIDs();
<span id="output"></span>
<div id="accel"></div>
<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.
class global_class {
function utctotime($utc,$format)
{
setlocale(LC_TIME, 'en_US');
$da = explode("T",$utc);
$un = strtotime($da[0]);
$time = date($format,$un);
return $time;
}
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()
.
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()
.
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.
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];
map.clearOverlays();
document.getElementById('accel').innerHTML = '';
map.setCenter(new GLatLng(lat, lng), 16);
var point = new GLatLng(lat, lng);
var marker = new GMarker(point);
var html = getFile('accel',id,null);
document.getElementById('accel').innerHTML = html;
GEvent.addListener(marker, "click", function() {
marker.openInfoWindowHtml(html);
});
map.addOverlay(marker);
var linearray = polyline(id);
if(linearray!=0)
map.addOverlay( new GPolyline
( linearray, '#66FF00', 2, 1 ) );
document.getElementById('output').innerHTML = '';
}
else
{
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