Last active
April 26, 2017 10:57
-
-
Save sdennler/e88bf087a24e423b86eaeac518aaf4a7 to your computer and use it in GitHub Desktop.
Reads GeoCaching GPX files and combines them to one CSV
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Reads GeoCaching GPX files (GPX files whit GeoCache data in groundspeak extendion) | |
* and creates a CSV file 'result.csv' in the current folder whit all found caches. | |
* @version 1 | |
* @author https://github.com/sdennler | |
*/ | |
#include '../SadWebTools/lib/sad.php'; | |
$cDebug = true; | |
if(count($argv) < 3){ | |
die("Usage: ${argv[0]} FinderName file1.gpx [firle2.gpx [...]]\n"); | |
} | |
// Read files | |
$caches = array(); | |
array_shift($argv); | |
$finder = array_shift($argv); | |
$reader = new XMLReader(); | |
while(($file = array_shift($argv))){ | |
if(!is_readable($file)){ | |
echo 'File "'.$file."\" not readable!\n"; | |
} | |
$reader->open($file); | |
$caches = readGeoCachingGPX($reader, $caches); | |
} | |
$reader->close(); | |
// Do things wiht readed data | |
GeoCache::setDefault('find_no', ''); | |
$geoName = new GeoNames($finder); | |
$fo = fopen('result.csv', 'w'); | |
fputcsv($fo, $caches[0]->getFindHead()); | |
foreach($caches as $cache){ | |
$cache->setGeonames($geoName->get($cache->get('lat'), $cache->get('lon'))); | |
$values = $cache->getFindValuesFor($finder); | |
if($values) fputcsv($fo, $values); | |
} | |
#sad(GeoCacheLog::$noFoundStatus, 'No Found Status'); | |
function readGeoCachingGPX(XMLReader $reader, $caches = array()){ | |
while($reader->read()) { //start reading. | |
#sad($reader->name, 'name frist loop'); | |
if($reader->nodeType == XMLReader::ELEMENT && $reader->name == 'author'){ // only opening tags of <author> | |
$author = $reader->readInnerXML(); | |
if($author == 'Groundspeak'){ | |
GeoCache::setDefault('src', 'geocaching.com'); | |
GeoCache::setDefault('find_gc.com', '1'); | |
} | |
elseif($author == 'Opencaching.de'){ | |
GeoCache::setDefault('src', 'opencaching.de'); | |
GeoCache::setDefault('find_gc.com', ''); | |
} | |
} | |
elseif($reader->nodeType == XMLReader::ELEMENT && $reader->name == 'wpt'){ // only opening tags of <wpt> | |
$cache = new GeoCache(); | |
if($reader->hasAttributes){ // read attributes of <wpt> | |
while($reader->moveToNextAttribute()){ | |
$cache->set($reader->name, $reader->value); | |
} | |
} | |
$namesUnhandled = array(); | |
$namesToReadPlain = array('time', 'name', 'desc', 'url', 'urlname', 'sym', 'type', 'groundspeak:name', 'groundspeak:placed_by', 'groundspeak:owner', 'groundspeak:type', 'groundspeak:container', 'groundspeak:difficulty', 'groundspeak:terrain', 'groundspeak:country', 'groundspeak:state', 'groundspeak:short_description', 'groundspeak:long_description'); | |
$namesToIgnore = array('src', 'groundspeak:cache', 'groundspeak:attributes', 'groundspeak:attribute', 'groundspeak:logs', 'groundspeak:log', 'groundspeak:date', 'groundspeak:type', 'groundspeak:finder', 'groundspeak:text'); | |
while($reader->read()){ | |
#sad($reader->name, 'name wpt loop '.$reader->nodeType); | |
switch(true){ | |
case ($reader->nodeType == XMLReader::END_ELEMENT && $reader->name === 'wpt'): | |
break 2; // on </wpt> go back to main loop | |
case ($reader->nodeType == XMLReader::ELEMENT && in_array($reader->name, $namesToReadPlain)): | |
$cache->set($reader->name, htmlspecialchars_decode($reader->readInnerXML())); | |
break; | |
case ($reader->nodeType == XMLReader::ELEMENT && $reader->name == 'groundspeak:log'): | |
$log = $cache->createLog(); | |
$log->set('id', $reader->getAttribute('id')); | |
while($reader->read()){ | |
switch(true){ | |
case ($reader->nodeType == XMLReader::END_ELEMENT && $reader->name === 'groundspeak:log'): | |
break 2; // on </groundspeak:log> go back to main wpt | |
case ($reader->nodeType == XMLReader::ELEMENT): | |
$log->set($reader->name, htmlspecialchars_decode($reader->readInnerXML())); | |
break; | |
} | |
} | |
break; | |
case ($reader->nodeType == XMLReader::ELEMENT): | |
$namesUnhandled[] = $reader->name; | |
break; | |
} | |
} | |
$caches[] = $cache; | |
} | |
} | |
#sad(implode(", ", $namesUnhandled), 'Tag Names Unhandled'); | |
return $caches; | |
} | |
// Classes | |
class Collector{ | |
protected $data = array(); | |
static protected $dataDefaults = array(); | |
protected $dataDefaultsLocal = array(); | |
public function __construct(){ | |
$this->dataDefaultsLocal = self::$dataDefaults; | |
} | |
public function set($key, $value){ | |
$this->data[$key] = $value; | |
} | |
static public function setDefault($key, $value){ | |
self::$dataDefaults[$key] = $value; | |
} | |
public function get($key){ | |
if(isset($this->data[$key])) return $this->data[$key]; | |
elseif(isset($this->dataDefaultsLocal[$key])) return $this->dataDefaultsLocal[$key]; | |
else return null; | |
} | |
public function getKeys(){ | |
return array_keys($this->data); | |
} | |
} | |
class GeoCache extends Collector{ | |
protected $logs = array(); | |
protected $colsForFind = array( | |
'find_no' => 'find_no', | |
'find' => 'find', | |
'find_gc.com' => 'find_gc.com', | |
'logs#groundspeak:date' => 'found_date', | |
'logs#groundspeak:type' => 'found_type', | |
'logs#&count' => 'found_count', | |
'groundspeak:type' => 'type', | |
'name' => 'waypoint', | |
'groundspeak:name' => 'name', | |
'groundspeak:container' => 'container', | |
'groundspeak:difficulty' => 'difficulty', | |
'groundspeak:terrain' => 'terrain', | |
'groundspeak:placed_by' => 'placed_by', | |
'groundspeak:owner' => 'owner', | |
'time' => 'hidden_date', | |
'lat' => 'lat', | |
'lon' => 'lon', | |
'groundspeak:country' => 'country', | |
'groundspeak:state' => 'state', | |
'geoname_country' => 'geoname_country', | |
'geoname_adminname' => 'geoname_adminname', | |
'src' => 'src', | |
'url' => 'url', | |
); | |
public function createLog(){ | |
$log = new GeoCacheLog(); | |
$this->logs[] = $log; | |
return $log; | |
} | |
public function getLogs(){ | |
return $this->logs; | |
} | |
public function getFindHead(){ | |
return array_values($this->colsForFind); | |
} | |
public function getFindValuesFor($finder){ | |
$values = array(); | |
$this->removeNotFoundLogs($finder); | |
$count = count($this->logs); | |
if($count <= 0) return null; // It is not a find | |
foreach($this->colsForFind as $key => $value){ | |
$keys = explode('#', $key); | |
if(count($keys) == 1){ | |
$values[] = $this->get($keys[0]); | |
}elseif($keys[1] == '&count'){ | |
$values[] = $count; | |
}else{ | |
$values[] = $this->{$keys[0]}[0]->get($keys[1]); | |
} | |
} | |
return $values; | |
} | |
public function removeNotFoundLogs($finder){ | |
$foundLogs = array(); | |
foreach($this->logs as $log){ | |
if($log->isFoundLogFor($finder)){ | |
$foundLogs[] = $log; | |
} | |
} | |
$this->logs = $foundLogs; | |
} | |
public function setGeonames(Array $geoNames){ | |
foreach($geoNames as $key => $value){ | |
$this->set('geoname_'.$key, $value); | |
} | |
} | |
} | |
class GeoCacheLog extends Collector{ | |
public static $noFoundStatus = array(); | |
public function isFoundLogFor($finder){ | |
if($this->get('groundspeak:finder') != $finder) return false; | |
$foundTypes = array( | |
'Found it', | |
'Attended', | |
'Webcam Photo Taken', | |
); | |
$isFind = in_array($this->get('groundspeak:type'), $foundTypes); | |
if(!$isFind){ // Collect log types which are not considered a found | |
$type = $this->getType(); | |
if(isset(self::$noFoundStatus[$type])) self::$noFoundStatus[$type]++; | |
else self::$noFoundStatus[$type] = 1; | |
} | |
return $isFind; | |
} | |
public function getType(){ | |
return $this->get('groundspeak:type'); | |
} | |
} | |
class GeoNames{ | |
protected $geonamesUsername; | |
public function __construct($geonamesUsername){ | |
$this->geonamesUsername = $geonamesUsername; | |
} | |
public function get($lat, $lon){ | |
$url = "http://api.geonames.org/countrySubdivision?lat=$lat&lng=$lon&username=$this->geonamesUsername"; | |
$missing = false; | |
$unknown = 0; | |
echo "Read: $url\n"; | |
$geonames = simplexml_load_file($url); | |
$country = (string) $geonames->countrySubdivision->countryName; | |
$adminName = (string) $geonames->countrySubdivision->adminName1; | |
if(!$country) { | |
echo "Unknown Country!\n"; | |
$country = 'Unknown Country'; | |
$missing = true; | |
} | |
if(!$adminName) { | |
echo "Unknown Adminname!\n"; | |
$adminName = 'Unknown'; | |
$missing = true; | |
} | |
if($missing){ | |
$unknown++; | |
var_dump($geonames); | |
} | |
return array('country' => $country, 'adminname' => $adminName); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment