GeoCachingFrogger Teil 3 - Domain Model und GPX Reader

Einleitung

Als leidenschaftlicher Geocacher kommt man an einem Punkt an, an dem man gerne komfortabel die Geocaches verwalten und planen möchte. Es gibt dafür einige Tools im Internet, aber ich habe mir überlegt ein eigenes Tool zu schreiben und dabei die NetbeansRCP kennen zu lernen. Ich habe schon einige Swing-Anwendungen entwickelt, aber noch nie mit NetbeansRCP und bin gespannt, was mir die Plattform alles bietet und ob es tatsächlich der einfachere/bessere Weg ist.

Teil3 – Domain Model und GPX Reader

Domain Model

Damit wir die eingelesenen Daten intern nutzen können, legen wir ein Domain Model an. Dafür haben wir zuvor das Modul Model angelegt.  Direkt im package “de.billmann.geocaching.model” legen wir die Model-Klassen an.

Hier Beispielhaft die Implementierung der Klasse “Cache. Die Klasse ist ein simples Bean und enthält nur Properties, sowie getter- und setter-Methoden.

package de.billmann.geocaching.model;
 
import java.io.Serializable;
import java.math.BigDecimal;
import java.net.URL;
import java.util.Date;
import java.util.List;
 
/**
 * Domain Model: Cache
 * This class is an implementation of a geocache.
 */
public class Cache implements Serializable {
 
  // 
  private String hash;
 
  // 
  private String name;
 
  // 
  private String description;
 
  // 

Damit nun vom Modul GPX auf die Model Klassen zugegriffen werden kann, müssen diese freigegeben werden. Dies geschieht in den “Project Properties” des Moduls. Um dorthin zu gelangen, klickt man auf das Modul “Model” und mit der rechten Maustaste wählt man nun im Kontextmenü den Punkt “Properties” aus. Anschliessend kann man unter “API Versioning” einen Haken bei “Public Packages” für alle Packages machen, die von anderen benutzt werden sollen.

GPX-Reader Interface

Da es verschiedene Anbieter von Geocaching-Seiten gibt, gibt es auch unterschiedliche GPX-Formate. Jeder Anbieter unterstützt den allgemeinen Standard und erweitert diesen aber um spezielle Tags.
Um eventuell später die verschiedenen Varianten zu unterstützen, habe ich mich dafür entschieden, ein Interface einzuführen und dann für jeden Anbieter eine spezielle Implementierung bereit zu stellen. Das Interface ist der GPXReader. Dieses Interface ist auch das einzige was die Anwendung direkt kennt und stellt den Service dar, den die Anwendung später nutzt.

public interface GPXReader {
  public static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
  public List importGPX(InputStream in);
}

Groundspeak Implementierung

Als wohl größter und erfolgreichster Anbieter gilt die Firma Groundspeak, welche die Plattform geocaching.com betreibt. Das Groundspeak-Format werde ich als erstes Implementieren.
Hier folgt ein Auszug aus einer solchen GPX-Datei.

<?xml version="1.0" encoding="utf-8"?>
<gpx xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" creator="Groundspeak Pocket Query" xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd http://www.groundspeak.com/cache/1/0/1 http://www.groundspeak.com/cache/1/0/1/cache.xsd" xmlns="http://www.topografix.com/GPX/1/0">
 <name>Pocket Query Name</name>
 <desc>Geocache file generated by Groundspeak</desc>
 <author>Groundspeak</author>
 <email>contact@groundspeak.com</email>
 <time>2012-07-23T10:46:05.441915Z</time>
 <keywords>cache, geocache, groundspeak</keywords>
 <bounds minlat="48.8296" minlon="8.8887" maxlat="49.155017" maxlon="9.382417" />
 <wpt lat="49.005717" lon="9.135983">
 <time>2003-04-18T07:00:00Z</time>
 <name>GC0000</name>
 <desc>Example Cache by Nobody, Multi-cache (2/3)</desc>
 <url>http://www.geocaching.com/seek/cache_details.aspx?guid=3be70555-7227-424f-adf5-1111111111</url>
 <urlname>Example Cache</urlname>
 <sym>Geocache</sym>
 <type>Geocache|Multi-cache</type>
 <groundspeak:cache id="100000" available="True" archived="False" xmlns:groundspeak="http://www.groundspeak.com/cache/1/0/1">
 <groundspeak:name>Example Cache</groundspeak:name>
 <groundspeak:placed_by>Nobody</groundspeak:placed_by>
 <groundspeak:owner id="100000">Nobody</groundspeak:owner>
 <groundspeak:type>Multi-cache</groundspeak:type>
 <groundspeak:container>Regular</groundspeak:container>
 <groundspeak:attributes>
 <groundspeak:attribute id="14" inc="0">Recommended at night</groundspeak:attribute>
 <groundspeak:attribute id="24" inc="0">Wheelchair accessible</groundspeak:attribute>
 <groundspeak:attribute id="19" inc="1">Ticks</groundspeak:attribute>
 <groundspeak:attribute id="39" inc="1">Thorns</groundspeak:attribute>
 <groundspeak:attribute id="15" inc="1">Available during winter</groundspeak:attribute>
 <groundspeak:attribute id="13" inc="1">Available at all times</groundspeak:attribute>
 <groundspeak:attribute id="32" inc="1">Bicycles</groundspeak:attribute>
 <groundspeak:attribute id="25" inc="1">Parking available</groundspeak:attribute>
 <groundspeak:attribute id="1" inc="1">Dogs</groundspeak:attribute>
 </groundspeak:attributes>
 <groundspeak:difficulty>2</groundspeak:difficulty>
 <groundspeak:terrain>3</groundspeak:terrain>
 <groundspeak:country>Germany</groundspeak:country>
 <groundspeak:state>Baden-Württemberg</groundspeak:state>
 <groundspeak:short_description html="False">

Innerhalb der Methode

// ...
 
private List caches = new ArrayList<>();
private Cache currentCache = null;
 
