FlightPHP, Geolocation, PHP

A WhatsMyIP GeoIP Map Site with FlightPHP

IP With A Here Map

FlightPHP is a micro-framework I’ve been tinkering around with lately. Written by Mike Cao, Flight is a small framework of eleven files that offers an elegant solution to routing and JSON API development in PHP. Installation is simple and Flights’ only requirement is having PHP 5.3 or higher on your host. FlightPHP is released under the MIT license so it’s legal to use in business endeavors without having to worry about major repercussions.
I feel it’s fair to mention that I also use two bits of functionality other than the framework from external sources in this example. Firstly, I use geoplugin.com and their PHP class for IP geolocation data. Yes, I could have either purchased or obtained a geolocation database from somewhere and pulled all the geographic information from that – I didn’t. The geoplugin site is free and easy to use. Secondly, I use a MIME class written by Robert Widdick for returning the proper headers when handling static file requests. Originally, I started with a limited associate array of file types and their associated header strings which I then used to create the response header for static file responses. I wanted to avoid the added headache of having to add header strings to an array every time I wanted to host a new filetype in the static folder. Robert Widdicks’ package works for nearly every different file type you would want to host and is released under the GNU GPL license.

IP With A Google Map

Why I chose to use the FlightPHP micro-framework

In any programming language the laconic solution is the most desirable. Thus, using any framework in your project becomes a balancing act of sorts. The bigger the framework, the larger the overhead. The more file reads a framework makes on the server, the slower the response. Too much server overhead lowers usage capacity. Using a framework that gives you desired functionality without excess becomes more and more important than in the past as the Internet grows to host single page web apps that demand more and more from the server.
The term “micro-framework” was alien to me until a few weeks ago. “How much functionality can a really small framework have and still be called a framework?” In my opinion, a framework in programming is a package of code that forms the underlying structure or interface for your application development that abstracts commonly needed functionality away to a simpler or more convenient interface. Or put another way; a bunch of code that makes programming an application a bit easier. Looking at the work “micro” prefixed to the definition made my expectations grow spartan.
I found that micro-frameworks are more task oriented and less one-size-fits-all from what would be considered a normal framework, so finding the right one for your particular project takes a bit of research. Faced with the myriad of micro-frameworks available on the Internet a programmer should choose one based on the common aspects needed in the particular project being planned. Most of the time you’ll find one that gives you almost all of the basic boilerplate functionality you desire. Depending on the feature needed, you could either fill in the gaps your own programming skills or find an external package. Either way, you’ll probably wind up with a smaller server footprint that loads faster and takes up less memory.
I came across Flight while looking for a smaller framework that I could use for an addon idea I came up with. If any, the only framework I would use for a new project is CodeIgniter. CodeIgniter is great and I really like using it, don’t misunderstand me; but its overhead seemed unnecessary for the task at hand. The addon idea I had would be creating mainly JSON traffic; entering data and retrieving it as needed. If it wasn’t for the data models, most of CodeIgniter would just be overhead. I desired a smaller framework for the backend layer that loads faster and has less overhead. I chose Flight because I like the way it ‘felt’ while coding with it. I eventually abandoned my idea, but I will most likely continue to use FlightPHP in future projects and experiments as its design naturally lends itself to single-page web application development.

IP With Wikimapia Map

Using FlightPHP

