My Trip Emission

PHP Project: My Trip Emission

After my one year trip to Australia I’m back to Brazil. It’s time to get this blog back to life. So, I’m going to start writing about a PHP application I did a few years ago called My Trip Emission. It is an application that give you the emission of CO2 of your trip, all you need is to define the model of your car and your trip (from and to). It is developed in pure PHP with jQuery and the Google Maps API.

Introduction

My Trip Emission is (or was – it is not a domain anymore) an application that I developed that allows the user to detect the emission of CO2 base on a Google Maps trip and the selection of a car. It was developed in pure PHP and uses the Google Maps API. I’ll go through the directory structure and then explain the most important parts. Please note that this is an old project that haven’t been updated in a while, which means some parts of the code are using old versions of PHP/jQuery/Google Maps API.

MyTripEmission

You can download the complete project here.

Directory Structure

This is a very simple project, with a few files, so I’m going to show most of them.

  • css
    • bkp_style.css
    • style.css
    • styleIE.css
  • images
  • include
    • config.php
  • js
    • jquery_cookies.js
    • scripts.js
  • about.php
  • contact.php
  • contact_sent.php
  • favicon.ico
  • get_emission.php
  • get_model.php
  • get_version.php
  • help.php
  • index.php
  • mytripemission.sql
  • robots.txt
  • sitemap.xml

CSS Folder

This folder includes three files. bkp_style.css and style.css are the same file, but style.css is minified. styleIE.css has IE specific styling. I’m not going to explain the details of the styling here. If you want to know more about it, let me know in the comments.

Images Folder

Just images…

Include Folder

The include folder contains the config.php file, which handles the database connection. You need to update this file with your database info. Furthermore, this file defines two functions cleanInput($input) and sanitize($input) which are responsible for cleaning the GET variable and ensuring no bad inputs will go to the database.

// Database information
$db_user = 'root';
$db_pass = '';
$db_host = 'localhost';
$db_name = 'mytripemission';

// Database connection
$dbc = mysql_connect($db_host, $db_user, $db_pass) OR die('It was not possible to connect to MySQL: ' . mysql_error());
mysql_select_db ($db_name) OR die('It was not possible to select the database: ' . mysql_error());

define('BASE_URL', 'http://localhost/mytripemission/');

// Cleaning function
function cleanInput($input) {

    $search = array(
        '@]*?>.*?@si',   // Strip out javascript
        '@]*?>@si',            // Strip out HTML tags
        '@]*?>.*?@siU',    // Strip style tags properly
        '@@'         // Strip multi-line comments
    );

    $output = preg_replace($search, '', $input);
    return $output;
}
// Sanitizing function
function sanitize($input) {
    if (is_array($input)) {
        foreach($input as $var=>$val) {
            $output[$var] = sanitize($val);
        }
    }
    else {
        if (get_magic_quotes_gpc()) {
            $input = stripslashes($input);
        }
        $input  = cleanInput($input);
        $output = mysql_real_escape_string($input);
    }
    return $output;
}

JS Folder

Contains the JavaScript files. jquery_cookies.js is a cookie plugin for jQuery developed by Klaus Hartl. It is used on the other file in this folder, scripts.js. This file handles different things and is important for the overall functionality, so lets go through it.
The first part is based on the Google Maps API examples and handles the map generation and events.

var map;
var gdir;

function initialize() {
  if (GBrowserIsCompatible()) {
    map = new GMap2(document.getElementById("map_canvas"));
    map.setCenter(new GLatLng(geoip_latitude(), geoip_longitude()), 10);
    map.addControl(new GLargeMapControl());
    map.addControl(new GMapTypeControl());
    map.enableScrollWheelZoom();
    gdir = new GDirections(map, document.getElementById("directions"));
    GEvent.addListener(gdir, "load", onGDirectionsLoad);
    GEvent.addListener(gdir, "error", handleErrors);
  }
}

