<html>
<head><title>Geostring Parsing in PHP example</title></head>
<body>
<!-- An example by "Epicanis" of The Big Room blog: http://www.bigroom.org/wordpress
comments and suggestions welcome. Permission granted to take this code and do more
or less whatever you want with it. I'd appreciate it if you'd post on the geostring
page if you find this useful or interesting:
http://www.bigroom.org/wordpress/?page_id=110 -->
<p>Examining <a href="http://www.twitter.com/Epicanis">Epicanis' Twitter feed</a> for geostrings...</p>
<p>If any are found, the timestamps on the points will appear as links to those points on Google Maps.</p>
<p>
<?php
//some simple procedures for generating, finding, and parsing geostring tags
//( http://www.bigroom.org/wordpress/?page_id=110 )
//This example is pretty pedantic, and purely procedural, just to illustrate things
function gatherGeostr($text)
{//given some text, this function attempts to pull out all portions of it which
//are within geostr tags
//returns an array containing each geostr tag's contents in an array, or false if
//none are found
$matches=Array(); //array to hold all matched portions of the text
if (preg_match_all('/geostr:([\w-+\.,:]+):geostr/',$text,$matches)) {
//this isn't perfect - if you have two geostr tags directly concatenated, one
//of the matches that comes back will be the entire string between the first
//tag's "geostr:" AND the SECOND tag's ":geostr". Hopefully this will almost never happen
//in practice
//at this point, we have at least one match. $matches[1] should contain an array of the
//tag contents that we need. ($matches[0] should contain an array of each entire tag,
//including the "geostr::geostr" part)
return $matches[1];
} else {
return false; //no geostr tags found
}
}
function parseGeostr($parseMe)
{//as written, this function expects to work on an array, but
//it'll accept a single string. It'll cheat and shove the string
//into a single-element array just so the rest of the code still works the same
//it returns an array, each element containing an array containing each
//tag's information, with the keys set to the attribute names:
//lat,lon,elev,timestamp,offset,trackid,heading,angle
//and their values set appropriately. If the geostr is a polygon
//rather than a point, lat, lon, elev will be set to the averages,
//so as to provide a "central point", and a "polygon" key will be
//added to the array, its value being an array which in turn contains
//an array of lat=> lon=> elev=> key/value pairs representing the corners
//of the polygon.
$toParse=Array();
$dataOut=Array(); //to hold the arrays of geostring data found
if (!(is_array($parseMe))) { //assume a string if not an array
$toParse[]=$parseMe;
} else {
$toParse=$parseMe;
}
foreach($toParse as $parseMe) { //no reason not to re-use $parseMe since we're done with it...
//first, split the string into its segments
list($where,$when,$whither) = explode(":",$parseMe);
//If the optional when or whither portions of the string are missing or blank,
//the variables in question will be null, but the line still does its job.
//if you haven't pre-removed the "geostr:*:geostr" part of the geostr tag already
//you can render that line as:
// list($geo,$where,$when,$whither,$geo)=explode(":",$parseMe);
//and just ignore or unset() the $geo variable afterwards. The end result is the same.
//now we can parse the individual segments of the string
//again, anything left out ends up simply left null
//since "when" and "whither" always have the same form, regardless of
//whether the geostring represents a point or polygon, we'll deal with them first
//when
list($timestamp,$offset,$trackid)=explode(",",$when);
//whither
list($heading,$angle)=explode(",",$whither);
//where - the only mandatory one
//"where" might be a point or a polygon, so check before assigning
if (substr_count($where,',') > 2) { //has to be a polygon if that many commas
//a point contains no more than 2 commas (sometimes only one if there is
//no elevation specified). A polygon must contain at least three points
//worth of information, and each point include a space for elevation (even if it's
//blank), so a polygon must contain at least 6 commas in the "where" segment
$polypoints=Array(); //the array that'll be stuck into the returned data
$latsum=0;
$lonsum=0;
$elevsum=0;
$polydata=explode(",",$where);
while(count($polypoints)) { //implicitly: "while(count($polypoints) >0)"
//the individual components are required by the standard to be repeating
//lat,lon,elevation sets, so we can just do this:
$lat=array_shift($polydata);
$lon=array_shift($polydata);
$elev=array_shift($polydata);
//now shove that point onto the stack of points
$polypoints[]=Array("lat"=>$lat,"lon"=>$lon,"elev"=>$elev);
//keep track of this point, so as to figure out an "average" single
//point at the end
$latsum+=$lat;
$lonsum+=$lon;
$elevsum+=$elev;
}
//okay, we've now shifted all of the points representing the corners of
//the polygon off. Now we can calculate the single "average" point
//and assemble the array to be returned
$pointcount=count($polypoints);
$lat=$latsum/$pointcount; //we're done with $lat/$lon/$elev from the loop
$lon=$lonsum/$pointcount; //so we might as well re-use them...
$elev=$elevsum/$pointcount; //elevation will be incorrect if only SOME of
//the points have elevation data, but I'm trying
//to keep this example as simple as feasible
//finally, shove this geostring's data into the list
$dataOut[]=Array("lat"=>$lat,"lon"=>$lon,"elev"=>$elev,"timestamp"=>$timestamp,"offset"=>$offset,
"trackid"=>$trackid,"heading"=>$heading,"angle"=>$angle,"polypoints"=>$polypoints);
} else { //only 1 or 2 commas, so only lat,lon (and optionall ,elev)
//much simpler than polygons...
list($lat,$lon,$elev)=explode(",",$where);
$dataOut[]=Array("lat"=>$lat,"lon"=>$lon,"elev"=>$elev,"timestamp"=>$timestamp,"offset"=>$offset,
"trackid"=>$trackid,"heading"=>$heading,"angle"=>$angle);
}
} //processing of each individual geostring ends here. Exit loop when there are not more geostrings
//found in the text to parse
//$dataOut now contains an array of one or more geostring's parsed data. Return it.
return $dataOut;
} //Yeah, I know, this is kind of long to keep as one long function. It's just an example.
function makeGoogleMapsUrl($lat,$lon)
{//ridiculous simplistic thing that generates a Google Maps URL
//from a given $lat and $lon
return "http://maps.google.com/?q=$lat,$lon&t=h";
}
function showGeostrPoints($parsedArray)
{//now we get to my stupid little example application
//pass this function the array that comes out of parseGeostr()
//and it generates and outputs HTML links to Google Maps
//displays centered at the given lat/lon pairs.
//This silly little example doesn't do anything special with polygons here, nor
//does it group points with the same trackid together as it
//should. Obviously you could draw a polygon or track using
//Google Maps functions assuming you get an API key.
if (!(count($parsedArray))) {//no points!
return false;
}
foreach($parsedArray as $pointArray) {
print("<a href=\"".makeGoogleMapsUrl($pointArray["lat"],$pointArray["lon"])."\" target=\"_new\">".
$pointArray["timestamp"]."</a><br />\n");
}
return true;
//TOLD you it was just a silly little example...
}
//Now the meat of this demonstration - we read my Twitter feed off of Twitter's
//website, then shove the entire batch of HTML through the parsing routine and
//it should spit out a series of one or more links to Google Maps displays of
//each point found. Drum roll, please....
$text_to_read="http://www.twitter.com/Epicanis"; //change if you want some other page
$twitterFeed=file_get_contents($text_to_read);
$geostrings=parseGeostr(gatherGeostr($twitterFeed));
if(!showGeostrPoints($geostrings)) { //there were no geostr tags found
print("We read ".strlen($twitterFeed)." bytes from the Twitter feed ");
print("but we found no geostring tags in it!<br />\n");
} //since "showGeostrPoints()" generates its own output, we don't have
//to explicitly do anything if any points were found, just give a message
//if there were none...
?>
</p>
</body>
</html>