package net.sourceforge.gpstools;
/* gpsdings
* Copyright (C) 2006 Moritz Ringler
* $Id: Ovl2gpx.java 441 2010-12-13 20:04:20Z ringler $
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import java.awt.geom.Rectangle2D;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.gpstools.ovl.AbstractOVLSymbol;
import net.sourceforge.gpstools.ovl.Coordinate;
import net.sourceforge.gpstools.ovl.OVL;
import net.sourceforge.gpstools.ovl.OVLSymbol;
import net.sourceforge.gpstools.ovl.OVLTrackSegment;
import net.sourceforge.gpstools.ovl.OVLVisitor;
import net.sourceforge.gpstools.ovl.OVLWaypoint;
import de.mospace.xml.SaxXMLWriter;
import org.xml.sax.SAXException;
public class Ovl2gpx {
private final static Pattern SYMBOL = Pattern
.compile("(?i)\\s*\\[Symbol (\\d+)]");
private final static int SYMBOL_INDEX = 1;
private final static Pattern COORDS = Pattern.compile("(x|y)koord(\\d*)");
private final static Pattern COORDS_VALUE = Pattern
.compile("[+-]?\\d*((\\d\\.?)|(\\.\\d))\\d*");
private final static Pattern KEYVALUE = Pattern
.compile("(.*?)\\s*=\\s*(.*)");
private final static int KEYVALUE_KEY = 1;
private final static int KEYVALUE_VALUE = 2;
private int linesRead = 0;
private String unparsed;
private OVL parseresult = new OVL();
private boolean eof = false;
public void transform(InputStream in, OutputStream out) throws IOException,
OVLVisitor.OVLVisitorException, ParseException {
BufferedReader read = new BufferedReader(new InputStreamReader(in));
/* read */
linesRead = 0;
OVLSymbol sym = readOVLSymbol(read);
while (!eof) {
if (sym != null) {
// add symbol to parse tree
sym.apply(parseresult);
}
// read next symbol
sym = readOVLSymbol(read);
}
/* write */
GPXWriter gxw = new GPXWriter(out);
gxw.write(parseresult);
gxw = null;
}
/**
* Parses a coordinate in decimal degrees.
*
* @return the double value of the coordinate or Double.NaN if value does
* not have the required format or is out of range
**/
private static double toCoords(String value, char axis) {
double d = Double.NaN;
double maxdeg = (axis == 'x') ? 180 : 90;
String val = value.trim();
if (COORDS_VALUE.matcher(val).matches()) {
try {
d = Double.parseDouble(value);
} catch (NumberFormatException cannothappen) {
throw new RuntimeException(cannothappen);
}
if (Math.abs(d) > maxdeg) {
d = Double.NaN;
}
}
return d;
}
private OVLSymbol readOVLSymbol(BufferedReader read) throws IOException,
ParseException {
// find a new symbol;
Matcher mSymbol = SYMBOL.matcher("");
int symbol = -1;
int symbolStart = -1;
while (symbol == -1) {
if (unparsed == null) {
unparsed = read.readLine();
linesRead++;
if (unparsed == null) {
eof = true;
return null;
}
}
mSymbol.reset(unparsed);
if (mSymbol.matches()) {
symbolStart = linesRead;
try {
symbol = Integer.parseInt(mSymbol.group(SYMBOL_INDEX));
} catch (NumberFormatException cannothappen) {
throw new RuntimeException(cannothappen);
}
}
unparsed = null;
}
// we're at the start of a new symbol
Map<String, Object> kv = new TreeMap<String, Object>();
Matcher mKeyval = KEYVALUE.matcher("");
Matcher mCoords = COORDS.matcher("");
unparsed = read.readLine();
eof = (unparsed == null);
linesRead++;
for (String key, value; !eof;) {
mKeyval.reset(unparsed);
if (!mKeyval.matches()) {
break;
}
key = mKeyval.group(KEYVALUE_KEY).toLowerCase().trim();
value = mKeyval.group(KEYVALUE_VALUE).toLowerCase().trim();
if (mCoords.reset(key).matches()) {
double d = toCoords(value, key.charAt(0));
if (d == Double.NaN) {
throw new ParseException("Illegal coordinate value "
+ value + " at line " + linesRead, linesRead);
}
kv.put(key, Double.valueOf(d));
} else {
kv.put(key, value);
}
unparsed = read.readLine();
eof = (unparsed == null);
linesRead++;
}
// we're at the end of a symbol;
return AbstractOVLSymbol.getInstance(symbol, symbolStart, kv);
}
private static class GPXWriter implements OVLVisitor {
private final SaxXMLWriter xw;
private final NumberFormat cf;
public GPXWriter(OutputStream out) throws OVLVisitorException {
try {
xw = new SaxXMLWriter(out, "UTF-8",
"http://www.topografix.com/GPX/1/1");
final DecimalFormat dcf = new DecimalFormat("00.000000");
final DecimalFormatSymbols sym = dcf.getDecimalFormatSymbols();
sym.setDecimalSeparator('.');
dcf.setDecimalFormatSymbols(sym);
cf = dcf;
} catch (UnsupportedEncodingException wonthappen) {
throw new Error(wonthappen);
} catch (SAXException ex) {
throw new OVLVisitorException(ex);
}
}
@Override
public void visitWaypoint(OVLWaypoint wpt) throws OVLVisitorException {
try {
// <wpt>
xw.attribute("lat", cf.format(wpt.coord.latitude()));
xw.attribute("lon", cf.format(wpt.coord.longitude()));
xw.startElement("wpt");
// <name>
xw.startElement("name");
xw.text("Symbol " + wpt.id());
xw.endElement("name");
// <desc>
xw.startElement("desc");
xw.text("Symbol " + wpt.id());
xw.endElement("desc");
// </wpt>
xw.endElement("wpt");
} catch (SAXException ex) {
throw new OVLVisitorException(ex);
}
}
@Override
public void visitTrackSegment(OVLTrackSegment trkseg)
throws OVLVisitorException {
try {
// <trkseg>
xw.startElement("trkseg");
for (Coordinate pt : trkseg.getCoords()) {
// <trkpt />
xw.attribute("lat", cf.format(pt.latitude()));
xw.attribute("lon", cf.format(pt.longitude()));
xw.emptyElement("trkpt");
}
xw.endElement("trkseg");
} catch (SAXException ex) {
throw new OVLVisitorException(ex);
}
}
public void write(OVL ovl) throws OVLVisitorException {
try {
// <gpx>
final String XSI = "http://www.w3.org/2001/XMLSchema-instance";
xw.declarePrefix("xsi", XSI);
xw.attribute("creator", "net.sourceforge.gpstools.Ovl2gpx");
xw.attribute("version", "1.1");
xw.attribute(
"schemaLocation",
"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd",
XSI);
xw.startElement("gpx");
// <metadata>
xw.startElement("metadata");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
// <time>
xw.startElement("time");
xw.text(df.format(new Date()));
xw.endElement("time");
// <bounds>
Rectangle2D.Double bounds = ovl.getBounds();
xw.attribute("minlat", String.valueOf(bounds.y));
xw.attribute("maxlat", String.valueOf(bounds.y + bounds.height));
xw.attribute("minlon", String.valueOf(bounds.x));
xw.attribute("maxlon", String.valueOf(bounds.x + bounds.width));
xw.emptyElement("bounds");
// </metadata>
xw.endElement("metadata");
for (OVLWaypoint wpt : ovl.getWaypoints()) {
// <wpt>
wpt.apply(this);
}
for (String name : ovl.getTrackNames()) {
// <trk>
xw.startElement("trk");
xw.startElement("name");
xw.text("Track " + name);
xw.endElement("name");
for (OVLTrackSegment trkseg : ovl.getTrack(name)) {
trkseg.apply(this);
}
xw.endElement("trk");
}
// </gpx>
xw.endElement("gpx");
xw.close();
} catch (SAXException ex) {
throw new OVLVisitorException(ex);
}
}
}
}