Skip to content

Instantly share code, notes, and snippets.

@sdennler
Last active April 26, 2017 10:57
Show Gist options
  • Save sdennler/e88bf087a24e423b86eaeac518aaf4a7 to your computer and use it in GitHub Desktop.
Save sdennler/e88bf087a24e423b86eaeac518aaf4a7 to your computer and use it in GitHub Desktop.
Reads GeoCaching GPX files and combines them to one CSV
<?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