2009-01-27

Embedding Google Maps in Web Pages Served as application/xhtml+xml

I ran into trouble while trying to embed a Google Map into a page with Content-Type of application/xhtml+xml. I tried a few different ways of loading the script and initializing the GMap2 object, but they all ended up with Mozilla giving a somewhat cryptic error message (quotemarks exactly as below):

Object cannot be created in this context" code: "9

Turns out that Google's code uses the document.write() method and that that does not work with documents served as application/xhtml+xml. Which means that the pages must be served as text/html. I hope I could find a better solution but for now, that'll have to do.

2009-01-14

iStumbling at +60° 27' 47.54", +26° 56' 13.53"

See map (iStumbler was ran at Kotkankatu-Mariankatu crossing).

Quite a few of the access points have really obvious SSIDs:

iStumbling @Bar Blue 2009-01-14

2009-01-06

Visitor Locator, Take Two

The new version that I hacked together stores number of visits per country and shows the totals when a user clicks a countrys' marker. Visits are stored in an SQLite database, which, as you may know, makes things very easy as there is no server to look after etc. I was thinking of using Berkeley DB, because in an app like this, all that SQL is simply unnecessary sugar, but was lazy in the end (as usual).

Update: Added country flags in place of the same default icon for every country (see: Custom Icons section in Google Maps API).

Update 2: Added tooltip-like functionality, which shows country details in a transient window (label) instead of the default info window. See GxMarker for additional info.

Continuing here from where last nights' script ended. This is just the PHP side of things; Google Maps API examples can be found elsewhere. First we open an SQLite database and create a table for our visitor data if table does not exist:

try {
        $db = new PDO('sqlite:' . $_SERVER['DOCUMENT_ROOT'] . '/../db/visitor-locator.sqlite3');
} catch(PDOException $exception) {
        die($exception->getMessage());
}

$stmt = $db->query('SELECT name FROM sqlite_master WHERE type = \'table\'');
$result = $stmt->fetchAll();
if(sizeof($result) == 0) {
        $db->beginTransaction();
        $db->exec('CREATE TABLE visits (country TEXT, visits INTEGER, lat TEXT, lng TEXT);');
        $db->commit();
}

Next, check if the country is already in the table and if it is, increment the 'visits' field:

$stmt = $db->query('SELECT country, visits FROM visits WHERE country = \'' . $countryname . '\'');
$result = $stmt->fetch();

if($result['country']) {
        $db->beginTransaction();
        $stmt = $db->prepare('UPDATE visits SET visits=:visits, lat=:lat, lng=:lng WHERE country=:country');
        $stmt->bindParam(':country', $countryname, PDO::PARAM_STR);
        $visits = $result['visits'] + 1;
        $stmt->bindParam(':visits', $visits, PDO::PARAM_INT);
        $stmt->bindParam(':lat', $lat, PDO::PARAM_STR);
        $stmt->bindParam(':lng', $lng, PDO::PARAM_STR);
        $stmt->execute();
        $db->commit();
}

If country was not in the table, create a row for it:

else {
        $db->beginTransaction();
        $stmt = $db->prepare('INSERT INTO visits (country, visits, lat, lng) VALUES (:country, :visits, :lat, :lng)');
        $stmt->bindParam(':country', $countryname, PDO::PARAM_STR);
        $visits = 1;
        $stmt->bindParam(':visits', $visits, PDO::PARAM_INT);
        $stmt->bindParam(':lat', $lat, PDO::PARAM_STR);
        $stmt->bindParam(':lng', $lng, PDO::PARAM_STR);
        $stmt->execute();
        $db->commit();
}

And lastly, fetch all rows and form a Javascript array for our client-side script to use:

$result = $db->query('SELECT country, visits, lat, lng FROM visits');

echo "<script type=\"text/javascript\">\n";
echo "//<![CDATA[\n";
echo "var tbl_country = []; var tbl_visits = []; var tbl_lat = []; var tbl_lng = []; var count = 0;\n";
foreach($result->fetchAll() as $row) {
        echo 'tbl_country[count] = \'' . $row['country'] . '\'; ';
        echo 'tbl_visits[count] = \'' . $row['visits'] . '\'; ';
        echo 'tbl_lat[count] = \'' . $row['lat'] . '\'; ';
        echo 'tbl_lng[count] = \'' . $row['lng'] . '\';';
        echo " count++;\n";
}
echo "//]]>\n";
echo "</script>\n";