function setDirections(fromAddress, toAddress, locale) {
  gdir.load("from: " + fromAddress + " to: " + toAddress,
            { "locale": locale });
}

function handleErrors(){
       if (gdir.getStatus().code == G_GEO_UNKNOWN_ADDRESS)
         alert("No corresponding geographic location could be found for one of the specified addresses. This may be due to the fact that the address is relatively new, or it may be incorrect.\nError code: " + gdir.getStatus().code);

       else if (gdir.getStatus().code == G_GEO_SERVER_ERROR)
         alert("A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.\n Error code: " + gdir.getStatus().code);

       else if (gdir.getStatus().code == G_GEO_MISSING_QUERY)
         alert("The HTTP q parameter was either missing or had no value. For geocoder requests, this means that an empty address was specified as input. For directions requests, this means that no query was specified in the input.\n Error code: " + gdir.getStatus().code);

       else if (gdir.getStatus().code == G_GEO_BAD_KEY)
         alert("The given key is either invalid or does not match the domain for which it was given. \n Error code: " + gdir.getStatus().code);

       else if (gdir.getStatus().code == G_GEO_BAD_REQUEST)
         alert("A directions request could not be successfully parsed.\n Error code: " + gdir.getStatus().code);

       else alert("An unknown error occurred.");

}

function onGDirectionsLoad(){
    distance = gdir.getDistance().meters;

    // Get value for emission
    $.ajax({
        type: 'GET',
        url: 'get_emission.php',
        data: 'manufact=' + $('#manufact').val() + '&model=' + $('#model').val() +
              '&version=' + $('#version').val() + '&meters=' + distance +
              '&fuel_uom=' + $.cookie('fuel_uom'),
        success: function(result){
            $('#emissions').html(result);
        }
    });
}

The function setDirections(fromAddress, toAddress, locale) loads the new trip definition. The other function which is interesting here is onGDirectionsLoad() because it calls the calculation of the emission using AJAX and displays it in the sidebar. Next we will continue in the same file.

/*
 * jQuery
 */
function setError(field) {
    if($(field).val() == 0) {
        $(field).animate({
        backgroundColor: 'red'
            }, 1000, function() {
            $(field).animate({
            backgroundColor: '#FFD1D1'
            }, 1000);
        });

        return false;
    }
    else {
        $(field).css('background-color','white');
        return true;
    }
}

function executeForm() {
    var ok = true;

    ok = setError('#manufact');
    ok = setError('#model');
    ok = setError('#version');
    ok = setError('#fromAddress');
    ok = setError('#toAddress');

    if(!ok) return false;

    // Set cookies
    $.cookie('manufacturer',$('#manufact').val(), { expires: 30 });
    $.cookie('model',$('#model').val(), { expires: 30 });
    $.cookie('version',$('#version').val(), { expires: 30 });
    $.cookie('fromAddress',$('#fromAddress').val(), { expires: 30 });
    $.cookie('toAddress',$('#toAddress').val(), { expires: 30 });

    // Define directions according to language
    if($.cookie('language')){
        setDirections($('#fromAddress').val(), $('#toAddress').val(), $.cookie('language'));
    } else {
        setDirections($('#fromAddress').val(), $('#toAddress').val(), 'en_US');
    }

    return false;
}

function changeUom() {
    if ($.cookie('fuel_uom') == 'km/l') {
        $.cookie('fuel_uom','mpg_US', { expires: 180 });
    } else if ($.cookie('fuel_uom') == 'mpg_US') {
        $.cookie('fuel_uom','mpg_UK', { expires: 180 });
    } else if ($.cookie('fuel_uom') == 'mpg_UK') {
        $.cookie('fuel_uom','km/l', { expires: 180 });
    }

    $.ajax({
        type: 'GET',
        url: 'get_emission.php',
        data: 'manufact=' + $('#manufact').val() + '&model=' + $('#model').val() +
            '&version=' + $('#version').val() + '&meters=' + distance +
            '&fuel_uom=' + $.cookie('fuel_uom'),
        success: function(result){
            $('#emissions').html(result);
        }
    });
}

