/*
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.ParserContext;
import slash.navigation.base.RouteCharacteristics;
import slash.navigation.common.NavigationPosition;
import slash.navigation.kml.binding22.AbstractContainerType;
import slash.navigation.kml.binding22.AbstractFeatureType;
import slash.navigation.kml.binding22.AbstractGeometryType;
import slash.navigation.kml.binding22.AbstractTimePrimitiveType;
import slash.navigation.kml.binding22.AbstractViewType;
import slash.navigation.kml.binding22.DocumentType;
import slash.navigation.kml.binding22.FolderType;
import slash.navigation.kml.binding22.KmlType;
import slash.navigation.kml.binding22.LineStringType;
import slash.navigation.kml.binding22.LineStyleType;
import slash.navigation.kml.binding22.LinkType;
import slash.navigation.kml.binding22.LookAtType;
import slash.navigation.kml.binding22.MultiGeometryType;
import slash.navigation.kml.binding22.NetworkLinkType;
import slash.navigation.kml.binding22.ObjectFactory;
import slash.navigation.kml.binding22.PlacemarkType;
import slash.navigation.kml.binding22.PointType;
import slash.navigation.kml.binding22.ScreenOverlayType;
import slash.navigation.kml.binding22.StyleType;
import slash.navigation.kml.binding22.TimeSpanType;
import slash.navigation.kml.binding22.TimeStampType;
import slash.navigation.kml.binding22.UnitsEnumType;
import slash.navigation.kml.binding22.Vec2Type;
import slash.navigation.kml.binding22gx.AbstractTourPrimitiveType;
import slash.navigation.kml.binding22gx.FlyToType;
import slash.navigation.kml.binding22gx.MultiTrackType;
import slash.navigation.kml.binding22gx.TourType;
import slash.navigation.kml.binding22gx.TrackType;
import slash.navigation.kml.bindingatom.Link;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.lang.Math.asin;
import static java.lang.Math.atan2;
import static java.lang.Math.cos;
import static java.lang.Math.sin;
import static java.lang.Math.toDegrees;
import static java.lang.Math.toRadians;
import static java.lang.String.valueOf;
import static java.util.Collections.singletonList;
import static slash.common.io.Transfer.isEmpty;
import static slash.common.io.Transfer.toDouble;
import static slash.common.io.Transfer.trim;
import static slash.common.type.HexadecimalNumber.decodeBytes;
import static slash.common.type.ISO8601.formatDate;
import static slash.navigation.base.RouteCharacteristics.Route;
import static slash.navigation.base.RouteCharacteristics.Track;
import static slash.navigation.base.RouteCharacteristics.Waypoints;
import static slash.navigation.common.Bearing.EARTH_RADIUS;
import static slash.navigation.common.NavigationConversion.formatPositionAsString;
import static slash.navigation.common.PositionParser.parseExtensionPositions;
import static slash.navigation.kml.KmlUtil.marshal22;
import static slash.navigation.kml.KmlUtil.unmarshal22;
import static slash.navigation.kml.binding22.UnitsEnumType.FRACTION;
import static slash.navigation.kml.binding22.UnitsEnumType.PIXELS;
/**
* Reads and writes Google Earth 5 (.kml) files.
*
* @author Christian Pesch
*/
public class Kml22Format extends KmlFormat {
private static final int METERS_BETWEEN_MARKS = 1000;
public String getName() {
return "Google Earth 5 (*" + getExtension() + ")";
}
public void read(InputStream source, ParserContext<KmlRoute> context) throws Exception {
KmlType kmlType = unmarshal22(source);
process(kmlType, context);
}
protected void process(KmlType kmlType, ParserContext<KmlRoute> context) throws IOException {
if (kmlType == null || kmlType.getAbstractFeatureGroup() == null)
return;
extractTracks(kmlType, context);
}
@SuppressWarnings({"UnusedDeclaration", "unchecked"})
private <T> List<JAXBElement<T>> find(List<JAXBElement<? extends AbstractFeatureType>> elements, String name, Class<T> resultClass) {
List<JAXBElement<T>> result = new ArrayList<>();
if(elements != null) {
for (JAXBElement<? extends AbstractFeatureType> element : elements) {
if (name.equals(element.getName().getLocalPart()))
result.add((JAXBElement<T>) element);
}
}
return result;
}
protected void extractTracks(KmlType kmlType, ParserContext<KmlRoute> context) throws IOException {
AbstractFeatureType feature = kmlType.getAbstractFeatureGroup().getValue();
if (feature instanceof AbstractContainerType) {
AbstractContainerType containerType = (AbstractContainerType) feature;
List<JAXBElement<? extends AbstractFeatureType>> features = null;
if (containerType instanceof FolderType)
features = ((FolderType) containerType).getAbstractFeatureGroup();
else if (containerType instanceof DocumentType)
features = ((DocumentType) containerType).getAbstractFeatureGroup();
extractTracks(trim(containerType.getName()), trim(containerType.getDescription()), features, context);
}
if (feature instanceof PlacemarkType) {
PlacemarkType placemarkType = (PlacemarkType) feature;
String placemarkName = asDescription(trim(placemarkType.getName()), trim(placemarkType.getDescription()));
List<KmlPosition> positions = extractPositionsFromGeometry(placemarkType.getAbstractGeometryGroup());
for (KmlPosition position : positions) {
enrichPosition(position, extractTime(placemarkType.getAbstractTimePrimitiveGroup()), placemarkName, placemarkType.getDescription(), context.getStartDate());
}
context.appendRoute(new KmlRoute(this, Waypoints, placemarkName, null, positions));
}
if (feature instanceof TourType) {
TourType tourType = (TourType) feature;
String tourName = asDescription(trim(tourType.getName()), trim(tourType.getDescription()));
List<KmlPosition> positions = extractPositionsFromTour(tourType.getPlaylist().getAbstractTourPrimitiveGroup());
for (KmlPosition position : positions) {
enrichPosition(position, extractTime(tourType.getAbstractTimePrimitiveGroup()), tourName, tourType.getDescription(), context.getStartDate());
}
context.appendRoute(new KmlRoute(this, Track, tourName, null, positions));
}
}
private void extractTracks(String name, String description, List<JAXBElement<? extends AbstractFeatureType>> features, ParserContext<KmlRoute> context) throws IOException {
List<JAXBElement<PlacemarkType>> placemarks = find(features, "Placemark", PlacemarkType.class);
extractWayPointsAndTracksFromPlacemarks(name, description, placemarks, context);
List<JAXBElement<NetworkLinkType>> networkLinks = find(features, "NetworkLink", NetworkLinkType.class);
extractWayPointsAndTracksFromNetworkLinks(networkLinks, context);
List<JAXBElement<FolderType>> folders = find(features, "Folder", FolderType.class);
for (JAXBElement<FolderType> folder : folders) {
FolderType folderTypeValue = folder.getValue();
String folderName = trim(folderTypeValue.getName());
// ignore speed and marks folders
if (folderName == null || (!folderName.equals(SPEED) && !folderName.equals(MARKS)))
extractTracks(concatPath(name, folderName), description, folderTypeValue.getAbstractFeatureGroup(), context);
}
List<JAXBElement<DocumentType>> documents = find(features, "Document", DocumentType.class);
for (JAXBElement<DocumentType> document : documents) {
DocumentType documentTypeValue = document.getValue();
String documentName = concatPath(name, documentTypeValue.getName());
extractTracks(documentName, description, documentTypeValue.getAbstractFeatureGroup(), context);
}
}
private CompactCalendar extractTime(JAXBElement<? extends AbstractTimePrimitiveType> timePrimitiveType) {
if (timePrimitiveType != null) {
AbstractTimePrimitiveType timePrimitiveTypeValue = timePrimitiveType.getValue();
String time = null;
if (timePrimitiveTypeValue instanceof TimeSpanType) {
time = ((TimeSpanType) timePrimitiveTypeValue).getBegin();
} else if (timePrimitiveTypeValue instanceof TimeStampType) {
time = ((TimeStampType) timePrimitiveTypeValue).getWhen();
}
return parseTime(time);
}
return null;
}
private void extractWayPointsAndTracksFromPlacemarks(String name, String description, List<JAXBElement<PlacemarkType>> placemarkTypes, ParserContext<KmlRoute> context) {
List<KmlPosition> waypoints = new ArrayList<>();
for (JAXBElement<PlacemarkType> placemarkType : placemarkTypes) {
PlacemarkType placemarkTypeValue = placemarkType.getValue();
String placemarkName = asDescription(trim(placemarkTypeValue.getName()), trim(placemarkTypeValue.getDescription()));
JAXBElement<? extends AbstractGeometryType> abstractGeometryGroup = placemarkTypeValue.getAbstractGeometryGroup();
if (abstractGeometryGroup == null)
continue;
List<KmlPosition> positions = extractPositionsFromGeometry(abstractGeometryGroup);
if (positions.size() == 1) {
// all placemarks with one position form one waypoint route
KmlPosition wayPoint = positions.get(0);
enrichPosition(wayPoint, extractTime(placemarkTypeValue.getAbstractTimePrimitiveGroup()), placemarkName, placemarkTypeValue.getDescription(), context.getStartDate());
waypoints.add(wayPoint);
} else {
// each placemark with more than one position is one track
String routeName = concatPath(name, asName(placemarkName));
List<String> routeDescription = asDescription(placemarkTypeValue.getDescription() != null ? placemarkTypeValue.getDescription() : description);
RouteCharacteristics characteristics = parseCharacteristics(routeName, Track);
context.appendRoute(new KmlRoute(this, characteristics, routeName, routeDescription, positions));
}
}
if (waypoints.size() > 0) {
RouteCharacteristics characteristics = parseCharacteristics(name, Waypoints);
context.prependRoute(new KmlRoute(this, characteristics, name, asDescription(description), waypoints));
}
}
private void extractWayPointsAndTracksFromNetworkLinks(List<JAXBElement<NetworkLinkType>> networkLinkTypes, ParserContext<KmlRoute> context) throws IOException {
for (JAXBElement<NetworkLinkType> networkLinkType : networkLinkTypes) {
Link link = networkLinkType.getValue().getLink();
if (link != null) {
String url = link.getHref();
context.parse(url);
}
List<JAXBElement<?>> rest = networkLinkType.getValue().getRest();
for (JAXBElement<?> r : rest) {
Object rValue = r.getValue();
if (rValue instanceof LinkType) {
LinkType linkType = (LinkType) rValue;
String url = linkType.getHref();
context.parse(url);
}
}
}
}
private List<KmlPosition> asExtendedKmlPositions(List<String> strings) {
List<KmlPosition> result = new ArrayList<>();
for (String string : strings) {
for (NavigationPosition position : parseExtensionPositions(string)) {
result.add(asKmlPosition(position));
}
}
return result;
}
private List<KmlPosition> extractPositions(TrackType trackType) {
List<String> coords = trackType.getCoord();
List<String> whens = trackType.getWhen();
List<KmlPosition> result = asExtendedKmlPositions(coords);
for (int i = 0; i < whens.size(); i++) {
String when = whens.get(i);
if (when != null) {
CompactCalendar calendar = parseTime(when);
if (calendar != null && i < result.size())
result.get(i).setTime(calendar);
}
}
return result;
}
private List<KmlPosition> extractPositionsFromGeometry(JAXBElement<? extends AbstractGeometryType> geometryType) {
List<KmlPosition> positions = new ArrayList<>();
AbstractGeometryType geometryTypeValue = geometryType.getValue();
if (geometryTypeValue instanceof PointType) {
PointType point = (PointType) geometryTypeValue;
positions.addAll(asKmlPositions(point.getCoordinates()));
}
if (geometryTypeValue instanceof LineStringType) {
LineStringType lineString = (LineStringType) geometryTypeValue;
positions.addAll(asKmlPositions(lineString.getCoordinates()));
}
if (geometryTypeValue instanceof MultiGeometryType) {
MultiGeometryType multiGeometryType = (MultiGeometryType) geometryTypeValue;
List<JAXBElement<? extends AbstractGeometryType>> geometryTypes = multiGeometryType.getAbstractGeometryGroup();
for (JAXBElement<? extends AbstractGeometryType> geometryType2 : geometryTypes) {
positions.addAll(extractPositionsFromGeometry(geometryType2));
}
}
if (geometryTypeValue instanceof MultiTrackType) {
MultiTrackType multiTrackType = (MultiTrackType) geometryTypeValue;
List<TrackType> tracks = multiTrackType.getTrack();
for (TrackType track : tracks) {
positions.addAll(extractPositions(track));
}
}
if (geometryTypeValue instanceof TrackType) {
TrackType trackType = (TrackType) geometryTypeValue;
positions.addAll(extractPositions(trackType));
}
return positions;
}
private List<KmlPosition> extractPositionsFromTour(List<JAXBElement<? extends AbstractTourPrimitiveType>> tourPrimitives) {
List<KmlPosition> positions = new ArrayList<>();
for (JAXBElement<? extends AbstractTourPrimitiveType> tourPrimitive : tourPrimitives) {
AbstractTourPrimitiveType tourPrimitiveValue = tourPrimitive.getValue();
if (tourPrimitiveValue instanceof FlyToType) {
FlyToType flyToType = (FlyToType) tourPrimitiveValue;
AbstractViewType abstractViewGroupValue = flyToType.getAbstractViewGroup().getValue();
if (abstractViewGroupValue instanceof LookAtType) {
LookAtType lookAtType = (LookAtType) abstractViewGroupValue;
Double elevation = isEmpty(lookAtType.getAltitude()) ? null : lookAtType.getAltitude();
KmlPosition position = new KmlPosition(lookAtType.getLongitude(), lookAtType.getLatitude(),
elevation, null, null, null);
position.setHeading(lookAtType.getHeading());
positions.add(position);
}
}
}
return positions;
}
private List<PlacemarkType> createWayPoints(KmlRoute route, int startIndex, int endIndex) {
ObjectFactory objectFactory = new ObjectFactory();
List<PlacemarkType> result = new ArrayList<>();
List<KmlPosition> positions = route.getPositions();
for (int i = startIndex; i < endIndex; i++) {
KmlPosition position = positions.get(i);
PlacemarkType placemarkType = objectFactory.createPlacemarkType();
result.add(placemarkType);
placemarkType.setName(asName(isWriteName() ? position.getDescription() : null));
placemarkType.setDescription(asDesc(isWriteDesc() ? position.getDescription() : null));
if (position.hasTime()) {
TimeStampType timeStampType = objectFactory.createTimeStampType();
timeStampType.setWhen(formatDate(position.getTime()));
placemarkType.setAbstractTimePrimitiveGroup(objectFactory.createTimeStamp(timeStampType));
}
PointType pointType = objectFactory.createPointType();
placemarkType.setAbstractGeometryGroup(objectFactory.createPoint(pointType));
pointType.getCoordinates().add(createCoordinates(position, false));
}
return result;
}
private PlacemarkType createRoute(KmlRoute route) {
ObjectFactory objectFactory = new ObjectFactory();
PlacemarkType placemarkType = objectFactory.createPlacemarkType();
placemarkType.setName(ROUTE);
placemarkType.setDescription(asDescription(route.getDescription()));
placemarkType.setStyleUrl("#" + ROUTE_LINE_STYLE);
MultiGeometryType multiGeometryType = objectFactory.createMultiGeometryType();
placemarkType.setAbstractGeometryGroup(objectFactory.createMultiGeometry(multiGeometryType));
LineStringType lineStringType = objectFactory.createLineStringType();
multiGeometryType.getAbstractGeometryGroup().add(objectFactory.createLineString(lineStringType));
List<String> coordinates = lineStringType.getCoordinates();
for (KmlPosition position : route.getPositions()) {
coordinates.add(createCoordinates(position, false));
}
return placemarkType;
}
private PlacemarkType createTrack(KmlRoute route, int startIndex, int endIndex) {
ObjectFactory objectFactory = new ObjectFactory();
PlacemarkType placemarkType = objectFactory.createPlacemarkType();
placemarkType.setName(TRACK);
placemarkType.setDescription(asDescription(route.getDescription()));
placemarkType.setStyleUrl("#" + TRACK_LINE_STYLE);
// create gx:Track if there are at least two positions with a time stamp
if (containTime(route)) {
slash.navigation.kml.binding22gx.ObjectFactory gxObjectFactory = new slash.navigation.kml.binding22gx.ObjectFactory();
TrackType trackType = gxObjectFactory.createTrackType();
List<KmlPosition> positions = route.getPositions();
for (int i = startIndex; i < endIndex; i++) {
KmlPosition position = positions.get(i);
String time = position.hasTime() ? formatDate(position.getTime()) : "";
trackType.getWhen().add(time);
}
for (int i = startIndex; i < endIndex; i++) {
KmlPosition position = positions.get(i);
trackType.getCoord().add(createCoordinates(position, true));
}
placemarkType.setAbstractGeometryGroup(gxObjectFactory.createTrack(trackType));
} else {
LineStringType lineStringType = objectFactory.createLineStringType();
placemarkType.setAbstractGeometryGroup(objectFactory.createLineString(lineStringType));
List<String> coordinates = lineStringType.getCoordinates();
List<KmlPosition> positions = route.getPositions();
for (int i = startIndex; i < endIndex; i++) {
KmlPosition position = positions.get(i);
coordinates.add(createCoordinates(position, false));
}
}
return placemarkType;
}
private boolean containTime(KmlRoute route) {
int foundTime = 0;
for (NavigationPosition position : route.getPositions()) {
if (position.hasTime())
foundTime++;
}
return foundTime > 1;
}
private boolean isWriteMarks() {
return preferences.getBoolean("writeMarks", true);
}
private boolean isWriteSpeed() {
return preferences.getBoolean("writeSpeed", true);
}
private boolean hasCharacteristics(List<KmlRoute> routes, RouteCharacteristics characteristics) {
for(KmlRoute route : routes)
if(route.getCharacteristics().equals(characteristics))
return true;
return false;
}
private static final String[] SPEED_COLORS = {
"FF00ffff",
"FF008080",
"FF00ff00",
"FF008000",
"FFffff00",
"FF808000",
"FFff0000",
"FF800000",
"FFff00ff",
"FF800080",
"FF0000ff",
"FF000080",
"FF194c80",
"FF000000"};
private static final String SPEEDBAR_URL = "http://www.routeconverter.com/images/speedbar.png";
private float getSpeedLineWidth() {
return preferences.getFloat("speedLineWidth", 5.0f);
}
private int getSpeedScale() {
return preferences.getInt("speedScale", 10);
}
private int getSpeedClass(double speed) {
int speedClass = (int) speed / getSpeedScale();
return speedClass < SPEED_COLORS.length ? speedClass >= 0 ? speedClass : 0 : SPEED_COLORS.length - 1;
}
private String getSpeedColor(int speedClass) {
return "speedColor_" + valueOf(speedClass);
}
private String getSpeedDescription(int speedClass) {
if (speedClass == 0)
return "< " + valueOf(getSpeedScale()) + " Km/h";
else if (speedClass <= SPEED_COLORS.length)
return valueOf(speedClass * getSpeedScale()) + " - " + valueOf((speedClass + 1) * getSpeedScale()) + " Km/h";
return "> " + valueOf(speedClass * getSpeedScale()) + " Km/h";
}
private List<StyleType> createSpeedTrackColors(float width) {
List<StyleType> styleTypeList = new ArrayList<>();
for (int i = 0; i < SPEED_COLORS.length; i++) {
String styleName = getSpeedColor(i);
StyleType styleType = createLineStyle(styleName, width, decodeBytes(SPEED_COLORS[i]));
styleTypeList.add(styleType);
}
return styleTypeList;
}
private ScreenOverlayType createScreenOverlayImage(String name, String url, Vec2Type overlayXY,
Vec2Type screenXY, Vec2Type size) {
ObjectFactory objectFactory = new ObjectFactory();
ScreenOverlayType screenOverlayType = objectFactory.createScreenOverlayType();
screenOverlayType.setName(name);
screenOverlayType.setOverlayXY(overlayXY);
screenOverlayType.setScreenXY(screenXY);
screenOverlayType.setSize(size);
LinkType icon = objectFactory.createLinkType();
icon.setHref(url);
screenOverlayType.setIcon(icon);
return screenOverlayType;
}
private Vec2Type createVec2Type(double x, double y, UnitsEnumType unitX, UnitsEnumType unitY) {
ObjectFactory objectFactory = new ObjectFactory();
Vec2Type vec2Type = objectFactory.createVec2Type();
vec2Type.setX(x);
vec2Type.setY(y);
vec2Type.setXunits(unitX);
vec2Type.setYunits(unitY);
return vec2Type;
}
private FolderType createSpeed(KmlRoute route, int startIndex, int endIndex) {
ObjectFactory objectFactory = new ObjectFactory();
FolderType folderType = objectFactory.createFolderType();
folderType.setName(SPEED);
folderType.setVisibility(FALSE);
folderType.setOpen(FALSE);
folderType.getAbstractFeatureGroup().add(objectFactory.createScreenOverlay(createSpeedbar()));
int segmentIndex = 0;
List<String> coordinates = new ArrayList<>();
Integer previousSpeedClass = null;
Double previousSpeed = null;
KmlPosition previous = null;
List<KmlPosition> positions = route.getPositions();
// since the speed of a position is the average speed of the previous segment
for (int i = startIndex; i < endIndex; i++) {
KmlPosition position = positions.get(i);
Double speed = null;
if(position.hasSpeed())
speed = position.getSpeed();
else if (previous != null)
speed = previous.calculateSpeed(position);
if (speed == null)
speed = previousSpeed;
if (speed == null)
continue;
coordinates.add(createCoordinates(position, false));
int speedClass = getSpeedClass(speed);
if (previousSpeedClass != null && previousSpeedClass != speedClass) {
PlacemarkType placemarkType = createSpeedSegment(++segmentIndex, previousSpeedClass, coordinates);
folderType.getAbstractFeatureGroup().add(objectFactory.createPlacemark(placemarkType));
coordinates.clear();
coordinates.add(createCoordinates(position, false));
}
previousSpeedClass = speedClass;
previousSpeed = speed;
previous = position;
}
return segmentIndex > 0 ? folderType : null;
}
private PlacemarkType createSpeedSegment(int currentSegment, int speedClass, List<String> coordinates) {
ObjectFactory objectFactory = new ObjectFactory();
PlacemarkType placemarkType = objectFactory.createPlacemarkType();
placemarkType.setName("Segment " + currentSegment);
placemarkType.setDescription(getSpeedDescription(speedClass));
placemarkType.setStyleUrl('#' + getSpeedColor(speedClass));
placemarkType.setVisibility(FALSE);
LineStringType lineStringType = objectFactory.createLineStringType();
lineStringType.getCoordinates().addAll(coordinates);
placemarkType.setAbstractGeometryGroup(objectFactory.createLineString(lineStringType));
return placemarkType;
}
private ScreenOverlayType createSpeedbar() {
ScreenOverlayType speedbar = createScreenOverlayImage("Speedbar",
SPEEDBAR_URL,
createVec2Type(0.0, 0.01, FRACTION, FRACTION),
createVec2Type(0.0, 0.01, FRACTION, FRACTION),
createVec2Type(250, 0, PIXELS, PIXELS));
speedbar.setVisibility(FALSE);
return speedbar;
}
private FolderType createMarks(KmlRoute route, int startIndex, int endIndex) {
ObjectFactory objectFactory = new ObjectFactory();
FolderType marks = objectFactory.createFolderType();
marks.setName(MARKS);
marks.setVisibility(FALSE);
marks.setOpen(FALSE);
double currentDistance = 0, previousDistance = 0;
int currentKiloMeter = 1;
List<KmlPosition> positions = route.getPositions();
for (int i = startIndex + 1; i < endIndex; i++) {
KmlPosition previousPosition = positions.get(i - 1);
KmlPosition currentPosition = positions.get(i);
Double distance = currentPosition.calculateDistance(previousPosition);
if (isEmpty(distance))
continue;
currentDistance += distance;
if (currentDistance >= METERS_BETWEEN_MARKS) {
// calculate the point at the kilometer mark that's between the current position and the previous one.
// it is possible, that there's more than one point to create
// see: http://www.movable-type.co.uk/scripts/latlong.html and http://williams.best.vwh.net/avform.htm#LL
KmlPosition intermediate = new KmlPosition(previousPosition.getLongitude(), previousPosition.getLatitude(), null, null, null, null);
// remaining distance between the last point and the mark
double remainingDistance = METERS_BETWEEN_MARKS - (previousDistance % METERS_BETWEEN_MARKS);
do {
double angle = toRadians(intermediate.calculateAngle(currentPosition));
double latitude1 = toRadians(intermediate.getLatitude());
double longitude1 = toRadians(intermediate.getLongitude());
double latitude2 = asin(sin(latitude1) * cos(remainingDistance / EARTH_RADIUS) +
cos(latitude1) * sin(remainingDistance / EARTH_RADIUS) * cos(angle));
double longitude2 = longitude1 +
atan2(sin(angle) * sin(remainingDistance / EARTH_RADIUS) * cos(latitude1),
cos(remainingDistance / EARTH_RADIUS) - sin(latitude1) * sin(latitude2));
intermediate.setLatitude(toDegrees(latitude2));
intermediate.setLongitude(toDegrees(longitude2));
PlacemarkType placeMark = createMark(currentKiloMeter++, intermediate.getLongitude(), intermediate.getLatitude());
marks.getAbstractFeatureGroup().add(objectFactory.createPlacemark(placeMark));
remainingDistance = METERS_BETWEEN_MARKS;
} while (toDouble(intermediate.calculateDistance(currentPosition)) > METERS_BETWEEN_MARKS);
currentDistance = currentDistance % METERS_BETWEEN_MARKS;
}
previousDistance = currentDistance;
}
return marks;
}
private PlacemarkType createMark(int kiloMeter, double longitude, double latitude) {
ObjectFactory objectFactory = new ObjectFactory();
PlacemarkType placeMark = objectFactory.createPlacemarkType();
placeMark.setName(kiloMeter + ". Km");
placeMark.setVisibility(FALSE);
PointType point = objectFactory.createPointType();
point.getCoordinates().add(formatPositionAsString(longitude) + "," + formatPositionAsString(latitude) + ",0");
placeMark.setAbstractGeometryGroup(objectFactory.createPoint(point));
return placeMark;
}
private StyleType createLineStyle(String styleName, double width, byte[] color) {
ObjectFactory objectFactory = new ObjectFactory();
StyleType styleType = objectFactory.createStyleType();
styleType.setId(styleName);
LineStyleType lineStyleType = objectFactory.createLineStyleType();
styleType.setLineStyle(lineStyleType);
lineStyleType.setColor(color);
lineStyleType.setWidth(width);
return styleType;
}
protected KmlType createKmlType(KmlRoute route, int startIndex, int endIndex) {
ObjectFactory objectFactory = new ObjectFactory();
KmlType kmlType = objectFactory.createKmlType();
DocumentType documentType = objectFactory.createDocumentType();
kmlType.setAbstractFeatureGroup(objectFactory.createDocument(documentType));
documentType.setName(asRouteName(route.getName()));
documentType.setDescription(asDescription(route.getDescription()));
documentType.setOpen(TRUE);
if (hasCharacteristics(singletonList(route), Route))
documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(createLineStyle(ROUTE_LINE_STYLE, getLineWidth(), getRouteLineColor())));
if (hasCharacteristics(singletonList(route), Track)) {
documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(createLineStyle(TRACK_LINE_STYLE, getLineWidth(), getTrackLineColor())));
if (isWriteSpeed())
for (StyleType style : createSpeedTrackColors(getSpeedLineWidth()))
documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(style));
}
List<PlacemarkType> wayPoints = createWayPoints(route, startIndex, endIndex);
for (PlacemarkType wayPoint : wayPoints)
documentType.getAbstractFeatureGroup().add(objectFactory.createPlacemark(wayPoint));
PlacemarkType track = createTrack(route, startIndex, endIndex);
documentType.getAbstractFeatureGroup().add(objectFactory.createPlacemark(track));
if (hasCharacteristics(singletonList(route), Track)) {
FolderType speed = createSpeed(route, startIndex, endIndex);
if (speed != null)
documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(speed));
}
if (!route.getCharacteristics().equals(Waypoints) && isWriteMarks()) {
FolderType marks = createMarks(route, startIndex, endIndex);
documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(marks));
}
return kmlType;
}
private KmlType createKmlType(List<KmlRoute> routes) {
ObjectFactory objectFactory = new ObjectFactory();
KmlType kmlType = objectFactory.createKmlType();
DocumentType documentType = objectFactory.createDocumentType();
kmlType.setAbstractFeatureGroup(objectFactory.createDocument(documentType));
/* might make sense for Waypoint lists with one position lists in the file
if(routes.size() == 1) {
KmlRoute route = routes.get(0);
documentType.setName(createDocumentName(route));
documentType.setDescription(asDescription(route.getDescription()));
}
*/
documentType.setOpen(TRUE);
if (hasCharacteristics(routes, Route))
documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(createLineStyle(ROUTE_LINE_STYLE, getLineWidth(), getRouteLineColor())));
if (hasCharacteristics(routes, Track)) {
documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(createLineStyle(TRACK_LINE_STYLE, getLineWidth(), getTrackLineColor())));
if (isWriteSpeed())
for (StyleType style : createSpeedTrackColors(getSpeedLineWidth()))
documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(style));
}
for (KmlRoute route : routes) {
switch (route.getCharacteristics()) {
case Waypoints:
FolderType wayPointsFolder = objectFactory.createFolderType();
wayPointsFolder.setName(createPlacemarkName(WAYPOINTS, route));
wayPointsFolder.setDescription(asDescription(route.getDescription()));
documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(wayPointsFolder));
List<PlacemarkType> wayPoints = createWayPoints(route, 0, route.getPositionCount());
for (PlacemarkType wayPoint : wayPoints)
wayPointsFolder.getAbstractFeatureGroup().add(objectFactory.createPlacemark(wayPoint));
break;
case Route:
FolderType routeFolder = objectFactory.createFolderType();
routeFolder.setName(createPlacemarkName(ROUTE, route));
documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(routeFolder));
PlacemarkType routePlacemarks = createRoute(route);
routeFolder.getAbstractFeatureGroup().add(objectFactory.createPlacemark(routePlacemarks));
if (isWriteMarks())
routeFolder.getAbstractFeatureGroup().add(objectFactory.createFolder(createMarks(route, 0, route.getPositionCount())));
break;
case Track:
FolderType trackFolder = objectFactory.createFolderType();
trackFolder.setName(createPlacemarkName(TRACK, route));
documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(trackFolder));
PlacemarkType track = createTrack(route, 0, route.getPositionCount());
trackFolder.getAbstractFeatureGroup().add(objectFactory.createPlacemark(track));
if (isWriteSpeed()) {
FolderType speed = createSpeed(route, 0, route.getPositionCount());
if (speed != null)
trackFolder.getAbstractFeatureGroup().add(objectFactory.createFolder(speed));
}
if (isWriteMarks())
trackFolder.getAbstractFeatureGroup().add(objectFactory.createFolder(createMarks(route, 0, route.getPositionCount())));
break;
default:
throw new IllegalArgumentException("Unknown RouteCharacteristics " + route.getCharacteristics());
}
}
return kmlType;
}
public void write(KmlRoute route, OutputStream target, int startIndex, int endIndex) {
try {
marshal22(createKmlType(route, startIndex, endIndex), target);
} catch (JAXBException e) {
throw new IllegalArgumentException(e);
}
}
public void write(List<KmlRoute> routes, OutputStream target) throws IOException {
try {
marshal22(createKmlType(routes), target);
} catch (JAXBException e) {
throw new IllegalArgumentException(e);
}
}
}