   // ...
 
@Override
public List importGPX(InputStream in) {
 
 final XMLInputFactory factory = XMLInputFactory.newInstance();
 factory.setProperty("javax.xml.stream.isCoalescing", true);
 final XMLStreamReader parser = factory.createXMLStreamReader(in);
 
 while (parser.hasNext()) {
  int event = parser.next();
 
  // if the end of the document is reached close the parser
  if (event == XMLStreamConstants.END_DOCUMENT) {
   parser.close();
   break;
  }
 
  // element start
  if (event == XMLStreamConstants.START_ELEMENT) {
   final String prefix = parser.getName().getPrefix().trim().toLowerCase();
   final String tagname = parser.getName().getLocalPart().trim().toLowerCase();
 
   currentCache = new Cache();
   caches.add(currentCache);
 
   // ...
 
   switch (tagname) {
    case TAG_TIME:
        event = parser.next();
        final String timeAsString = parser.getText();
        try {
            final Date time = dateFormat.parse(timeAsString);
            currentCache.setTime(time);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        continue;
    case TAG_NAME:
        event = parser.next();
        currentCache.setHash(parser.getText().trim());
        continue;
    case TAG_DESC:
        event = parser.next();
        currentCache.setDescription(parser.getText().trim());
        continue;
    case TAG_URL:
        event = parser.next();
        try {
            currentCache.setUrl(new URL(parser.getText().trim()));
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        continue;
    case TAG_URLNAME:
        event = parser.next();
        currentCache.setUrlName(parser.getText().trim());
        continue;
    case TAG_SYM:
        event = parser.next();
        currentCache.setUrlName(parser.getText().trim());
        continue;
   }
 
  // ...
}

wird jetzt die als InputStream übergebene GPX-Datei eingelesen und es wird das interne Model aufgebaut. Am Ende wird dann eine Liste mit allen gefundenen Caches zurück geliefert.

Fazit

Damit haben wir nun die Möglichkeit geschaffen, dass die Daten aus der GPX-Datei ausgelesen und in das interne Domain Model gewandelt wird. Im nächsten Schritt werden wir dann eine minimale Anzeige der eingelesenen Daten entwickeln.

Comments