This section has other two important functions. The function executeForm() will check for errors in the form, define cookies (so the user won’t need to define the information again next time he visits the site) and execute the setDirections(), which will run the Google Maps API.
The second function is changeUom(), which updates the unity of measure cookie and does an AJAX call to update the emissions HTML in the sidebar. The next part of the code is a jQuery event handler.

// Change window and DIV's width and height on resize
$(window).resize(
    function(){
        $('#container').css('height',($(window).height()-26)+'px');
        if($(window).width() > 734){
            $('#container').css('width',($(window).width()-3)+'px');
        } else {
            $('#container').css('width','730px');
        }
        $('#sidebar').css('height',($('#container').height()-$('#header').height())+'px');
        $('#directions').css('height',($('#sidebar').height()-150)+'px');
        $('#map_canvas').css('height',($('#container').height()-$('#header').height())+'px');
        $('#map_canvas').css('width',($('#container').width()-310)+'px');
    }
);

It resizes the map container, in order to make it fit the screen area properly. Finally we arrive at the document ready event, which initializes some things and adds events.

$(document).ready(function(){
    // Hide scrollbars
    $("body").css("overflow", "hidden");

    // Initialize window and DIV's width and height
    $('#container').css('height',($(window).height()-26)+'px');
    if($(window).width() > 734){
        $('#container').css('width',($(window).width()-3)+'px');
    } else {
        $('#container').css('width','730px');
    }
    $('#sidebar').css('height',($('#container').height()-$('#header').height())+'px');
    $('#directions').css('height',($('#sidebar').height()-150)+'px');
    $('#map_canvas').css('height',($('#container').height()-$('#header').height())+'px');
    $('#map_canvas').css('width',($('#container').width()-310)+'px');

    // Settings dialog
    $("#settings").dialog({
      bgiframe: true, autoOpen: false, height: 60, width: 400, modal: false, closeOnEscape: true
    });

    // Initialize settings from cookies
    if($.cookie('fuel_uom')) {
        $('#fuel_uom').val($.cookie('fuel_uom'));
    } else {
        $.cookie('fuel_uom', 'mpg_US', { expires: 180 });
    }

    if($.cookie('language')) {
        $('#language').val($.cookie('language'));
    } else {
        $.cookie('language', 'en_US', { expires: 180 });
    }

    // Initialize values - get cookies
    if($.cookie('manufacturer')) {
        $('#manufact').val($.cookie('manufacturer'));

        $.ajax({
            type: 'GET',
            url: 'get_model.php',
            data: 'manufact=' + $('#manufact').val(),
            success: function(result){
                $('#model').children().remove();
                $('#model').append('- Select -')
                           .append(result)
                           .removeAttr('disabled');

                if($.cookie('model')) {
                    $('#model').val($.cookie('model'));

                    $.ajax({
                        type: 'GET',
                        url: 'get_version.php',
                        data: 'manufact=' + $('#manufact').val() + '&model=' + $('#model').val(),
                        success: function(result){
                            $('#version').children().remove();
                            $('#version').append('- Select -')
                                         .append(result)
                                         .removeAttr('disabled');

                            if($.cookie('version')) {
                                $('#version').val($.cookie('version'));
                            }
                        }
                    });
                }
            }
        });
    }

    // Vehicle selection ajax
    $('#manufact').change(
        function() {
            $.ajax({
                type: 'GET',
                url: 'get_model.php',
                data: 'manufact=' + $(this).val(),
                success: function(result){
                    $('#model').children().remove();
                    $('#model').append('- Select -')
                               .append(result)
                               .removeAttr('disabled');
                    $('#version').children().remove();
                    $('#version').append('- Select -')
                                 .attr('disabled', 'disabled');
                }
            });
        });

    $('#model').change(
        function() {
            $.ajax({
                type: 'GET',
                url: 'get_version.php',
                data: 'manufact=' + $('#manufact').val() + '&model=' + $(this).val(),
                success: function(result){
                    $('#version').children().remove();
                    $('#version').append('- Select -')
                                 .append(result)
                                 .removeAttr('disabled');
                }
            });
        });

    // Check my trip button
    $('#check_btn').click(
        function() {
            return executeForm();
        });

    // Fuel unit of measure Cookie
    $('#fuel_uom').change(
        function() {
            $.cookie('fuel_uom',$('#fuel_uom').val(), { expires: 180 });
        });

    // Language Cookie
    $('#language').change(
        function() {
            $.cookie('language',$('#language').val(), { expires: 180 });
        });
});

