]> git.argeo.org Git - gpl/argeo-suite.git/blob - geo/GpxUtils.java
Prepare next development cycle
[gpl/argeo-suite.git] / geo / GpxUtils.java
1 package org.argeo.app.geo;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.io.OutputStreamWriter;
7 import java.io.Writer;
8 import java.nio.charset.StandardCharsets;
9 import java.util.ArrayList;
10 import java.util.List;
11 import java.util.Objects;
12 import java.util.StringTokenizer;
13
14 import javax.xml.parsers.ParserConfigurationException;
15 import javax.xml.parsers.SAXParser;
16 import javax.xml.parsers.SAXParserFactory;
17
18 import org.geotools.api.feature.simple.SimpleFeature;
19 import org.geotools.api.feature.simple.SimpleFeatureType;
20 import org.geotools.data.DataUtilities;
21 import org.geotools.feature.SchemaException;
22 import org.geotools.feature.simple.SimpleFeatureBuilder;
23 import org.locationtech.jts.geom.Coordinate;
24 import org.locationtech.jts.geom.Geometry;
25 import org.locationtech.jts.geom.GeometryFactory;
26 import org.locationtech.jts.geom.LineString;
27 import org.locationtech.jts.geom.MultiPoint;
28 import org.locationtech.jts.geom.Polygon;
29 import org.xml.sax.Attributes;
30 import org.xml.sax.SAXException;
31 import org.xml.sax.helpers.DefaultHandler;
32
33 /** Utilities around the GPX format. */
34 public class GpxUtils {
35 /** GPX as a LineString in WGS84 (feature type with only the_geom). */
36 public static final SimpleFeatureType LINESTRING_FEATURE_TYPE;
37 /** GPX as a Polygon in WGS84 (feature type with only the_geom). */
38 public static final SimpleFeatureType POLYGON_FEATURE_TYPE;
39
40 static {
41 try {
42 LINESTRING_FEATURE_TYPE = DataUtilities.createType("Area", "the_geom:LineString:srid=4326");
43 POLYGON_FEATURE_TYPE = DataUtilities.createType("Area", "the_geom:Polygon:srid=4326");
44 } catch (SchemaException e) {
45 throw new RuntimeException("Cannot create GPX Feature type", e);
46 }
47 }
48
49 /**
50 * Converts a GPX track to either a {@link Geometry} with WGS84 coordinates
51 * ({@link LineString} or {@link Polygon}) or a {@link SimpleFeature} (with
52 * {@link #LINESTRING_FEATURE_TYPE}).
53 */
54 @SuppressWarnings("unchecked")
55 public static <T> T parseGpxTrackTo(InputStream in, Class<T> clss) throws IOException {
56 GeometryFactory geometryFactory = JTS.GEOMETRY_FACTORY_WGS84;
57 List<Coordinate> coordinates = new ArrayList<>();
58 try {
59 SAXParserFactory factory = SAXParserFactory.newInstance();
60 SAXParser saxParser = factory.newSAXParser();
61
62 saxParser.parse(in, new DefaultHandler() {
63
64 @Override
65 public void startElement(String uri, String localName, String qName, Attributes attributes)
66 throws SAXException {
67 if ("trkpt".equals(qName)) {
68 Double latitude = Double.parseDouble(attributes.getValue("lat"));
69 Double longitude = Double.parseDouble(attributes.getValue("lon"));
70 // TODO elevation in 3D context
71 Coordinate coordinate = new Coordinate(longitude, latitude);
72 coordinates.add(coordinate);
73 }
74 }
75
76 });
77 } catch (ParserConfigurationException | SAXException e) {
78 throw new RuntimeException("Cannot convert GPX", e);
79 }
80
81 if (LineString.class.isAssignableFrom(clss)) {
82 LineString lineString = geometryFactory
83 .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
84 return (T) lineString;
85 } else if (MultiPoint.class.isAssignableFrom(clss)) {
86 MultiPoint multiPoint = geometryFactory
87 .createMultiPointFromCoords(coordinates.toArray(new Coordinate[coordinates.size()]));
88 // multiPoint.normalize();
89 return (T) multiPoint;
90 } else if (Polygon.class.isAssignableFrom(clss)) {
91 Coordinate first = coordinates.get(0);
92 Coordinate last = coordinates.get(coordinates.size() - 1);
93 if (!(first.getX() == last.getX() && first.getY() == last.getY())) {
94 // close the line string
95 coordinates.add(first);
96 }
97 Polygon polygon = geometryFactory.createPolygon(coordinates.toArray(new Coordinate[coordinates.size()]));
98 return (T) polygon;
99 } else if (SimpleFeature.class.isAssignableFrom(clss)) {
100 SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(LINESTRING_FEATURE_TYPE);
101 LineString lineString = geometryFactory
102 .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
103 featureBuilder.add(lineString);
104 SimpleFeature area = featureBuilder.buildFeature(null);
105 return (T) area;
106 } else {
107 throw new IllegalArgumentException("Unsupported format " + clss);
108 }
109 }
110
111 /** @deprecated Use {@link #parseGpxTrackTo(InputStream, Class)} instead. */
112 @Deprecated
113 public static SimpleFeature parseGpxToPolygon(InputStream in) throws IOException {
114 SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(POLYGON_FEATURE_TYPE);
115 Polygon polygon = parseGpxTrackTo(in, Polygon.class);
116 featureBuilder.add(polygon);
117 SimpleFeature area = featureBuilder.buildFeature(null);
118 return area;
119 }
120
121 /** Write ODK GeoShape as a GPX file. */
122 public static void writeGeoShapeAsGpx(String geoShape, OutputStream out) throws IOException {
123 Objects.requireNonNull(geoShape);
124 Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
125 writer.append("<gpx><trk><trkseg>");
126 StringTokenizer stSeg = new StringTokenizer(geoShape.trim(), ";");
127 while (stSeg.hasMoreTokens()) {
128 StringTokenizer stPt = new StringTokenizer(stSeg.nextToken().trim(), " ");
129 String lat = stPt.nextToken();
130 String lng = stPt.nextToken();
131 String alt = stPt.nextToken();
132 // String precision = stPt.nextToken();
133 writer.append("<trkpt");
134 writer.append(" lat=\"").append(lat).append('\"');
135 writer.append(" lon=\"").append(lng).append('\"');
136 if (!alt.equals("0.0")) {
137 writer.append('>');
138 writer.append("<ele>").append(alt).append("</ele>");
139 writer.append("</trkpt>");
140 } else {
141 writer.append("/>");
142 }
143 }
144 writer.append("</trkseg></trk></gpx>");
145 writer.flush();
146 }
147
148 /** Singleton. */
149 private GpxUtils() {
150 }
151 }