Using FlightPHP is as simple as using a global static class to call functionality. You extend Flight by mapping your global variables, functions and classes to that class. I really like the concept of the framework and its functionality here in this Flight object and plain PHP for the rest. Scripting an application using Flight just seems more intuitive and clean to me as a result.
In Flight you have no Controllers, Flight just handles the routing and response. All the routes and route handlers in your application are defined in the index.php file. Defining routing paths in your application is as simple as calling the static ‘route’ method of the global Flight object with the path string and a callback. Path strings can contain wildcards, regular expressions, named parameters, optional parameters, and HTTP verbs. Route paths defined like this in Flight are, in structure, a bit similar to the way you would code a node router in javascript. However, Flight is very flexible with regard to routing, you can map route responses to static functions and class methods.
There is also another routing feature that reminded me of node: if you return true from any route handler, execution is passed to the next matching route handler. That makes any route handler that returns true “middleware” of sorts, except that it is more explicit in functionality as it only executes for the route defined. Handling common request evaluation functionality, user agent parsing for instance, in a wildcard route defined before any of the other routes just seems more elegant and intuitive to me than calling a separate function at the beginning of every route handler.
Although FlightPHP uses the concept of views, there is no template engine included with the framework. By default, Flight uses straight PHP to render template output. However, you could use any template engine you desire by registering a class and overriding the ‘render’ function if the need arises. To render any PHP template you call the static ‘render’ function on the global Flight object passing it a path string and an associate array of data that will resolve as variables in the template during the rendering process. If you want to use partial views and a layout template that’s easy too. Just call ‘render’ on the partial views before the layout template render call with a third string representing the variable name that will hold the results to echo out during the layout render call. Really simple and elegant.
Request information is handled by Flight as well. If you need to access information about the request: request cookies, GET/POST variables etc.., they are contained in an object retrieved through the Flight::request method. Aggregate data properties of the returned request object are conveniently accessible using either object or array syntax.
Error handling, caching, redirects, JSON and JSONP responses are all handled through the FlightPHP object also. For more information on using FlightPHP its well documented here.
All in all, FlightPHP gives you a pleasant beginning to any project you may have in mind without the burden of too much overhead.

The Project Layout

The No Script Version

A Few Notes About My Code

Going over each step of the code step by step seems redundant to me. There are, however, a few notes I felt should be mentioned before gazing upon the code. Most programmers can figure out exactly what is going on in the code just by looking at the index.php file. As I stated previously, everything goes through the Flight object. There is no having to remember a bunch of different classes to extend in Flight.

About how the external resources are used

I created an includes folder to keep all of the external resources separate from the framework. As I mentioned previously, I used an external class for handling MIME types on static file requests. I didn’t register that class in the global object as not every request is a static file request. I did, however map the ‘getStaticFile’ method in the global object where I include it as necessary lazily in an effort to conserve resources. I also didn’t register the geoplugin class globally in the FlightPHP object as it is only used once per ip. All subsequent requests after the initial geoplugin service request for IP geolocation data are retrieved from the SQLite database – not the geoplugin web service.

About SQLite

I used SQLite and PDO instead of MySQL and PDO in this example for convenience. SQLite is included with PHP, so there is no need to configure a database if you want to try the code out on XAMPP. Another benefit of using SQLite is that SELECT queries are executed faster than MySQL queries; so its performant. The SQL used in the table creation in this example technically doesn’t have to be as verbose as it is. I could have just used: “CREATE TABLE(and a bunch of column name separated by commas)” and the table would have been created, SQLite would have figured the rest out. It’s fairly easy to translate the table creation SQL to MySQL the way it is now if you have to. Also, using PDO isn’t the same as using the SQLite engine; don’t count on any of the SQLite engine functions documented in the PHP documentation in other words. When using PDO you can only use PDO functions. Although not required in this instance, I find that relying totally on PHP for the grunt work that SQLite functions provide makes my code more portable. The down side of using SQLite is that it does not scale up at all. For an example on initializing MySQL with PDO and FlightPHP check out the FlightPHP documentation.

About Serving Static Files

Setting up Flight to serve up static file was simple. I first set a global variable in Flight for the static folder path using the same format as Flight uses for the views folder for convenience. I then mapped a global function in Flight to handle returning the file. I could have, technically, set up static file retrieval through the htaccess file on the server. I’ve had my share of problems, however, with htaccess files hosted on shared hosting in the past; they seem to differ in small ways from host to host. I actually like having all requests run through the index.php file better as it lends to greater flexibility in my coding.
The route used for serving static files is matched to the root folder level with a wildcard. Based on the premise that the routes are tested in order, from top to bottom, other dynamic routes can be included if needed. When adding routes with this setup you should consider at least one folder level route prefix to make route matching easier. Or, you could change the code to serve all of the static resources from a different folder route instead of root; just remember create a sub-folder structure in the static folder to match the route as the getStaticFile function operates off of the request URL path.

About The Maps

