/* 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.kml.binding22beta.*; 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 slash.common.io.Transfer.trim; import static slash.common.type.ISO8601.formatDate; import static slash.navigation.base.RouteCharacteristics.Track; import static slash.navigation.base.RouteCharacteristics.Waypoints; import static slash.navigation.kml.KmlUtil.marshal22Beta; import static slash.navigation.kml.KmlUtil.unmarshal22Beta; /** * Reads and writes Google Earth 4.2 (.kml) files. * * @author Christian Pesch */ public class Kml22BetaFormat extends KmlFormat { public String getName() { return "Google Earth 4.2 (*" + getExtension() + ")"; } public void read(InputStream source, ParserContext<KmlRoute> context) throws Exception { KmlType kmlType = unmarshal22Beta(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; } private 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.getNameElement()), trim(containerType.getDescription()), features, context); } if (feature instanceof PlacemarkType) { PlacemarkType placemarkType = (PlacemarkType) feature; String placemarkName = asDescription(trim(placemarkType.getNameElement()), trim(placemarkType.getDescription())); List<KmlPosition> positions = extractPositions(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)); } } 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 = concatPath(name, folderTypeValue.getNameElement()); extractTracks(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.getNameElement()); extractTracks(documentName, description, documentTypeValue.getAbstractFeatureGroup(), context); } } 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.getNameElement()), trim(placemarkTypeValue.getDescription())); List<KmlPosition> positions = extractPositions(placemarkTypeValue.getAbstractGeometryGroup()); 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> extractPositions(JAXBElement<? extends AbstractGeometryType> geometryType) { List<KmlPosition> positions = new ArrayList<>(); if (geometryType == null) return positions; 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(extractPositions(geometryType2)); } } return positions; } private CompactCalendar extractTime(JAXBElement<? extends AbstractTimePrimitiveType> timePrimitiveType) { if (timePrimitiveType != null) { AbstractTimePrimitiveType timePrimitiveTypeValue = timePrimitiveType.getValue(); String time = ""; if (timePrimitiveTypeValue instanceof TimeSpanType) { time = ((TimeSpanType) timePrimitiveTypeValue).getBegin(); } else if (timePrimitiveTypeValue instanceof TimeStampType) { time = ((TimeStampType) timePrimitiveTypeValue).getWhen(); } return parseTime(time); } return null; } private FolderType createWayPoints(KmlRoute route) { ObjectFactory objectFactory = new ObjectFactory(); FolderType folderType = objectFactory.createFolderType(); folderType.setNameElement(WAYPOINTS); folderType.setDescription(asDescription(route.getDescription())); for (KmlPosition position : route.getPositions()) { PlacemarkType placemarkType = objectFactory.createPlacemarkType(); folderType.getAbstractFeatureGroup().add(objectFactory.createPlacemark(placemarkType)); placemarkType.setNameElement(asName(isWriteName() ? position.getDescription() : null)); placemarkType.setDescription(asDesc(isWriteDesc() ? position.getDescription() : null)); placemarkType.setVisibility(FALSE); 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 folderType; } private PlacemarkType createRoute(KmlRoute route) { ObjectFactory objectFactory = new ObjectFactory(); PlacemarkType placemarkType = objectFactory.createPlacemarkType(); placemarkType.setNameElement(createPlacemarkName(ROUTE, 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) { ObjectFactory objectFactory = new ObjectFactory(); PlacemarkType placemarkType = objectFactory.createPlacemarkType(); placemarkType.setNameElement(createPlacemarkName(TRACK, route)); placemarkType.setDescription(asDescription(route.getDescription())); placemarkType.setStyleUrl("#" + TRACK_LINE_STYLE); LineStringType lineStringType = objectFactory.createLineStringType(); placemarkType.setAbstractGeometryGroup(objectFactory.createLineString(lineStringType)); List<String> coordinates = lineStringType.getCoordinates(); for (KmlPosition position : route.getPositions()) { coordinates.add(createCoordinates(position, false)); } return placemarkType; } private StyleType createLineStyle(String styleName, float width, byte[] color) { ObjectFactory objectFactory = new ObjectFactory(); StyleType style = objectFactory.createStyleType(); style.setId(styleName); LineStyleType lineStyle = objectFactory.createLineStyleType(); style.setLineStyle(lineStyle); lineStyle.setColor(color); lineStyle.setWidth((double) width); return style; } private KmlType createKmlType(KmlRoute route) { ObjectFactory objectFactory = new ObjectFactory(); KmlType kmlType = objectFactory.createKmlType(); DocumentType documentType = objectFactory.createDocumentType(); kmlType.setAbstractFeatureGroup(objectFactory.createDocument(documentType)); documentType.setNameElement(createDocumentName(route)); documentType.setDescription(asDescription(route.getDescription())); documentType.setOpen(TRUE); documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(createLineStyle(ROUTE_LINE_STYLE, getLineWidth(), getRouteLineColor()))); documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(createLineStyle(TRACK_LINE_STYLE, getLineWidth(), getTrackLineColor()))); FolderType folderType = createWayPoints(route); documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(folderType)); PlacemarkType placemarkTrack = createTrack(route); documentType.getAbstractFeatureGroup().add(objectFactory.createPlacemark(placemarkTrack)); 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)); documentType.setOpen(TRUE); documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(createLineStyle(ROUTE_LINE_STYLE, getLineWidth(), getRouteLineColor()))); documentType.getAbstractStyleSelectorGroup().add(objectFactory.createStyle(createLineStyle(TRACK_LINE_STYLE, getLineWidth(), getTrackLineColor()))); for (KmlRoute route : routes) { switch (route.getCharacteristics()) { case Waypoints: FolderType folderType = createWayPoints(route); documentType.getAbstractFeatureGroup().add(objectFactory.createFolder(folderType)); documentType.setNameElement(createDocumentName(route)); documentType.setDescription(asDescription(route.getDescription())); break; case Route: PlacemarkType placemarkRoute = createRoute(route); documentType.getAbstractFeatureGroup().add(objectFactory.createPlacemark(placemarkRoute)); break; case Track: PlacemarkType placemarkTrack = createTrack(route); documentType.getAbstractFeatureGroup().add(objectFactory.createPlacemark(placemarkTrack)); break; default: throw new IllegalArgumentException("Unknown RouteCharacteristics " + route.getCharacteristics()); } } return kmlType; } public void write(KmlRoute route, OutputStream target, int startIndex, int endIndex) { try { marshal22Beta(createKmlType(route), target); } catch (JAXBException e) { throw new IllegalArgumentException(e); } } public void write(List<KmlRoute> routes, OutputStream target) throws IOException { try { marshal22Beta(createKmlType(routes), target); } catch (JAXBException e) { throw new IllegalArgumentException(e); } } }