2009-01-05

MyFirstMashup™

Some friday-night hacking; this gets the users' country from the IP-to-location service provided by hostip.info, looks up coordinates using Google Geocoding service, and displays the result in Google Maps. The geocoding part could also be done in client side.

This PHP snippet is called in the <head> element before any other script. Note: as is the fine tradition here, no error checking is performed. The cURL/parsing part could also be made a bit less redundant (as in making a function of those). But it's friday night. So – reader discretion is advised.

Update: it's definitely not friday, but monday… I was in "friday-mode" because tomorrow is Epiphany (holiday).

Update 2: hostip.info provides coordinates too, at least for some places in its' database (this can be enabled using the position=true parameter in request; you can enter your location into their database in the site, and by doing so help make the database more accurate). Google has far greater coverage, though, so using their geocoding at the moment.

<?php
$ip = $_SERVER['REMOTE_ADDR'];
$url = "http://api.hostip.info/?ip=$ip";

$curl_handle=curl_init();
curl_setopt($curl_handle, CURLOPT_URL, $url);
curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, 7);
curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl_handle, CURLOPT_HEADER, 0);
$response = curl_exec($curl_handle);
curl_close($curl_handle);

$doc = new DOMDocument();
$parsestatus = $doc->loadXML($response, LIBXML_NOERROR | LIBXML_ERR_NONE);

$countryname = $doc->getElementsByTagName('countryName')->item(0)->nodeValue;

$url = "http://maps.google.com/maps/geo?output=xml&key=YOUR_API_KEY_GOES_HERE&q=" . urlencode($countryname);

$curl_handle=curl_init();
curl_setopt($curl_handle, CURLOPT_URL, $url);
curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, 7);
curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl_handle, CURLOPT_HEADER, 0);
$response = curl_exec($curl_handle);
curl_close($curl_handle);

$doc = new DOMDocument();
$parsestatus = $doc->loadXML($response, LIBXML_NOERROR | LIBXML_ERR_NONE);

$coordinates = $doc->getElementsByTagName('coordinates')->item(0)->nodeValue;
$coordinatesSplit = split(",", $coordinates);
$lat = $coordinatesSplit[1];
$lng = $coordinatesSplit[0];

echo "<script type=\"text/javascript\">\nvar countryname=\"$countryname\"; // from hostip.info\nvar lat=$lat; // from google geocoding\nvar\
 lng=$lng;\n</script>\n";
?>

Then, when initializing the map, those latitude and longitude variables are fed to the GMap2 .setCenter() method:

function load() {
  if (GBrowserIsCompatible()) {
    var map = new GMap2(document.getElementById("map"));
    map.setMapType(G_HYBRID_MAP);
    map.addControl(new GLargeMapControl());
    map.addControl(new GMapTypeControl());
    map.setCenter(new GLatLng(lat, lng), 3);

    var x = new GIcon(G_DEFAULT_ICON);
    x.image = "http://www.google.com/intl/en_us/mapfiles/ms/micons/green-dot.png";
    markerOptions = { icon:x };
    map.addOverlay(new GMarker(new GLatLng(lat, lng), markerOptions));
  }

2009-01-04

URL Fetch API, MiniDom (Google App Engine)

Fetching stuff with the URL Fetch API is simple (especially if one has faith that the source is there and it will deliver inside GAE time limits):

from google.appengine.api import urlfetch
from xml.dom import minidom

def parse(url):
  r = urlfetch.fetch(url)
  if r.status_code == 200:
    return minidom.parseString(r.content)

As is accessing the resulting DOM with MiniDom. Here the source is an Atom feed:

import time

dom = parse(URL)
for entry in dom.getElementsByTagName('entry'):
  try:
    published = entry.getElementsByTagName('published')[0].firstChild.data
    published = time.strftime('%a, %d %b', time.strptime(published, '%Y-%m-%dT%H:%M:%SZ'))
  except IndexError, ValueError:
    pass
  …