The three maps used in this example are displayed in an iframe. The google map is the only one of the three that require an API key for embedding. The way its coded, if you don’t include an embed key from google the google map will not be displayed. The maps lacks a bubble dialog on the exact IP location as a result. Considering that IP geolocation is an approximation of the users location, it really doesn’t matter. If you want to display a bubble dialog on the maps you’ll have to get a javascript API key for each of the three and do the javascript yourself.
Initially, I wanted to leave javascript out of this example, keeping the focus on using the PHP framework for HTML site development in this blog post. Since the maps displayed in the iframe require javascript to be enabled, and I had nothing but problems sizing the map iframe properly with CSS, I used javascript to accomplish the task. The noscript version of the page has no javascript and the center pane is sized and placed with CSS as its supposed to be.

About the CSS

I hacked out my own CSS for this project. Because I used the box-sizing feature of CSS3, this example only supports modern browsers. Furthermore, I didn’t use any media queries in the CSS. Horizontal scrolling starts at 700 pixels; so the outputted page is less than ideal for smaller screens. In reflection, perhaps I should have used Bootstrap and made it look like everything else on the Internet.

About setting this up and actually using this

I tried to make it easy to set this up for hosting by mapping global variable towards the top of the index.php file. Most of the variables are self explanatory. The title prefix variable is what is displayed in the upper left corner of the page (branding) and in the title. That links to whatever overrides the base URL variable. As I stated previously, if you don’t include a google embed key the google map will not be shown; its coded to show the wikimapia map instead if the default map type is still set to google (which is based on a google map). If you use google analytics, uncomment and set the UA key and the script will inject the boilerplate code at the bottom of the HTML. Every hit is recorded in the database in case you want to roll your own later.

A Birdhouse or a Townhome

Technically, this is a WhatsMyIPSite too.

<?php echo “<h1>Your IP is: “ . $_SERVER[REMOTE_ADDR] . “</h1>”; ?>


// http://flightphp.com/
require 'flight/Flight.php';
// http://www.geoplugin.com/webservices/php
// require 'includes/geoplugin.class.php';
// http://www.phpclasses.org/package/5082-PHP-Determine-the-MIME-type-of-a-file.html
// require 'includes/phpmimetypeclass-2010-10-19/class.mime.php';

// http://firephp.org/
//require 'includes/FirePHP.class.php';
//Flight::set('isdebug', false);
//$isdebug = Flight::get('isdebug');
//if($isdebug){$firephp = FirePHP::getInstance(true);}

// Set framework variables
// Override the base url of the request. (default: null)
Flight::set('flight.base_url', null);
// Allow Flight to handle all errors internally. (default: true)
Flight::set('flight.handle_errors', true);
// Log errors to the web server's error log file. (default: false)
Flight::set('flight.log_errors', true);
// Directory containing view template files (default: ./views)
Flight::set('flight.views.path', 'views');
// set your own variables now
Flight::set('flight.static.path', 'static');
// use js? (include maps and scripts in other words)
Flight::set('js', false);
Flight::set('title_prefix', 'WhatsMyIPSite');
Flight::set('meta_description', 'WhatsMyIPSite tells visitors what IP address they are making requests from.');
// set default maptype here (google, here, or wikimapia)
Flight::set('maptype', 'google');
// Google Maps Embed API key
// https://developers.google.com/maps/documentation/embed/guide#api_key
// Flight::set('google_embed_key', 'your_google_map_embed_key_here');
// Google Analytics - replace UA-XXXXX-X with your site id
//Flight::set('google_analytics_site_id', 'UA-XXXXX-X');