First I initialize different things:

  • Hide body scroll bars;
  • Initialize map size;
  • Define settings dialog;
  • Initialize values according to cookies.

The cookies manufacturer, model and version are initialized one after the other, because they depend on each other. So, first we get the manufacturer. Than we check the database and the cookie for the model. Finally we check the database again and the cookie for the version.
Next, we have the Vehicle selection AJAX calls. When we change one of the dropdowns, we check the database to fill the options of the other dropdowns.
Lastly, we have events for the “Check my Trip” button and the settings dialog screen, with the unit of measure and language changes. Now let’s go to the root folder and talk about the PHP itself.

Root Folder

The root folder contains simple files like about.php (basic information about the site), contact.php and contact_sent.php (the contact form and it’s answer), favicon.ico (site icon), help.php (information on how to use the website), mytripemission.sql (database creation script), robots.txt (defines where the search engine can go) and the sitemap.xml. I’m only go through the other files, which are more interesting.

index.php

The index file has the HTML for the main page. Besides the form, which handles the search, it also defines the main DIVs used by the JavaScript. In this file I also use the BASE_URL constant which is defined in the config.php file. You need to remember to update this if you want the application to run.

get_model.php and get_version.php

Both files are called via AJAX and are simple database queries, getting information to fill the dropdowns in the index.php file.
get_model.php

require_once 'include/config.php';

$_GET = sanitize($_GET);

$sql = 'SELECT id, name FROM model WHERE manufacturer='.$_GET['manufact'];
$res = mysql_query($sql);
while($row = mysql_fetch_array($res)) {
    echo ''.$row['name'].'';
}

get_version.php

require_once 'include/config.php';

$_GET = sanitize($_GET);

$sql = 'SELECT id, name, trans_type FROM version
        WHERE manufacturer='.$_GET['manufact'].'
          AND model='.$_GET['model'];
$res = mysql_query($sql);
while($row = mysql_fetch_array($res)) {
    if($row['trans_type'] == 'M') {
        $transmission_type = 'Manual';
    } elseif($row['trans_type'] == 'A') {
        $transmission_type = 'Automatic';
    } else {
        $transmission_type = '';
    }

    echo ''.$row['name'].' '. $transmission_type.'';
}

get_emission.php

This is the file that really does the trick. This file I’m going to explain step by step.

require_once 'include/config.php';

// Get emisison logic
$_GET = sanitize($_GET);

$fuel_uom = $_GET['fuel_uom'];
$distance = $_GET['meters'];

$vehicle = explode('-', $_GET['version']);
$version = $vehicle[0];
$trans_type = $vehicle[1];

First, we require the config file, which does the database connection and defines the sanitize() function. Then we start the emission logic by sanitizing the $_GET variable and assigning the fuel unit of measure, the distance and the vehicle, which includes the version and transmission type.

// Get values for calulating vehicle green stars
$sql_stars = 'SELECT MAX( emission_co2 ) AS worse, MIN( emission_co2 ) AS best FROM version';
$res_stars = mysql_query($sql_stars) or die('Database error!');
$row_stars = mysql_fetch_array($res_stars);
$worse = $row_stars['worse'];
$best = $row_stars['best'];
$star_rate = ($worse - $best)/5; // Rate for each star

