/*
This file is part of RouteConverter.
RouteConverter 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 2 of the License, or
(at your option) any later version.
RouteConverter 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 RouteConverter; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Copyright (C) 2007 Christian Pesch. All Rights Reserved.
*/
package slash.navigation.kml;
import slash.common.type.CompactCalendar;
import slash.navigation.base.RouteCharacteristics;
import slash.navigation.common.NavigationPosition;
import java.text.ParseException;
import java.util.*;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.regex.Pattern.DOTALL;
import static slash.common.io.Transfer.parseDouble;
import static slash.common.io.Transfer.trim;
import static slash.common.type.CompactCalendar.*;
import static slash.common.type.HexadecimalNumber.decodeBytes;
import static slash.common.type.ISO8601.parseDate;
import static slash.navigation.base.RouteCharacteristics.*;
import static slash.navigation.common.NavigationConversion.formatElevationAsString;
import static slash.navigation.common.NavigationConversion.formatPositionAsString;
import static slash.navigation.common.PositionParser.parsePositions;
/**
* The base of all Google Earth formats.
*
* @author Christian Pesch
*/
public abstract class KmlFormat extends BaseKmlFormat {
static final Preferences preferences = Preferences.userNodeForPackage(KmlFormat.class);
static final String WAYPOINTS = "Waypoints";
static final String ROUTE = "Route";
static final String TRACK = "Track";
static final String SPEED = "Speed [Km/h]";
static final String MARKS = "Marks [Km]";
static final String ROUTE_LINE_STYLE = "routeStyle";
static final String TRACK_LINE_STYLE = "trackStyle";
public String getExtension() {
return ".kml";
}
public boolean isSupportsMultipleRoutes() {
return true;
}
public boolean isWritingRouteCharacteristics() {
return true;
}
@SuppressWarnings("unchecked")
public <P extends NavigationPosition> KmlRoute createRoute(RouteCharacteristics characteristics, String name, List<P> positions) {
return new KmlRoute(this, characteristics, name, null, (List<KmlPosition>) positions);
}
protected KmlPosition asKmlPosition(NavigationPosition position) {
return new KmlPosition(position.getLongitude(), position.getLatitude(), position.getElevation(), null, null, position.getDescription());
}
protected List<KmlPosition> asKmlPositions(List<String> strings) {
StringBuilder buffer = new StringBuilder();
for (String string : strings) {
buffer.append(string);
// to make sure the numbers are separated if they were already parsed by the XML parse
buffer.append(' ');
}
List<KmlPosition> result = new ArrayList<>();
for (NavigationPosition position : parsePositions(buffer.toString()))
result.add(asKmlPosition(position));
return result;
}
protected String createDocumentName(KmlRoute route) {
// some kind of crude workaround since the route carries the name of the
// plus and divided by a slash the route of the track
String name = asRouteName(route.getName());
if (name != null) {
StringTokenizer tokenizer = new StringTokenizer(name, "/");
if (tokenizer.hasMoreTokens())
name = tokenizer.nextToken();
}
return name;
}
protected String createPlacemarkName(String prefix, KmlRoute route) {
// some kind of crude workaround since the route carries the name of the
// plus and divided by a slash the route of the track
String name = route.getName();
if (name != null) {
StringTokenizer tokenizer = new StringTokenizer(name, "/");
while (tokenizer.hasMoreTokens())
name = tokenizer.nextToken();
if (!name.startsWith(prefix))
name = prefix + ": " + name;
} else
name = prefix;
return name;
}
protected String createCoordinates(KmlPosition position, boolean separateWithSpace) {
return formatPositionAsString(position.getLongitude()) + (separateWithSpace ? " " : ",") +
formatPositionAsString(position.getLatitude()) + (separateWithSpace ? " " : ",") +
formatElevationAsString(position.getElevation());
}
protected RouteCharacteristics parseCharacteristics(String nameToParse, RouteCharacteristics fallback) {
RouteCharacteristics result = fallback;
if (nameToParse != null) {
int slashIndex = nameToParse.lastIndexOf('/');
String folder = slashIndex != -1 ? nameToParse.substring(slashIndex + 1) : nameToParse;
if (folder.startsWith("Waypoint") || nameToParse.contains("Waypoint"))
result = Waypoints;
else if (folder.startsWith("Route") || nameToParse.contains("Route"))
result = Route;
else if (folder.startsWith("Track") || folder.startsWith("Path") || nameToParse.contains("Track"))
result = Track;
}
return result;
}
protected CompactCalendar parseTime(String time) {
if (time != null) {
Calendar calendar = parseDate(time);
if (calendar != null) {
calendar.setTimeZone(UTC);
return fromCalendar(calendar);
}
}
return null;
}
protected void enrichPosition(KmlPosition position, CompactCalendar time, String name, String description, CompactCalendar startDate) {
if (!position.hasTime() && time != null)
position.setTime(time);
if (!position.hasTime())
parseTime(position, description, startDate);
if (!position.hasTime())
parseTime(position, name, startDate);
if (position.getDescription() == null)
position.setDescription(name);
if (position.getElevation() == null) {
Double elevation = parseElevation(description);
if (elevation == null)
elevation = parseElevation(name);
position.setElevation(elevation);
}
if (position.getSpeed() == null) {
Double speed = parseSpeed(description);
position.setSpeed(speed);
}
}
private static final Pattern TAVELLOG_DATE_PATTERN = Pattern.compile(".*Time:.*(\\d{4}/\\d{2}/\\d{2} \\d{2}:\\d{2}:\\d{2}).*");
private static final String TAVELLOG_DATE ="yyyy/MM/dd HH:mm:ss";
private static final Pattern NAVIGON6310_TIME_AND_ELEVATION_PATTERN = Pattern.compile(".*(\\d{2}:\\d{2}:\\d{2}),([\\d\\.\\s]+)meter.*");
private static final String NAVIGON6310_TIME = "HH:mm:ss";
private static final Pattern BT747_TIME_AND_ELEVATION_PATTERN = Pattern.compile(".*TIME:.*>(\\d{2}-.+-\\d{2} \\d{2}:\\d{2}:\\d{2})<.*>([\\d\\.\\s]+)m<.*");
private static final String BT747_DATE = "dd-MMMMM-yy HH:mm:ss";
private static final Pattern QSTARTZ_DATE_AND_SPEED_PATTERN = Pattern.compile(".*Date:\\s*(\\d{4}/\\d{2}/\\d{2}).*Time:\\s*(\\d{2}:\\d{2}:\\d{2}).*Speed:\\s*([\\d\\.]+)\\s*.*", DOTALL);
void parseTime(NavigationPosition position, String description, CompactCalendar startDate) {
if (description != null) {
Matcher tavelLogMatcher = TAVELLOG_DATE_PATTERN.matcher(description);
if (tavelLogMatcher.matches()) {
String timeString = tavelLogMatcher.group(1);
try {
Date parsed = createDateFormat(TAVELLOG_DATE).parse(timeString);
position.setTime(fromDate(parsed));
} catch (ParseException e) {
// intentionally left empty;
}
}
Matcher navigonMatcher = NAVIGON6310_TIME_AND_ELEVATION_PATTERN.matcher(description);
if (navigonMatcher.matches()) {
String timeString = navigonMatcher.group(1);
try {
Date parsed = createDateFormat(NAVIGON6310_TIME).parse(timeString);
position.setTime(fromDate(parsed));
position.setStartDate(startDate);
} catch (ParseException e) {
// intentionally left empty;
}
}
Matcher bt747Matcher = BT747_TIME_AND_ELEVATION_PATTERN.matcher(description);
if (bt747Matcher.matches()) {
String timeString = bt747Matcher.group(1);
try {
Date parsed = createDateFormat(BT747_DATE).parse(timeString);
position.setTime(fromDate(parsed));
} catch (ParseException e) {
// intentionally left empty;
}
}
Matcher qstarzMatcher = QSTARTZ_DATE_AND_SPEED_PATTERN.matcher(description);
if (qstarzMatcher.matches()) {
String dateString = qstarzMatcher.group(1);
String timeString = qstarzMatcher.group(2);
try {
Date parsed = createDateFormat(TAVELLOG_DATE).parse(dateString + " " + timeString);
position.setTime(fromDate(parsed));
} catch (ParseException e) {
// intentionally left empty;
}
}
}
}
private static final Pattern TAVELLOG_SPEED_PATTERN = Pattern.compile(".*Speed:\\s*(\\d+\\.\\d+).*");
private static final Pattern WBT201LOG_SPEED_PATTERN = Pattern.compile(".*Speed=\\s*(\\d+)\\s*Km.*", DOTALL); // dot captures line terminators, too
Double parseSpeed(String description) {
if (description != null) {
Matcher tavelLogMatcher = TAVELLOG_SPEED_PATTERN.matcher(description);
if (tavelLogMatcher.matches()) {
return parseDouble(tavelLogMatcher.group(1));
}
Matcher wbt201LogMatcher = WBT201LOG_SPEED_PATTERN.matcher(description);
if (wbt201LogMatcher.matches()) {
return parseDouble(wbt201LogMatcher.group(1));
}
Matcher qstarzMatcher = QSTARTZ_DATE_AND_SPEED_PATTERN.matcher(description);
if (qstarzMatcher.matches()) {
return parseDouble(qstarzMatcher.group(3));
}
}
return null;
}
private static final Pattern TAVELLOG_ELEVATION_PATTERN = Pattern.compile(".*Altitude:\\s*(\\d+\\.\\d+).*");
Double parseElevation(String description) {
if (description != null) {
Matcher tavelLogMatcher = TAVELLOG_ELEVATION_PATTERN.matcher(description);
if (tavelLogMatcher.matches()) {
return parseDouble(tavelLogMatcher.group(1));
}
Matcher navigonMatcher = NAVIGON6310_TIME_AND_ELEVATION_PATTERN.matcher(description);
if (navigonMatcher.matches()) {
return parseDouble(navigonMatcher.group(2));
}
Matcher bt747Matcher = BT747_TIME_AND_ELEVATION_PATTERN.matcher(description);
if (bt747Matcher.matches()) {
return parseDouble(bt747Matcher.group(2));
}
}
return null;
}
protected String concatPath(String path, String fragment) {
path = trim(path);
fragment = trim(fragment);
String result = path != null ? path : "";
if (fragment != null)
result = result + "/" + fragment;
return result;
}
protected float getLineWidth() {
return preferences.getFloat("lineWidth", 3.0f);
}
protected byte[] getRouteLineColor() {
String color = preferences.get("routeLineColor", "7FFF0055");
return decodeBytes(color);
}
protected byte[] getTrackLineColor() {
String color = preferences.get("trackLineColor", "FFFF00FF");
return decodeBytes(color);
}
protected boolean isWriteName() {
return preferences.getBoolean("writeName", true);
}
protected boolean isWriteDesc() {
return preferences.getBoolean("writeDesc", true);
}
}