// Now do classes
// Register db class with constructor parameters
Flight::register('db', 'PDO', array('sqlite:data/flightgeoip.sqlite3'), function($db){
    // after construct callback (with new object passed in)
    // now create tables if needed
    $db->exec('CREATE TABLE IF NOT EXISTS geo (
                    id INTEGER PRIMARY KEY,
                    ip TEXT UNIQUE,
                    geoplugin_city TEXT,
                    geoplugin_region TEXT,
                    geoplugin_areaCode TEXT,
                    geoplugin_dmaCode TEXT,
                    geoplugin_countryCode TEXT,
                    geoplugin_countryName TEXT,
                    geoplugin_continentCode TEXT,
                    geoplugin_latitude TEXT,
                    geoplugin_longitude TEXT,
                    geoplugin_regionCode TEXT,
                    geoplugin_regionName TEXT)');
    $db->exec('CREATE TABLE IF NOT EXISTS requests (
                    id INTEGER PRIMARY KEY,
                    path TEXT,
                    ip TEXT,
                    uastring TEXT,
                    time INTEGER)');
//$db = Flight::db();

// Now extend Flight with your methods
 * getStaticFile
 * Returns a static file in the static directory or 404.
 * @param [string] $filepath - path to the static file
Flight::map('getStaticFile', function($req_path){
    $filepath = Flight::get('flight.static.path') . $req_path;
	// sometimes relative to root not folder
		$server_path = realpath($_SERVER['DOCUMENT_ROOT']);
		$this_path = realpath(dirname(__FILE__));
		if (strlen($this_path) > strlen($server_path)) {
			$tmp = substr($this_path, strlen($server_path));
			$filepath = Flight::get('flight.static.path') . substr($req_path, strlen($tmp));
    if (file_exists($filepath)) {
		// we don't need MIME id until we need it
		require_once 'includes/phpmimetypeclass-2010-10-19/class.mime.php';
		$mime = new MIMETypes('includes/phpmimetypeclass-2010-10-19/class.types.php');
        $mimetype = $mime->getMimeType($filepath);
        header("Content-Type: $mimetype", true);
    } else {
        // 404 it
 * checkIP
 * Checks the ip of the request for geolocation
 * information, if none is found the information is requested.
 * @param [string] $ip - request IP
Flight::map('checkIP', function($ip){
    // query db for ip first
    $db = Flight::db();
	// If localhost set a real IP
	if($ip = '::1'){
		$ip = '';
    $dbquery ="SELECT * FROM geo WHERE ip IS '" . $ip . "'";
    $result = $db->query($dbquery);
	// does it exist in db?
    if($result->fetch(PDO::FETCH_NUM) > 0){
		$result = $db->query($dbquery);
		foreach ($result as $row) {
			// set geo information into globals
			if(strlen($row['ip']) > 0){Flight::set('ip', $row['ip']);}
			if(strlen($row['geoplugin_city']) > 0){Flight::set('city', $row['geoplugin_city']);}
			if(strlen($row['geoplugin_region']) > 0){Flight::set('region', $row['geoplugin_region']);}
			if(strlen($row['geoplugin_areaCode']) > 0){Flight::set('areaCode', $row['geoplugin_areaCode']);}
			if(strlen($row['geoplugin_dmaCode']) > 0){Flight::set('dmaCode', $row['geoplugin_dmaCode']);}
			if(strlen($row['geoplugin_countryCode']) > 0){Flight::set('countryCode', $row['geoplugin_countryCode']);}
			if(strlen($row['geoplugin_countryName']) > 0){Flight::set('countryName', $row['geoplugin_countryName']);}
			if(strlen($row['geoplugin_continentCode']) > 0){Flight::set('continentCode', $row['geoplugin_continentCode']);}
			if(strlen($row['geoplugin_latitude']) > 0){Flight::set('latitude', $row['geoplugin_latitude']);}
			if(strlen($row['geoplugin_longitude']) > 0){Flight::set('longitude', $row['geoplugin_longitude']);}
			if(strlen($row['geoplugin_regionCode']) > 0){Flight::set('regionCode', $row['geoplugin_regionCode']);}
			if(strlen($row['geoplugin_regionName']) > 0){Flight::set('regionName', $row['geoplugin_regionName']);}
		Flight::set('geoinfo', true);
		require 'includes/geoplugin.class.php';
        $geoplugin = new geoPlugin();
		// geoplugin.com API up?
        if( strlen($geoplugin->ip ) > 0){
			$ipquery = "INSERT INTO geo (ip, geoplugin_city, geoplugin_region, geoplugin_areaCode, geoplugin_dmaCode, geoplugin_countryCode, geoplugin_countryName, geoplugin_continentCode, geoplugin_latitude, geoplugin_longitude, geoplugin_regionCode, geoplugin_regionName) VALUES(:ip, :geoplugin_city, :geoplugin_region, :geoplugin_areaCode, :geoplugin_dmaCode, :geoplugin_countryCode, :geoplugin_countryName, :geoplugin_continentCode, :geoplugin_latitude, :geoplugin_longitude, :geoplugin_regionCode, :geoplugin_regionName)";
			$stmt = $db->prepare($ipquery);
			$stmt->bindParam(':ip', $geoplugin->ip);
			$stmt->bindParam(':geoplugin_city', $geoplugin->city);
			$stmt->bindParam(':geoplugin_region', $geoplugin->region);
			$stmt->bindParam(':geoplugin_areaCode', $geoplugin->areaCode);
			$stmt->bindParam(':geoplugin_dmaCode', $geoplugin->dmaCode);
			$stmt->bindParam(':geoplugin_countryCode', $geoplugin->countryCode);
			$stmt->bindParam(':geoplugin_countryName', $geoplugin->countryName);
			$stmt->bindParam(':geoplugin_continentCode', $geoplugin->continentCode);
			$stmt->bindParam(':geoplugin_latitude', $geoplugin->latitude);
			$stmt->bindParam(':geoplugin_longitude', $geoplugin->longitude);
			$stmt->bindParam(':geoplugin_regionCode', $geoplugin->regionCode);
			$stmt->bindParam(':geoplugin_regionName', $geoplugin->regionName);
			// globals
			if(strlen($geoplugin->ip) > 0){Flight::set('ip', $geoplugin->ip);}
			if(strlen($geoplugin->city) > 0){Flight::set('city', $geoplugin->city);}
			if(strlen($geoplugin->region) > 0){Flight::set('region', $geoplugin->region);}
			if(strlen($geoplugin->areaCode) > 0){Flight::set('areaCode', $geoplugin->areaCode);}
			if(strlen($geoplugin->dmaCode) > 0){Flight::set('dmaCode', $geoplugin->dmaCode);}
			if(strlen($geoplugin->countryCode) > 0){Flight::set('countryCode', $geoplugin->countryCode);}
			if(strlen($geoplugin->countryName) > 0){Flight::set('countryName', $geoplugin->countryName);}
			if(strlen($geoplugin->continentCode) > 0){Flight::set('continentCode', $geoplugin->continentCode);}
			if(strlen($geoplugin->latitude) > 0){Flight::set('latitude', $geoplugin->latitude);}
			if(strlen($geoplugin->longitude) > 0){Flight::set('longitude', $geoplugin->longitude);}
			if(strlen($geoplugin->regionCode) > 0){Flight::set('regionCode', $geoplugin->regionCode);}
			if(strlen($geoplugin->regionName) > 0){Flight::set('regionName', $geoplugin->regionName);}
			Flight::set('geoinfo', true);
			// geoplugin.com not responding
			Flight::set('geoinfo', false);

 * This acts as middleware of sorts
 * If your route function returns true
 * execution is passed on to the next
 * matching route function.
Flight::route('*', function(){
    $request = Flight::request();
    $db = Flight::db();
    $insert = "INSERT INTO requests (path, ip, uastring, time)
                VALUES (:path, :ip, :uastring, :time)";
    $stmt = $db->prepare($insert);
    $stmt->bindParam(':path', $request->url);
    $stmt->bindParam(':ip', $request->ip);
    $stmt->bindParam(':uastring', $request->user_agent);
    $stmt->bindParam(':time', time());
    // stuff hit into db
    // return true to pass execution
    return true;
// Now define your routes and handlers

//front page
Flight::route('GET /', function(){
	$templatedata = array();
	$request = Flight::request();
	// redundant as Flight::get is available in template too 
	$templatedata['ip'] = $request->ip;
	$templatedata['geoinfo'] = Flight::get('geoinfo');
	$templatedata['meta_description'] = Flight::get('meta_description');
	// evaluate the query string (if any)
		$templatedata['js'] = $request->query->js;
		$templatedata['js'] = Flight::get('js');
		$templatedata['maptype'] = $request->query->maptype;
		$templatedata['maptype'] = Flight::get('maptype');
	Flight::render('partials/iframe_insert.php', $templatedata,
    Flight::render('default.php', $templatedata);

// place any other dynamic routes here

// everything else (static folder)
Flight::route('GET /*', function(){
    $fpath = Flight::request()->url;
// Now Set It Off!


<!DOCTYPE html>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title><?php echo Flight::get('title_prefix'); ?> - Your IP is: <?php echo Flight::get('ip'); ?></title>
    <meta name="description" content="<?php echo $meta_description; ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="icon" type="image/x-icon" href="favicon.ico" />
	<?php if($geoinfo && $js){	?>
    <script type="text/javascript">
		window.onload = function() {resizemap();}
		window.onresize = function() {resizemap();}
		function resizemap(){
			var map = document.getElementById("map");
			var container = document.getElementById("container");
			map.height = container.clientHeight - 75;
			map.width = container.clientWidth;
	<?php } ?>
	<link rel="stylesheet" href="css/main.css">
<noscript>Either enable JavaScript or click <a href="?js=0">here</a> </noscript>
<div id="container">
            <div id="toppane">
                <div class="box30">
					<div id="lefttop"><div id="brand"><a href="<?php echo Flight::get('base_url'); ?>"><?php echo Flight::get( "title_prefix"); ?></a></div></div>
                <div class="box40">
					<div id="centertop"><span id="iplbl">Your IP is: </span><br/><?php echo Flight::get('ip'); ?></div>
                <div class="box30">
					<div id="righttop">
						<?php if($geoinfo && $js){	?>
						<div class="tmbutt"><a href="?maptype=here">Here</a></div>
						<div class="tmbutt"><a href="?maptype=wikimapia">Wikimapia</a></div>
						<?php if(Flight::has('google_embed_key')){ ?>
						<div class="tmbutt"><a href="?maptype=google">Google</a></div>
						<?php } ?>
						<?php }else{ ?>
						<div class="tmbutt">
						<?php echo date('M d Y'); ?>
						<?php } ?>
				<!--<div style="clear:both;"></div>-->
			<?php echo $iframe_insert; ?>
	<div id="bottompane">
		IP <?php echo Flight::get('ip'); ?> =
		<?php echo Flight::get('latitude'); ?> ,
		<?php echo Flight::get('longitude'); ?> - 
		<?php echo Flight::get('city'); ?>, 
		<?php echo Flight::get('region'); ?>, 
		<?php echo Flight::get('countryName'); ?> - 
		Telephone Area Code: <?php echo Flight::get('areaCode'); ?>
		<a class="right" target="_blank" href="http://geoplugin.com/">geoplugin.com</a>
<?php if($js && Flight::has('google_analytics_site_id')){	?>
        function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date;
    ga('create','<?php echo Flight::get('google_analytics_site_id'); ?>');ga('send','pageview');

<?php } ?>


<?php if($geoinfo && $js){
		switch ($maptype)
		case 'here':
			$mapurl = "https://www.here.com/?map=" . Flight::get('latitude') . "," . Flight::get('longitude') . ",12,satellite";
		case 'google':
				$mapurl = "https://www.google.com/maps/embed/v1/view?key=" . Flight::get('google_embed_key') . "&center=" . Flight::get('latitude') . "," . Flight::get('longitude') . "&zoom=12&maptype=satellite";
			} else{
				$mapurl = "http://wikimapia.org/#lang=en&lat=" . Flight::get('latitude') . "&lon=" . Flight::get('longitude') . "&z=12&m=b";
		case 'wikimapia':
			$mapurl = "http://wikimapia.org/#lang=en&lat=" . Flight::get('latitude') . "&lon=" . Flight::get('longitude') . "&z=12&m=b";
			$mapurl = "https://www.google.com/maps/embed/v1/view?key=" . Flight::get('google_embed_key') . "&center=" . Flight::get('latitude') . "," . Flight::get('longitude') . "&zoom=12&maptype=satellite";

			<div id="mappane">
			<a href="?phpecho$mapurl;?">?phpecho$mapurl;?</a>	
<?php }elseif($geoinfo){  ?>
	$heremapurl = "https://www.here.com/?map=" . Flight::get('latitude') . "," . Flight::get('longitude') . ",12,satellite";
	$googlemapurl = "https://www.google.com/maps/@" . Flight::get('latitude') . "," . Flight::get('longitude') . ",12z";
	$wikimapiamapurl = "http://wikimapia.org/#lang=en&lat=" . Flight::get('latitude') . "&lon=" . Flight::get('longitude') . "&z=12&m=b";
	$openstreetmapmapurl = "http://www.openstreetmap.org/#map=12/" . Flight::get('latitude') . "/" . Flight::get('longitude');
	$weathermapurl = "http://www.wunderground.com/wundermap/?lat=" . Flight::get('latitude') . "&lon=" . Flight::get('longitude') . "&zoom=8&pin=" . Flight::get('city') . "%2c%20" . Flight::get('region');
	<div id="mappane">
		<div class="box50">
			<h3><?php echo Flight::get('ip'); ?></h3>
			<ul class="nsl">
				<li class="nsli"><span class="nsinfo">City: <strong><?php echo Flight::get('city'); ?></strong></span> </li>
				<li class="nsli"><span class="nsinfo">Region: <strong><?php echo Flight::get('region'); ?></strong></span> </li>
				<li class="nsli"><span class="nsinfo">Area Code: <strong><?php echo Flight::get('areaCode'); ?></strong></span> </li>
				<li class="nsli"><span class="nsinfo">DMA Code: <strong><?php echo Flight::get('dmaCode'); ?></strong></span> </li>
				<li class="nsli"><span class="nsinfo">Country Code: <strong><?php echo Flight::get('countryCode'); ?></strong></span> </li>
				<li class="nsli"><span class="nsinfo">Country Name: <strong><?php echo Flight::get('countryName'); ?></strong></span> </li>
				<li class="nsli"><span class="nsinfo">Continent Code: <strong><?php echo Flight::get('continentCode'); ?></strong></span> </li>
				<li class="nsli"><span class="nsinfo">Latitude: <strong><?php echo Flight::get('latitude'); ?></strong></span> </li>
				<li class="nsli"><span class="nsinfo">Longitude: <strong><?php echo Flight::get('longitude'); ?></strong></span> </li>
		<div class="box50">
			<h4>View location on one of these map sites:</h4>
			<ul class="nsl">
				<li class="nsli"><a class="nsinfo" href="<?php echo $googlemapurl; ?>" target="_blank">Google Map</a> </li>
				<li class="nsli"><a class="nsinfo" href="<?php echo $heremapurl; ?>" target="_blank">Here Map</a> </li>
				<li class="nsli"><a class="nsinfo" href="<?php echo $wikimapiamapurl; ?>" target="_blank">Wikimapia Map</a> </li>
				<li class="nsli"><a class="nsinfo" href="<?php echo $openstreetmapmapurl; ?>" target="_blank">Open Street Map</a> </li>
			<h4>Weather Map:</h4>
			<ul class="nsl">
				<li class="nsli"><a class="nsinfo" href="<?php echo $weathermapurl; ?>" target="_blank">
					Weather Underground Wundermap
					</a> </li>
		<!--<div style="clear:both;"></div>-->
<?php }else{  ?>
			<div id="mappane">
				<h2>Unable to retrieve Geographical information at this time. Please try again later.</h2>
				<h3>Our Geographical Information Provider May Not Be Responding</h3>
				<p>If you are connected to the Internet you can try one of the links below to view your IP address location.
				 These Internet map application providers provide a map based on your IP address by default.</p>
					<li><a href="https://www.google.com/maps/" >google.com</a></li>
					<li><a href="https://www.here.com/" >here.com</a></li>
					<li><a href="https://maps.yahoo.com/" >maps.yahoo.com</a></li>
<?php } ?>	

My hacked out main.css

body  {
	margin: 0px;
	padding: 0px;
	background-color: #444444;
	color: white;
	min-width: 700px;
#container {
div#toppane {
	box-sizing: border-box;
	height: 50px;
	background-color: #030303;
	color: #cccccc;
	box-sizing: border-box;
	padding-bottom: 25px;
	margin-bottom: -25px;
	height: 100vh;
	box-sizing: border-box;
	background-color: #030303;
	color: #cccccc;
	height: 25px;
	position: fixed;
	width: 100%;
	font-size: 200%;
#brand a {
	font-size: 70%;
	line-height: 100%;
	box-sizing: content-box;
	width: 30%;
	box-sizing: content-box;
	width: 40%;
	box-sizing: content-box;
	width: 50%;
	box-sizing: border-box;
	height: 100%;
	padding:15px 10px 10px 10px;
.tmbutt a {
	color: #cccccc;
	text-decoration: none;
	background-color: #444444;
	color: #eeeeee;
	font-size: 125%;
	list-style: none;
	padding: 10px;

External Resources Used:
Debugging PHP:
Articles relating to micro-frameworks:
A great listing of various PHP resources:

Source Code Package

Available on GitHub


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s