/* * Copyright (C) 2012 The Serval Project * * This file is part of the Serval Maps Data Manipulator Software * * Serval Maps Data Manipulator Software 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 source code 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 source code; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.servalproject.maps.dataman.builders; import java.io.PrintWriter; import java.util.ArrayList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.servalproject.maps.dataman.Utils; import org.servalproject.maps.dataman.types.GpsTraceElement; import org.servalproject.maps.dataman.types.KmlStyle; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * utility class to construct KML */ public class KmlBuilder { /* * private class level variables */ private DocumentBuilderFactory factory; private DocumentBuilder builder; private DOMImplementation domImpl; private Document xmlDoc; private Element rootElement; private Element rootDocument; private boolean hasStyle = false; /* * private class level constants */ private final String STYLE_URL = "gpsTraceStyle"; /** * instantiates a new KML builder * @throws BuildException if the underlying XML infrastructure cannot be used */ public KmlBuilder() throws BuildException { // create the xml document builder factory object factory = DocumentBuilderFactory.newInstance(); // set the factory to be namespace aware factory.setNamespaceAware(true); // create the xml document builder object and get the DOMImplementation object try { builder = factory.newDocumentBuilder(); } catch (javax.xml.parsers.ParserConfigurationException e) { throw new BuildException("unable to instantiate class", e); } domImpl = builder.getDOMImplementation(); // create a document with the appropriate default namespace xmlDoc = domImpl.createDocument("http://www.opengis.net/kml/2.2", "kml", null); // get the root element rootElement = this.xmlDoc.getDocumentElement(); // add atom namespace to the root element rootElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:atom", "http://www.w3.org/2005/Atom"); // add google earth extension namespace to the root element rootElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:gx", "http://www.google.com/kml/ext/2.2"); // add schema namespace to the root element rootElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); // add reference to the kml schema rootElement.setAttribute("xsi:schemaLocation", "http://www.opengis.net/kml/2.2 http://schemas.opengis.net/kml/2.2.0/ogckml22.xsd"); // add the root document rootDocument = xmlDoc.createElement("Document"); rootElement.appendChild(rootDocument); // add author information Element author = xmlDoc.createElement("atom:author"); Element authorName = xmlDoc.createElement("atom:name"); authorName.setTextContent("Serval Maps Data Manipulator"); author.appendChild(authorName); // add link information Element link = xmlDoc.createElement("atom:link"); link.setAttribute("href", "http://servalproject.org"); // add info to node tree rootDocument.appendChild(author); rootDocument.appendChild(link); } /** * set the KML style for the line that represents the GPS trace * * @param style an object representing the style parameters */ public boolean setStyle(KmlStyle style) { if(style != null && hasStyle == false) { // add the style information Element styleElem = xmlDoc.createElement("Style"); styleElem.setAttribute("id", STYLE_URL); Element lineStyle = xmlDoc.createElement("LineStyle"); styleElem.appendChild(lineStyle); Element elem = xmlDoc.createElement("color"); elem.setTextContent(style.getColour()); lineStyle.appendChild(elem); elem = xmlDoc.createElement("width"); elem.setTextContent(Integer.toString(style.getWidth())); lineStyle.appendChild(elem); rootDocument.appendChild(styleElem); hasStyle = true; } return false; } /** * add a basic GPS trace * * @param trace a list of GpsTraceElements * @throws BuildException if a error occurs while processing the list of traces */ public void addTrace(ArrayList<GpsTraceElement> trace) throws BuildException { // validate the parameters if(trace == null) { throw new IllegalArgumentException("the trace parameter is required"); } if(trace.size() == 0) { throw new IllegalArgumentException("the trace must contain at least one element"); } // add the start of the PlaceMark element Element elem = xmlDoc.createElement("Placemark"); rootDocument.appendChild(elem); if(hasStyle == true) { Element styleUrl = xmlDoc.createElement("styleUrl"); styleUrl.setTextContent(STYLE_URL); elem.appendChild(styleUrl); } // add the LineString element Element lineString = xmlDoc.createElement("LineString"); elem.appendChild(lineString); // add the tessellate element elem = xmlDoc.createElement("tessellate"); elem.setTextContent("1"); lineString.appendChild(elem); // create the altitude mode // used in conjunction with the tessellate element above to ensure the // line string is stuck to the ground elem = xmlDoc.createElement("altitudeMode"); elem.setTextContent("clampToGround"); lineString.appendChild(elem); // create the coordinates element elem = xmlDoc.createElement("coordinates"); StringBuilder coordinates = new StringBuilder(); // process the list of elements for(GpsTraceElement element : trace) { coordinates.append(Double.toString(element.getLongitude()) + "," + Double.toString(element.getLatitude()) + " "); } elem.setTextContent(coordinates.toString()); lineString.appendChild(elem); } /** * add a basic GPS trace * * @param trace a list of GpsTraceElements * @throws BuildException if a error occurs while processing the list of traces */ public void addTraceWithTime(ArrayList<GpsTraceElement> trace) throws BuildException { // validate the parameters if(trace == null) { throw new IllegalArgumentException("the trace parameter is required"); } if(trace.size() == 0) { throw new IllegalArgumentException("the trace must contain at least one element"); } GpsTraceElement previousTraceElement = null; String coordinates; Element elem; for(GpsTraceElement currentTraceElement : trace) { if(previousTraceElement == null) { previousTraceElement = currentTraceElement; continue; } // add the start of the PlaceMark element Element placemark = xmlDoc.createElement("Placemark"); rootDocument.appendChild(placemark); if(hasStyle == true) { Element styleUrl = xmlDoc.createElement("styleUrl"); styleUrl.setTextContent(STYLE_URL); placemark.appendChild(styleUrl); } // add the LineString element Element lineString = xmlDoc.createElement("LineString"); placemark.appendChild(lineString); // add the tessellate element elem = xmlDoc.createElement("tessellate"); elem.setTextContent("1"); lineString.appendChild(elem); // create the altitude mode // used in conjunction with the tessellate element above to ensure the // line string is stuck to the ground elem = xmlDoc.createElement("altitudeMode"); elem.setTextContent("clampToGround"); lineString.appendChild(elem); // create the coordinates element elem = xmlDoc.createElement("coordinates"); coordinates = new String(); coordinates = Double.toString(previousTraceElement.getLongitude()) + "," + Double.toString(previousTraceElement.getLatitude()) + " " + Double.toString(currentTraceElement.getLongitude()) + "," + Double.toString(currentTraceElement.getLatitude()) + " "; elem.setTextContent(coordinates); lineString.appendChild(elem); // create the timespan element Element timespan = xmlDoc.createElement("TimeSpan"); elem = xmlDoc.createElement("begin"); elem.setTextContent(Utils.buildTime(previousTraceElement.getTimestamp(), previousTraceElement.getTimezone())); timespan.appendChild(elem); elem = xmlDoc.createElement("end"); elem.setTextContent(Utils.buildTime(currentTraceElement.getTimestamp(), currentTraceElement.getTimezone())); timespan.appendChild(elem); placemark.appendChild(timespan); // store reference to the current place mark for later previousTraceElement = currentTraceElement; } } /** * output the contents of the KML to a file * * @throws BuildException if an error occurs during xml transformation our file writing */ public void outputToFile(PrintWriter printWriter) throws BuildException { try { // create a transformer TransformerFactory transFactory = TransformerFactory.newInstance(); Transformer transformer = transFactory.newTransformer(); // set some options on the transformer transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); // get a transformer and supporting classes StreamResult result = new StreamResult(printWriter); DOMSource source = new DOMSource(xmlDoc); // transform the internal objects into XML and print it transformer.transform(source, result); } catch (javax.xml.transform.TransformerException e) { throw new BuildException("unable to create the XML and output it to the file", e); } } }