1 package org
.argeo
.app
.geo
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.io
.OutputStream
;
6 import java
.io
.OutputStreamWriter
;
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
;
14 import javax
.xml
.parsers
.ParserConfigurationException
;
15 import javax
.xml
.parsers
.SAXParser
;
16 import javax
.xml
.parsers
.SAXParserFactory
;
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
;
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
;
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
);
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}).
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
<>();
59 SAXParserFactory factory
= SAXParserFactory
.newInstance();
60 SAXParser saxParser
= factory
.newSAXParser();
62 saxParser
.parse(in
, new DefaultHandler() {
65 public void startElement(String uri
, String localName
, String qName
, Attributes attributes
)
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
);
77 } catch (ParserConfigurationException
| SAXException e
) {
78 throw new RuntimeException("Cannot convert GPX", e
);
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
);
97 Polygon polygon
= geometryFactory
.createPolygon(coordinates
.toArray(new Coordinate
[coordinates
.size()]));
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);
107 throw new IllegalArgumentException("Unsupported format " + clss
);
111 /** @deprecated Use {@link #parseGpxTrackTo(InputStream, Class)} instead. */
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);
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")) {
138 writer
.append("<ele>").append(alt
).append("</ele>");
139 writer
.append("</trkpt>");
144 writer
.append("</trkseg></trk></gpx>");