/*
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.mm;
import slash.navigation.base.*;
import slash.navigation.common.NavigationPosition;
import javax.xml.namespace.QName;
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import static java.util.Collections.singletonList;
import static slash.common.io.Transfer.*;
import static slash.navigation.base.RouteCalculations.asWgs84Position;
import static slash.navigation.common.NavigationConversion.formatPositionAsString;
/**
* Reads and writes MagicMaps Project (.ikt) files.
*
* @author Christian Pesch
*/
public class MagicMapsIktFormat extends XmlNavigationFormat<MagicMapsIktRoute> implements MultipleRoutesFormat<MagicMapsIktRoute> {
private static final String ROOT_ELEMENT = "Root";
private static final String FILE_FORMAT_ATTRIBUTE = "FileFormat";
private static final String FILE_FORMAT = "IK3D-Project-File";
private static final String HEADER_ELEMENT = "Header";
private static final String PROJECT_NAME_ELEMENT = "Projectname";
private static final String DESCRIPTION_ELEMENT = "description";
private static final String CONTENT_ELEMENT = "Content";
private static final String GEO_OBJECTS_ELEMENT = "MMGeoObjects";
private static final String COUNT_ELEMENT = "count";
private static final String GEO_OBJECT_ELEMENT = "MMGeoObject";
private static final String GEO_OBJECT_TYPE_ELEMENT = "GeoObjectType";
private static final String NAME_ELEMENT = "Name";
private static final String PATH_DRAW_TYPE_ELEMENT = "PathDrawType";
private static final String PATH_POINTS_ELEMENT = "PathPoints";
private static final String POINT_ELEMENT = "Point";
private static final String GEO_POSITION_ELEMENT = "GeoPosition";
private static final String X_ATTRIBUTE = "X";
private static final String Y_ATTRIBUTE = "Y";
public String getExtension() {
return ".ikt";
}
public String getName() {
return "MagicMaps Project (*" + getExtension() + ")";
}
public boolean isSupportsMultipleRoutes() {
return true;
}
public boolean isWritingRouteCharacteristics() {
return false;
}
@SuppressWarnings("unchecked")
public <P extends NavigationPosition> MagicMapsIktRoute createRoute(RouteCharacteristics characteristics, String name, List<P> positions) {
return new MagicMapsIktRoute(name, null, (List<Wgs84Position>) positions);
}
private Double extractValue(StartElement startElement, String attributeName) {
Attribute attribute = startElement.getAttributeByName(new QName(attributeName));
if (attribute == null)
return null;
return parseDouble(attribute.getValue());
}
private String extractValue(Characters chars, String value) {
String append = trim(chars.getData());
if (append == null)
return value;
if (value == null)
return append;
return value + append;
}
private Wgs84Position processPosition(StartElement startElement) {
return asWgs84Position(extractValue(startElement, X_ATTRIBUTE), extractValue(startElement, Y_ATTRIBUTE), null);
}
private List<MagicMapsIktRoute> process(XMLEventReader eventReader) throws XMLStreamException {
boolean hasValidRoot = false, nextIsProjectName = false, nextIsDescription = false, nextIsName = false;
String projectName = null, description = null, name = null;
List<MagicMapsIktRoute> routes = new ArrayList<>();
List<Wgs84Position> positions = null;
while (eventReader.hasNext()) {
XMLEvent event = eventReader.nextEvent();
if (event.getEventType() == XMLStreamConstants.CHARACTERS) {
Characters chars = event.asCharacters();
if (nextIsProjectName)
projectName = extractValue(chars, projectName);
if (nextIsDescription)
description = extractValue(chars, description);
if (nextIsName)
name = extractValue(chars, name);
}
if (event.getEventType() == XMLStreamConstants.START_ELEMENT) {
StartElement startElement = event.asStartElement();
String elementName = startElement.getName().getLocalPart();
// detect by having first element to be <Root FileFormat="IK3D-Project-File">
if (!hasValidRoot) {
if (!(ROOT_ELEMENT.equals(elementName)))
return null;
Attribute fileFormat = startElement.getAttributeByName(new QName(FILE_FORMAT_ATTRIBUTE));
if (fileFormat == null || !FILE_FORMAT.equals(fileFormat.getValue()))
return null;
hasValidRoot = true;
} else if (elementName.startsWith(GEO_OBJECT_ELEMENT)) {
name = null;
positions = new ArrayList<>();
} else if (elementName.startsWith(GEO_POSITION_ELEMENT)) {
positions.add(processPosition(startElement));
}
nextIsProjectName = PROJECT_NAME_ELEMENT.equals(elementName);
nextIsDescription = DESCRIPTION_ELEMENT.equals(elementName);
nextIsName = NAME_ELEMENT.equals(elementName);
}
if (event.getEventType() == XMLStreamConstants.END_ELEMENT) {
EndElement endElement = event.asEndElement();
String elementName = endElement.getName().getLocalPart();
if (ROOT_ELEMENT.equals(elementName)) {
return routes;
} else //noinspection StatementWithEmptyBody
if (elementName.startsWith(GEO_OBJECTS_ELEMENT)) {
} else if (elementName.startsWith(GEO_OBJECT_ELEMENT)) {
if (name == null)
name = projectName;
routes.add(new MagicMapsIktRoute(this, name, asDescription(description), positions));
}
}
}
return null;
}
public void read(InputStream source, ParserContext<MagicMapsIktRoute> context) throws Exception {
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader eventReader = factory.createXMLEventReader(source, UTF8_ENCODING);
context.appendRoutes(process(eventReader));
}
private void writeHeader(String name, String description, XMLEventWriter writer, XMLEventFactory eventFactory) throws XMLStreamException {
writer.add(eventFactory.createStartDocument(UTF8_ENCODING, "1.0", true));
List<Attribute> rootAttributes = new ArrayList<>();
rootAttributes.add(eventFactory.createAttribute(FILE_FORMAT_ATTRIBUTE, FILE_FORMAT));
writer.add(eventFactory.createStartElement(new QName(ROOT_ELEMENT), rootAttributes.iterator(), null));
writer.add(eventFactory.createStartElement(new QName(HEADER_ELEMENT), null, null));
writer.add(eventFactory.createStartElement(new QName(PROJECT_NAME_ELEMENT), null, null));
writer.add(eventFactory.createCharacters(name));
writer.add(eventFactory.createEndElement(new QName(PROJECT_NAME_ELEMENT), null));
writer.add(eventFactory.createStartElement(new QName(DESCRIPTION_ELEMENT), null, null));
writer.add(eventFactory.createCData(description));
writer.add(eventFactory.createEndElement(new QName(DESCRIPTION_ELEMENT), null));
writer.add(eventFactory.createEndElement(new QName(HEADER_ELEMENT), null));
writer.add(eventFactory.createStartElement(new QName(CONTENT_ELEMENT), null, null));
writer.add(eventFactory.createStartElement(new QName(GEO_OBJECTS_ELEMENT), null, null));
}
private void writePosition(Wgs84Position position, int index, XMLEventWriter writer, XMLEventFactory eventFactory) throws XMLStreamException {
writer.add(eventFactory.createStartElement(new QName(POINT_ELEMENT + "_" + index), null, null));
List<Attribute> attributes = new ArrayList<>();
attributes.add(eventFactory.createAttribute(X_ATTRIBUTE, formatPositionAsString(position.getLongitude())));
attributes.add(eventFactory.createAttribute(Y_ATTRIBUTE, formatPositionAsString(position.getLatitude())));
writer.add(eventFactory.createStartElement(new QName(GEO_POSITION_ELEMENT), attributes.iterator(), null));
writer.add(eventFactory.createEndElement(new QName(GEO_POSITION_ELEMENT), null));
writer.add(eventFactory.createEndElement(new QName(POINT_ELEMENT + "_" + index), null));
}
private void writeRoute(MagicMapsIktRoute route, int index, XMLEventWriter writer, XMLEventFactory eventFactory) throws XMLStreamException {
writer.add(eventFactory.createStartElement(new QName(GEO_OBJECT_ELEMENT + "_" + index), null, null));
writer.add(eventFactory.createStartElement(new QName(GEO_OBJECT_TYPE_ELEMENT), null, null));
writer.add(eventFactory.createCharacters("1"));
writer.add(eventFactory.createEndElement(new QName(GEO_OBJECT_TYPE_ELEMENT), null));
writer.add(eventFactory.createStartElement(new QName(NAME_ELEMENT), null, null));
writer.add(eventFactory.createCharacters(asRouteName(route.getName())));
writer.add(eventFactory.createEndElement(new QName(NAME_ELEMENT), null));
writer.add(eventFactory.createStartElement(new QName(PATH_DRAW_TYPE_ELEMENT), null, null));
writer.add(eventFactory.createCharacters("5"));
writer.add(eventFactory.createEndElement(new QName(PATH_DRAW_TYPE_ELEMENT), null));
writer.add(eventFactory.createStartElement(new QName(PATH_POINTS_ELEMENT), null, null));
List<Wgs84Position> positions = route.getPositions();
writer.add(eventFactory.createStartElement(new QName(COUNT_ELEMENT), null, null));
writer.add(eventFactory.createCharacters(formatIntAsString(positions.size())));
writer.add(eventFactory.createEndElement(new QName(COUNT_ELEMENT), null));
for (int i = 0; i < positions.size(); i++) {
writePosition(positions.get(i), i, writer, eventFactory);
}
writer.add(eventFactory.createEndElement(new QName(PATH_POINTS_ELEMENT), null));
writer.add(eventFactory.createEndElement(new QName(GEO_OBJECT_ELEMENT + "_" + index), null));
}
private void writeFooter(XMLEventWriter writer, XMLEventFactory eventFactory) throws XMLStreamException {
writer.add(eventFactory.createEndElement(new QName(GEO_OBJECTS_ELEMENT), null));
writer.add(eventFactory.createEndElement(new QName(CONTENT_ELEMENT), null));
writer.add(eventFactory.createEndElement(new QName(ROOT_ELEMENT), null));
writer.add(eventFactory.createEndDocument());
}
public void write(MagicMapsIktRoute route, OutputStream target, int startIndex, int endIndex) throws IOException {
write(singletonList(route), target);
}
private String getProjectName(List<MagicMapsIktRoute> routes) {
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < routes.size(); i++) {
MagicMapsIktRoute route = routes.get(i);
buffer.append(route.getName());
if (i < routes.size() - 1)
buffer.append(", ");
}
return buffer.toString();
}
private String getDescription(List<MagicMapsIktRoute> routes) {
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < routes.size(); i++) {
MagicMapsIktRoute route = routes.get(i);
List<String> descriptions = route.getDescription();
if (descriptions == null)
continue;
for (int j = 0; j < descriptions.size(); j++) {
String description = descriptions.get(j);
buffer.append(description);
if (j < descriptions.size() - 1)
buffer.append(", ");
}
if (i < routes.size() - 1)
buffer.append("; ");
}
return buffer.toString();
}
public void write(List<MagicMapsIktRoute> routes, OutputStream target) throws IOException {
try {
XMLEventFactory eventFactory = XMLEventFactory.newInstance();
XMLOutputFactory output = XMLOutputFactory.newInstance();
XMLEventWriter writer = output.createXMLEventWriter(target, UTF8_ENCODING);
try {
writeHeader(getProjectName(routes), getDescription(routes), writer, eventFactory);
writer.add(eventFactory.createStartElement(new QName(COUNT_ELEMENT), null, null));
writer.add(eventFactory.createCharacters(formatIntAsString(routes.size())));
writer.add(eventFactory.createEndElement(new QName(COUNT_ELEMENT), null));
for (int i = 0; i < routes.size(); i++) {
writeRoute(routes.get(i), i, writer, eventFactory);
}
writeFooter(writer, eventFactory);
} finally {
writer.flush();
writer.close();
target.flush();
target.close();
}
} catch (XMLStreamException e) {
throw new IOException("Error while marshalling: " + e, e);
}
}
}