diff options
| author | Bananeweizen <bananeweizen@gmx.de> | 2013-06-22 12:17:08 +0200 |
|---|---|---|
| committer | Bananeweizen <bananeweizen@gmx.de> | 2013-06-22 12:17:08 +0200 |
| commit | 01bfdcaeeae619fd638f48b8fe94463e8131edd2 (patch) | |
| tree | 192f5421c231d5e93fa112c21d023a703d9473c5 /main/src/cgeo/geocaching/export/GpxSerializer.java | |
| parent | 22fa318357e3143ba196871bf5233aed41016766 (diff) | |
| download | cgeo-01bfdcaeeae619fd638f48b8fe94463e8131edd2.zip cgeo-01bfdcaeeae619fd638f48b8fe94463e8131edd2.tar.gz cgeo-01bfdcaeeae619fd638f48b8fe94463e8131edd2.tar.bz2 | |
refactoring: extract GPX serialization to make it testable
Diffstat (limited to 'main/src/cgeo/geocaching/export/GpxSerializer.java')
| -rw-r--r-- | main/src/cgeo/geocaching/export/GpxSerializer.java | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/main/src/cgeo/geocaching/export/GpxSerializer.java b/main/src/cgeo/geocaching/export/GpxSerializer.java new file mode 100644 index 0000000..2c50721 --- /dev/null +++ b/main/src/cgeo/geocaching/export/GpxSerializer.java @@ -0,0 +1,256 @@ +package cgeo.geocaching.export; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.LogEntry; +import cgeo.geocaching.Waypoint; +import cgeo.geocaching.cgData; +import cgeo.geocaching.enumerations.CacheAttribute; +import cgeo.geocaching.enumerations.LoadFlags; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.utils.TextUtils; +import cgeo.geocaching.utils.XmlUtils; +import cgeo.org.kxml2.io.KXmlSerializer; + +import org.apache.commons.lang3.StringUtils; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public final class GpxSerializer { + + private static final SimpleDateFormat dateFormatZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + public static final String PREFIX_XSI = "http://www.w3.org/2001/XMLSchema-instance"; + public static final String PREFIX_GPX = "http://www.topografix.com/GPX/1/0"; + public static final String PREFIX_GROUNDSPEAK = "http://www.groundspeak.com/cache/1/0"; + + /** + * During the export, only this number of geocaches is fully loaded into memory. + */ + public static final int CACHES_PER_BATCH = 100; + + /** + * counter for exported caches, used for progress reporting + */ + private int countExported; + private ProgressListener progressListener; + private XmlSerializer gpx; + + protected static interface ProgressListener { + + void publishProgress(int countExported); + + } + + public void writeGPX(List<String> allGeocodesIn, Writer writer, final ProgressListener progressListener) throws IOException { + // create a copy of the geocode list, as we need to modify it, but it might be immutable + final ArrayList<String> allGeocodes = new ArrayList<String>(allGeocodesIn); + + this.progressListener = progressListener; + gpx = new KXmlSerializer(); + gpx.setOutput(writer); + + gpx.startDocument("UTF-8", true); + gpx.setPrefix("", PREFIX_GPX); + gpx.setPrefix("xsi", PREFIX_XSI); + gpx.setPrefix("groundspeak", PREFIX_GROUNDSPEAK); + gpx.startTag(PREFIX_GPX, "gpx"); + gpx.attribute("", "version", "1.0"); + gpx.attribute("", "creator", "c:geo - http://www.cgeo.org/"); + gpx.attribute(PREFIX_XSI, "schemaLocation", + PREFIX_GPX + " http://www.topografix.com/GPX/1/0/gpx.xsd " + + PREFIX_GROUNDSPEAK + " http://www.groundspeak.com/cache/1/0/1/cache.xsd"); + + // Split the overall set of geocodes into small chunks. That is a compromise between memory efficiency (because + // we don't load all caches fully into memory) and speed (because we don't query each cache separately). + while (!allGeocodes.isEmpty()) { + final List<String> batch = allGeocodes.subList(0, Math.min(CACHES_PER_BATCH, allGeocodes.size())); + exportBatch(gpx, batch); + batch.clear(); + } + + gpx.endTag(PREFIX_GPX, "gpx"); + gpx.endDocument(); + } + + private void exportBatch(final XmlSerializer gpx, Collection<String> geocodesOfBatch) throws IOException { + final Set<Geocache> caches = cgData.loadCaches(geocodesOfBatch, LoadFlags.LOAD_ALL_DB_ONLY); + for (final Geocache cache : caches) { + gpx.startTag(PREFIX_GPX, "wpt"); + gpx.attribute("", "lat", Double.toString(cache.getCoords().getLatitude())); + gpx.attribute("", "lon", Double.toString(cache.getCoords().getLongitude())); + + final Date hiddenDate = cache.getHiddenDate(); + if (hiddenDate != null) { + XmlUtils.simpleText(gpx, PREFIX_GPX, "time", dateFormatZ.format(hiddenDate)); + } + + XmlUtils.multipleTexts(gpx, PREFIX_GPX, + "name", cache.getGeocode(), + "desc", cache.getName(), + "url", cache.getUrl(), + "urlname", cache.getName(), + "sym", cache.isFound() ? "Geocache Found" : "Geocache", + "type", "Geocache|" + cache.getType().pattern); + + gpx.startTag(PREFIX_GROUNDSPEAK, "cache"); + gpx.attribute("", "id", cache.getCacheId()); + gpx.attribute("", "available", !cache.isDisabled() ? "True" : "False"); + gpx.attribute("", "archives", cache.isArchived() ? "True" : "False"); + + XmlUtils.multipleTexts(gpx, PREFIX_GROUNDSPEAK, + "name", cache.getName(), + "placed_by", cache.getOwnerDisplayName(), + "owner", cache.getOwnerUserId(), + "type", cache.getType().pattern, + "container", cache.getSize().id, + "difficulty", Float.toString(cache.getDifficulty()), + "terrain", Float.toString(cache.getTerrain()), + "country", cache.getLocation(), + "state", "", + "encoded_hints", cache.getHint()); + + writeAttributes(cache); + + gpx.startTag(PREFIX_GROUNDSPEAK, "short_description"); + gpx.attribute("", "html", TextUtils.containsHtml(cache.getShortDescription()) ? "True" : "False"); + gpx.text(cache.getShortDescription()); + gpx.endTag(PREFIX_GROUNDSPEAK, "short_description"); + + gpx.startTag(PREFIX_GROUNDSPEAK, "long_description"); + gpx.attribute("", "html", TextUtils.containsHtml(cache.getDescription()) ? "True" : "False"); + gpx.text(cache.getDescription()); + gpx.endTag(PREFIX_GROUNDSPEAK, "long_description"); + + writeLogs(cache); + + gpx.endTag(PREFIX_GROUNDSPEAK, "cache"); + gpx.endTag(PREFIX_GPX, "wpt"); + + writeWaypoints(cache); + + countExported++; + if (progressListener != null) { + progressListener.publishProgress(countExported); + } + } + } + + private void writeWaypoints(final Geocache cache) throws IOException { + final List<Waypoint> waypoints = cache.getWaypoints(); + final List<Waypoint> ownWaypoints = new ArrayList<Waypoint>(waypoints.size()); + final List<Waypoint> originWaypoints = new ArrayList<Waypoint>(waypoints.size()); + for (final Waypoint wp : cache.getWaypoints()) { + if (wp.isUserDefined()) { + ownWaypoints.add(wp); + } else { + originWaypoints.add(wp); + } + } + int maxPrefix = 0; + for (final Waypoint wp : originWaypoints) { + final String prefix = wp.getPrefix(); + try { + final int numericPrefix = Integer.parseInt(prefix); + maxPrefix = Math.max(numericPrefix, maxPrefix); + } catch (final NumberFormatException ex) { + // ignore non numeric prefix, as it should be unique in the list of non-own waypoints already + } + writeCacheWaypoint(wp, prefix); + } + // Prefixes must be unique. There use numeric strings as prefixes in OWN waypoints + for (final Waypoint wp : ownWaypoints) { + maxPrefix++; + final String prefix = StringUtils.leftPad(String.valueOf(maxPrefix), 2, '0'); + writeCacheWaypoint(wp, prefix); + } + } + + /** + * Writes one waypoint entry for cache waypoint. + * + * @param cache + * The + * @param wp + * @param prefix + * @throws IOException + */ + private void writeCacheWaypoint(final Waypoint wp, final String prefix) throws IOException { + final Geopoint coords = wp.getCoords(); + // TODO: create some extension to GPX to include waypoint without coords + if (coords != null) { + gpx.startTag(PREFIX_GPX, "wpt"); + gpx.attribute("", "lat", Double.toString(coords.getLatitude())); + gpx.attribute("", "lon", Double.toString(coords.getLongitude())); + XmlUtils.multipleTexts(gpx, PREFIX_GPX, + "name", prefix + wp.getGeocode().substring(2), + "cmt", wp.getNote(), + "desc", wp.getName(), + "sym", wp.getWaypointType().toString(), //TODO: Correct identifier string + "type", "Waypoint|" + wp.getWaypointType().toString()); //TODO: Correct identifier string + gpx.endTag(PREFIX_GPX, "wpt"); + } + } + + private void writeLogs(final Geocache cache) throws IOException { + if (cache.getLogs().isEmpty()) { + return; + } + gpx.startTag(PREFIX_GROUNDSPEAK, "logs"); + + for (final LogEntry log : cache.getLogs()) { + gpx.startTag(PREFIX_GROUNDSPEAK, "log"); + gpx.attribute("", "id", Integer.toString(log.id)); + + XmlUtils.multipleTexts(gpx, PREFIX_GROUNDSPEAK, + "date", dateFormatZ.format(new Date(log.date)), + "type", log.type.type); + + gpx.startTag(PREFIX_GROUNDSPEAK, "finder"); + gpx.attribute("", "id", ""); + gpx.text(log.author); + gpx.endTag(PREFIX_GROUNDSPEAK, "finder"); + + gpx.startTag(PREFIX_GROUNDSPEAK, "text"); + gpx.attribute("", "encoded", "False"); + gpx.text(log.log); + gpx.endTag(PREFIX_GROUNDSPEAK, "text"); + + gpx.endTag(PREFIX_GROUNDSPEAK, "log"); + } + + gpx.endTag(PREFIX_GROUNDSPEAK, "logs"); + } + + private void writeAttributes(final Geocache cache) throws IOException { + if (cache.getAttributes().isEmpty()) { + return; + } + //TODO: Attribute conversion required: English verbose name, gpx-id + gpx.startTag(PREFIX_GROUNDSPEAK, "attributes"); + + for (final String attribute : cache.getAttributes()) { + final CacheAttribute attr = CacheAttribute.getByRawName(CacheAttribute.trimAttributeName(attribute)); + if (attr == null) { + continue; + } + final boolean enabled = CacheAttribute.isEnabled(attribute); + + gpx.startTag(PREFIX_GROUNDSPEAK, "attribute"); + gpx.attribute("", "id", Integer.toString(attr.gcid)); + gpx.attribute("", "inc", enabled ? "1" : "0"); + gpx.text(attr.getL10n(enabled)); + gpx.endTag(PREFIX_GROUNDSPEAK, "attribute"); + } + + gpx.endTag(PREFIX_GROUNDSPEAK, "attributes"); + } + +} |