The code continues defining what will be a 5 star emission. For this purpose we fetch the MAX and MIN emissions from the database and define a $star_rate based on these values.

$sql = 'SELECT kml_urban, kml_extra, kml_combined, emission_co2
        FROM version
        WHERE manufacturer='.$_GET['manufact'].'
          AND model='.$_GET['model'].'
          AND id='.$version.'
          AND trans_type=\''.$trans_type.'\'';

$res = mysql_query($sql) or die($version.'-'.$trans_type);

$change_link = '
';

if ($row = mysql_fetch_array($res)) {
// This code is in the next code block
}

Next, we get the information for the selected vehicle. As we are going to return an HTML code, we also define the $change_link, responsible for changing the unit of measure. The next chunk of code is inside the last IF statement.

    $litres = round(($distance/1000) / $row['kml_combined'], 2);
    $co2 = round((($distance/1000) * $row['emission_co2'])/1000, 2);
    $stars_num = round(($worse-$row['emission_co2'])/$star_rate, 0);
    if ($stars_num == 1) {
        $stars_txt = ' Star';
    } else {
        $stars_txt = ' Stars';
    }

    // Change unit of measure

    if ($fuel_uom == 'km/l') {
        echo '</pre>
<h1>Consumption</h1>
<pre> '.$litres.' litres ('.$row['kml_combined'].' km/l)';
        echo $change_link;
        echo '</pre>
<h1>CO2 Emission</h1>
<pre> '.$co2.' kg ('.$row['emission_co2'].' g/km)';
    } else if ($fuel_uom == 'mpg_US') {
        $gallons = round($litres * 0.264, 2);
        $mpg = round($row['kml_extra'] / 0.425, 2);
        echo '</pre>
<h1>Consumption</h1>
<pre> '.$gallons.' gallons ('.$mpg.' MPG (US))';
        echo $change_link;
        echo '</pre>
<h1>CO2 Emission</h1>
<pre> '.$co2.' kg ('.$row['emission_co2'].' g/km)';
    } else if ($fuel_uom == 'mpg_UK') {
        $gallons = round($litres * 0.220, 2);
        $mpg = round($row['kml_extra'] / 0.354, 2);
        echo '</pre>
<h1>Consumption</h1>
<pre> '.$gallons.' gallons ('.$mpg.' MPG (Imp))';
        echo $change_link;
        echo '</pre>
<h1>CO2 Emission</h1>
<pre> '.$co2.' kg ('.$row['emission_co2'].' g/km)';
    }

    echo '<span class="fltrt">';
 for ($i = 5; $i >= 1; $i--) {
 if ($i echo '<img title="'.$stars_num.$stars_txt.'" src="'.BASE_URL.'images/star.png" alt="'.$stars_num.$stars_txt.'" />';
 } else {
 echo '<img title="'.$stars_num.$stars_txt.'" src="'.BASE_URL.'images/star_black.png" alt="'.$stars_num.$stars_txt.'" />';
 }

 }
 echo '</span>';

Finally, in this part of the code we start by calculating the consumption ($litres) and emission ($co2). Note that the distance is divided by 1000, to get the km. We also calculate the number of stars for the current emission, this way we can give a rating to the vehicle emission.
Continuing, after the “Change unit of measure” comment, we check which unit of measure is selected and create the HTML accordingly. To conclude the HTML, we also echo a SPAN tag, containing the star rating.

Conclusion

So, this what I had to show in this reactivation of my blog. This small project was online until last year, and I thought I should make it available in case anyone is interested. When I did it I had the intention to add more data and make it really usable, but in the end I changed my priorities… Anyway, it is available here totally free and open, but if you use it, don’t forget to reference this blog post.
Next week I’m going to (try to) write about a task board application that I developed using CodeIgniter. It is already available at GitHub, in case you’re interested.

You can download the complete project here.

References:
Google Maps API

VCACarfueldata – now here