/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.geo;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.structr.common.error.FrameworkException;
import org.structr.core.GraphObjectMap;
import org.structr.core.function.XmlFunction;
import org.structr.core.property.DoubleProperty;
import org.structr.core.property.GenericProperty;
import org.structr.core.property.IntProperty;
import org.structr.core.property.Property;
import org.structr.core.property.PropertyKey;
import org.structr.core.property.StringProperty;
import org.structr.schema.action.ActionContext;
import org.structr.schema.action.Function;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
*
*/
public class ImportGPXFunction extends Function<Object, Object> {
private static final Logger logger = LoggerFactory.getLogger(ImportGPXFunction.class.getName());
public static final Property<List<GraphObjectMap>> waypointsProperty = new GenericProperty<>("waypoints");
public static final Property<List<GraphObjectMap>> routesProperty = new GenericProperty<>("routes");
public static final Property<List<GraphObjectMap>> tracksProperty = new GenericProperty<>("tracks");
public static final Property<List<GraphObjectMap>> segmentsProperty = new GenericProperty<>("segments");
public static final Property<List<GraphObjectMap>> pointsProperty = new GenericProperty<>("points");
public static final Property<GraphObjectMap> metadataProperty = new GenericProperty<>("metadata");
public static final Property<GraphObjectMap> authorProperty = new GenericProperty<>("author");
public static final Property<Double> latitudeProperty = new DoubleProperty("latitude");
public static final Property<Double> longitudeProperty = new DoubleProperty("longitude");
public static final String ERROR_MESSAGE = "";
private static final Map<String, Property> fieldMapping = new LinkedHashMap<>();
static {
fieldMapping.put("ele", new DoubleProperty("altitude"));
fieldMapping.put("time", new StringProperty("time"));
fieldMapping.put("name", new StringProperty("name"));
fieldMapping.put("desc", new StringProperty("description"));
fieldMapping.put("link", new StringProperty("link"));
fieldMapping.put("src", new StringProperty("source"));
fieldMapping.put("magvar", new DoubleProperty("magvar"));
fieldMapping.put("cmt", new StringProperty("comment"));
fieldMapping.put("type", new StringProperty("type"));
fieldMapping.put("sym", new StringProperty("symbol"));
fieldMapping.put("fix", new StringProperty("fixType"));
fieldMapping.put("sat", new IntProperty("satelliteCount"));
fieldMapping.put("hdop", new DoubleProperty("horizontalDilution"));
fieldMapping.put("vdop", new DoubleProperty("verticalDilution"));
fieldMapping.put("pdop", new DoubleProperty("positionDilution"));
fieldMapping.put("ageofdgpsdata", new DoubleProperty("dgpsAge"));
fieldMapping.put("dgpsid", new StringProperty("dgpsId"));
fieldMapping.put("geoidheight", new DoubleProperty("geoidHeight"));
}
@Override
public Object apply(final ActionContext ctx, final Object caller, final Object[] sources) throws FrameworkException {
if (arrayHasLengthAndAllElementsNotNull(sources, 1)) {
if (sources[0] instanceof String) {
final String source = (String)sources[0];
if (source != null) {
// parse source, create a list of points
final GraphObjectMap result = new GraphObjectMap();
final XmlFunction xmlParser = new XmlFunction();
final Document doc = (Document)xmlParser.apply(ctx, caller, sources);
final List<GraphObjectMap> waypoints = new LinkedList<>();
final List<GraphObjectMap> routes = new LinkedList<>();
final List<GraphObjectMap> tracks = new LinkedList<>();
if (doc != null) {
final Element root = doc.getDocumentElement();
final List<Element> children = getChildren(root);
for (final Element child : children) {
switch (child.getTagName()) {
case "metadata":
final GraphObjectMap metadata = readPoint(child);
if (metadata != null) {
result.put(metadataProperty, metadata);
}
break;
case "rte":
readRoute(child, routes);
break;
case "wpt":
readWaypoint(child, waypoints);
break;
case "trk":
readTrack(child, tracks);
break;
}
}
if (!waypoints.isEmpty()) {
result.put(waypointsProperty, waypoints);
}
if (!routes.isEmpty()) {
result.put(routesProperty, routes);
}
if (!tracks.isEmpty()) {
result.put(tracksProperty, tracks);
}
}
return result;
}
} else {
logger.warn("Invalid parameter for GPX import, expected string, got {}", sources[0].getClass().getSimpleName() );
}
return "Invalid parameters";
}
return usage(ctx != null ? ctx.isJavaScriptContext() : false);
}
@Override
public String usage(final boolean inJavaScriptContext) {
return ERROR_MESSAGE;
}
@Override
public String shortDescription() {
return "Imports a GPX file and creates a list of objects of a given type from it.";
}
@Override
public String getName() {
return "import_gpx";
}
// ----- private methods -----
private void readWaypoint(final Element waypoint, final List<GraphObjectMap> resultList) {
final GraphObjectMap item = readPoint(waypoint);
if (item != null) {
resultList.add(item);
}
}
private void readRoute(final Element route, final List<GraphObjectMap> resultList) {
final List<GraphObjectMap> points = new LinkedList<>();
final GraphObjectMap result = new GraphObjectMap();
for (final Element elem : getChildren(route)) {
switch (elem.getTagName()) {
case "rtept":
final GraphObjectMap item = readPoint(elem);
if (item != null) {
points.add(item);
}
break;
}
readProperties(elem, result);
}
if (!points.isEmpty()) {
result.put(pointsProperty, points);
}
resultList.add(result);
}
private void readTrack(final Element track, final List<GraphObjectMap> resultList) {
final List<GraphObjectMap> segments = new LinkedList<>();
final GraphObjectMap result = new GraphObjectMap();
for (final Element elem : getChildren(track)) {
switch (elem.getTagName()) {
case "trkseg":
readTrackSegment(elem, segments);
break;
}
readProperties(elem, result);
}
if (!segments.isEmpty()) {
result.put(segmentsProperty, segments);
}
resultList.add(result);
}
private void readTrackSegment(final Element trackSegment, final List<GraphObjectMap> resultList) {
final List<GraphObjectMap> points = new LinkedList<>();
final GraphObjectMap result = new GraphObjectMap();
for (final Element elem : getChildren(trackSegment)) {
switch (elem.getTagName()) {
case "trkpt":
final GraphObjectMap item = readPoint(elem);
if (item != null) {
points.add(item);
}
break;
}
readProperties(elem, result);
}
if (!points.isEmpty()) {
result.put(pointsProperty, points);
}
resultList.add(result);
}
private GraphObjectMap readPoint(final Element point) {
final GraphObjectMap item = new GraphObjectMap();
// latitude and longitude are the only node attributes
final Double latitude = getDoubleAttribute(point, "lat");
if (latitude != null) {
item.put(latitudeProperty, latitude);
}
// latitude and longitude are the only node attributes
final Double longitude = getDoubleAttribute(point, "lon");
if (longitude != null) {
item.put(longitudeProperty, longitude);
}
// all other attributes are stored in child nodes
for (final Element child : getChildren(point)) {
readProperties(child, item);
}
return item;
}
private void readProperties(final Element child, final GraphObjectMap item) {
final String tagName = child.getTagName();
final Property property = fieldMapping.get(tagName);
if (property != null) {
final Class valueType = property.valueType();
if (valueType != null) {
switch (valueType.getSimpleName()) {
case "Double":
storeDouble(child, item, property);
break;
case "String":
storeString(child, item, property);
break;
case "Integer":
storeInt(child, item, property);
break;
}
}
} else {
if ("author".equals(tagName)) {
final GraphObjectMap author = readPoint(child);
if (!author.isEmpty()) {
item.put(authorProperty, author);
}
}
}
}
private List<Element> getChildren(final Element root) {
final List<Element> elements = new LinkedList<>();
final NodeList nodes = root.getChildNodes();
final int length = nodes.getLength();
for (int i=0; i<length; i++) {
final Node node = nodes.item(i);
if (node instanceof Element) {
elements.add((Element)node);
}
}
return elements;
}
private Double getDoubleAttribute(final Element source, final String name) {
final String str = source.getAttribute(name);
if (str != null && !str.isEmpty()) {
return Double.valueOf(str);
}
return null;
}
private void storeDouble(final Element element, final GraphObjectMap result, final PropertyKey<Double> key) {
final String source = element.getTextContent();
if (source != null && !source.isEmpty()) {
final Double value = Double.valueOf(source);
if (value != null) {
result.put(key, value);
}
}
}
private void storeInt(final Element element, final GraphObjectMap result, final PropertyKey<Integer> key) {
final String source = element.getTextContent();
if (source != null && !source.isEmpty()) {
final Integer value = Integer.valueOf(source);
if (value != null) {
result.put(key, value);
}
}
}
private void storeString(final Element element, final GraphObjectMap result, final PropertyKey<String> key) {
final String source = element.getTextContent();
if (source != null && !source.isEmpty()) {
result.put(key, source);
}
}
}