/* 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.binding20.*; 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.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.common.PositionParser.parsePosition; import static slash.navigation.common.PositionParser.parsePositions; import static slash.navigation.kml.KmlUtil.marshal20; import static slash.navigation.kml.KmlUtil.unmarshal20; /** * Reads and writes Google Earth 3 (.kml) files. * * @author Christian Pesch */ public class Kml20Format extends KmlFormat { public String getName() { return "Google Earth 3 (*" + getExtension() + ")"; } public void read(InputStream source, ParserContext<KmlRoute> context) throws Exception { Object o = unmarshal20(source); if (o instanceof Kml) { Kml kml = (Kml) o; extractTracks(kml.getDocument(), kml.getFolder(), context); } if (o instanceof Document) { Document document = (Document) o; extractTracks(document, null, context); } if (o instanceof Folder) { Folder folder = (Folder) o; extractTracks(null, folder, context); } } private void extractTracks(Document document, Folder folder, ParserContext<KmlRoute> context) throws IOException { List<Object> elements = null; if (document != null && document.getDocumentOrFolderOrGroundOverlay().size() > 0) elements = document.getDocumentOrFolderOrGroundOverlay(); if (folder != null && folder.getDocumentOrFolderOrGroundOverlay().size() > 0) elements = folder.getDocumentOrFolderOrGroundOverlay(); if (elements != null) extractTracks(extractName(elements), extractDescriptionList(elements), elements, context); } private JAXBElement findElement(List elements, String name) { for (Object element : elements) { if (element instanceof JAXBElement) { JAXBElement jaxbElement = (JAXBElement) element; if (name.equals(jaxbElement.getName().getLocalPart())) return jaxbElement; } } return null; } private String extractName(List<Object> elements) { JAXBElement name = findElement(elements, "name"); return name != null ? trim((String) name.getValue()) : null; } private String extractDescription(List<Object> elements) { JAXBElement name = findElement(elements, "description"); if (name == null) return null; String string = (String) name.getValue(); if (string == null) return null; string = string.replace(" ", " "); string = string.replace("©", "(c)"); return trim(string); } private String extractTime(TimeInstant timeInstant) { return timeInstant != null ? timeInstant.getTimePosition() : null; } private CompactCalendar extractTime(List<Object> elements) { JAXBElement element = findElement(elements, "TimePeriod"); if (element == null) return null; TimePeriod timePeriod = (TimePeriod) element.getValue(); String time = ""; if (timePeriod.getBegin() != null) time = extractTime(timePeriod.getBegin().getTimeInstant()); if (time == null && timePeriod.getEnd() != null) time = extractTime(timePeriod.getEnd().getTimeInstant()); return parseTime(time); } private List<String> extractDescriptionList(List<Object> elements) { JAXBElement name = findElement(elements, "description"); return name != null ? asDescription((String) name.getValue()) : null; } private List<Folder> findFolders(List<Object> elements) { List<Folder> folders = new ArrayList<>(); for (Object element : elements) { if (element instanceof Folder) { folders.add((Folder) element); } } return folders; } private List<Placemark> findPlacemarks(List<Object> elements) { List<Placemark> placemarks = new ArrayList<>(); for (Object element : elements) { if (element instanceof Placemark) { placemarks.add((Placemark) element); } } return placemarks; } private List<NetworkLink> findNetworkLinks(List<Object> elements) { List<NetworkLink> networkLinks = new ArrayList<>(); for (Object element : elements) { if (element instanceof NetworkLink) { networkLinks.add((NetworkLink) element); } } return networkLinks; } private void extractTracks(String name, List<String> description, List<Object> elements, ParserContext<KmlRoute> context) throws IOException { List<Placemark> placemarks = findPlacemarks(elements); extractWayPointsAndTracksFromPlacemarks(name, description, placemarks, context); List<NetworkLink> networkLinks = findNetworkLinks(elements); extractWayPointsAndTracksFromNetworkLinks(networkLinks, context); List<Folder> folders = findFolders(elements); for (Folder folder : folders) { List<Object> overlays = folder.getDocumentOrFolderOrGroundOverlay(); String folderName = concatPath(name, extractName(overlays)); extractTracks(folderName, description, overlays, context); } } private void extractWayPointsAndTracksFromPlacemarks(String name, List<String> description, List<Placemark> placemarks, ParserContext<KmlRoute> context) { List<KmlPosition> waypoints = new ArrayList<>(); for (Placemark placemark : placemarks) { String placemarkName = asDescription(extractName(placemark.getDescriptionOrNameOrSnippet()), extractDescription(placemark.getDescriptionOrNameOrSnippet())); List<KmlPosition> positions = extractPositions(placemark.getDescriptionOrNameOrSnippet()); if (positions.size() == 1) { // all placemarks with one position form one waypoint route KmlPosition wayPoint = positions.get(0); enrichPosition(wayPoint, extractTime(placemark.getDescriptionOrNameOrSnippet()), placemarkName, extractDescription(placemark.getDescriptionOrNameOrSnippet()), 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 = extractDescriptionList(placemark.getDescriptionOrNameOrSnippet()); if (routeDescription == null) routeDescription = 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, description, waypoints)); } } private void extractWayPointsAndTracksFromNetworkLinks(List<NetworkLink> networkLinks, ParserContext<KmlRoute> context) throws IOException { for (NetworkLink networkLink : networkLinks) { String url = networkLink.getUrl().getHref(); context.parse(url); } } private List<KmlPosition> extractPositions(LineString lineString) { List<KmlPosition> result = new ArrayList<>(); for (NavigationPosition position : parsePositions(lineString.getCoordinates())) { result.add(asKmlPosition(position)); } return result; } private List<KmlPosition> extractPositions(List<Object> elements) { List<KmlPosition> result = new ArrayList<>(); for (Object element : elements) { if (element instanceof Point) { Point point = (Point) element; result.add(asKmlPosition(parsePosition(point.getCoordinates(), null))); } if (element instanceof LineString) { LineString lineString = (LineString) element; result.addAll(extractPositions(lineString)); } if (element instanceof MultiGeometry) { MultiGeometry multiGeometry = (MultiGeometry) element; result.addAll(extractPositions(multiGeometry.getExtrudeOrTessellateOrAltitudeMode())); } if (element instanceof GeometryCollection) { GeometryCollection geometryCollection = (GeometryCollection) element; for (LineString lineString : geometryCollection.getLineString()) result.addAll(extractPositions(lineString)); } } return result; } private Folder createWayPoints(KmlRoute route) { ObjectFactory objectFactory = new ObjectFactory(); Folder folder = objectFactory.createFolder(); List<Object> folderList = folder.getDocumentOrFolderOrGroundOverlay(); folderList.add(objectFactory.createName(WAYPOINTS)); folderList.add(objectFactory.createDescription(asDescription(route.getDescription()))); for (KmlPosition position : route.getPositions()) { Placemark placemark = objectFactory.createPlacemark(); folderList.add(placemark); List<Object> placemarkList = placemark.getDescriptionOrNameOrSnippet(); placemarkList.add(objectFactory.createName(asName(isWriteName() ? position.getDescription() : null))); placemarkList.add(objectFactory.createDescription(asDesc(isWriteDesc() ? position.getDescription() : null))); placemarkList.add(objectFactory.createVisibility(Boolean.FALSE)); if (position.hasTime()) placemarkList.add(objectFactory.createTimePosition(formatDate(position.getTime()))); Point point = objectFactory.createPoint(); placemarkList.add(point); point.setCoordinates(createCoordinates(position, false)); } return folder; } private Placemark createRoute(KmlRoute route) { ObjectFactory objectFactory = new ObjectFactory(); Placemark placemark = objectFactory.createPlacemark(); List<Object> placemarkList = placemark.getDescriptionOrNameOrSnippet(); placemarkList.add(objectFactory.createName(createPlacemarkName(ROUTE, route))); placemarkList.add(objectFactory.createDescription(asDescription(route.getDescription()))); placemarkList.add(objectFactory.createStyleUrl("#" + ROUTE_LINE_STYLE)); MultiGeometry multiGeometry = objectFactory.createMultiGeometry(); placemarkList.add(multiGeometry); LineString lineString = objectFactory.createLineString(); multiGeometry.getExtrudeOrTessellateOrAltitudeMode().add(lineString); StringBuilder coordinates = new StringBuilder(); for (KmlPosition position : route.getPositions()) { coordinates.append(createCoordinates(position, false)).append(" "); } lineString.setCoordinates(coordinates.toString()); return placemark; } private Placemark createTrack(KmlRoute route) { ObjectFactory objectFactory = new ObjectFactory(); Placemark placemark = objectFactory.createPlacemark(); List<Object> placemarkList = placemark.getDescriptionOrNameOrSnippet(); placemarkList.add(objectFactory.createName(createPlacemarkName(TRACK, route))); placemarkList.add(objectFactory.createDescription(asDescription(route.getDescription()))); placemarkList.add(objectFactory.createStyleUrl("#" + TRACK_LINE_STYLE)); LineString lineString = objectFactory.createLineString(); placemarkList.add(lineString); StringBuilder coordinates = new StringBuilder(); for (KmlPosition position : route.getPositions()) { coordinates.append(createCoordinates(position, false)).append(" "); } lineString.setCoordinates(coordinates.toString()); return placemark; } private Style createLineStyle(String styleName, float width, byte[] color) { ObjectFactory objectFactory = new ObjectFactory(); Style style = objectFactory.createStyle(); style.setId(styleName); LineStyle lineStyle = objectFactory.createLineStyle(); style.setLineStyle(lineStyle); lineStyle.setColor(color); lineStyle.setWidth((int) width); return style; } private Kml createKml(KmlRoute route) { ObjectFactory objectFactory = new ObjectFactory(); Kml kml = objectFactory.createKml(); Folder root = objectFactory.createFolder(); kml.setFolder(root); List<Object> rootList = root.getDocumentOrFolderOrGroundOverlay(); rootList.add(objectFactory.createName(createDocumentName(route))); rootList.add(objectFactory.createDescription(asDescription(route.getDescription()))); rootList.add(objectFactory.createOpen(TRUE)); rootList.add(createLineStyle(ROUTE_LINE_STYLE, getLineWidth(), getRouteLineColor())); rootList.add(createLineStyle(TRACK_LINE_STYLE, getLineWidth(), getTrackLineColor())); rootList.add(createWayPoints(route)); rootList.add(createTrack(route)); return kml; } private Kml createKml(List<KmlRoute> routes) { ObjectFactory objectFactory = new ObjectFactory(); Kml kml = objectFactory.createKml(); Folder root = objectFactory.createFolder(); kml.setFolder(root); List<Object> rootList = root.getDocumentOrFolderOrGroundOverlay(); rootList.add(objectFactory.createOpen(TRUE)); rootList.add(createLineStyle(ROUTE_LINE_STYLE, getLineWidth(), getRouteLineColor())); rootList.add(createLineStyle(TRACK_LINE_STYLE, getLineWidth(), getTrackLineColor())); for (KmlRoute route : routes) { switch (route.getCharacteristics()) { case Waypoints: rootList.add(createWayPoints(route)); break; case Route: rootList.add(createRoute(route)); break; case Track: rootList.add(createTrack(route)); break; default: throw new IllegalArgumentException("Unknown RouteCharacteristics " + route.getCharacteristics()); } } return kml; } public void write(KmlRoute route, OutputStream target, int startIndex, int endIndex) throws IOException { try { marshal20(createKml(route), target); } catch (JAXBException e) { throw new IllegalArgumentException(e); } } public void write(List<KmlRoute> routes, OutputStream target) throws IOException { try { marshal20(createKml(routes), target); } catch (JAXBException e) { throw new IllegalArgumentException(e); } } }