mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-27 12:26:48 +01:00
Refactor and extend GpxParser
This commit is contained in:
parent
87c91fb9b0
commit
01ec74602a
@ -34,13 +34,16 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GpxParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParseException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
|
||||
|
||||
import static android.graphics.Bitmap.createBitmap;
|
||||
|
||||
@ -74,37 +77,35 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GpxParser gpxParser = null;
|
||||
FileInputStream inputStream = null;
|
||||
final GpxFile gpxFile;
|
||||
|
||||
try {
|
||||
inputStream = new FileInputStream(inputFile);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
try (FileInputStream inputStream = new FileInputStream(inputFile)) {
|
||||
final GpxParser gpxParser = new GpxParser(inputStream);
|
||||
gpxFile = gpxParser.getGpxFile();
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Failed to open {}", inputFile, e);
|
||||
return;
|
||||
} catch (final GpxParseException e) {
|
||||
LOG.error("Failed to parse gpx file", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (inputStream != null) {
|
||||
gpxParser = new GpxParser(inputStream);
|
||||
}
|
||||
|
||||
if (gpxParser != null) {
|
||||
if (gpxParser.getPoints().toArray().length > 0) {
|
||||
drawTrack(canvas, gpxParser.getPoints());
|
||||
}
|
||||
if (!gpxFile.getPoints().isEmpty()) {
|
||||
drawTrack(canvas, gpxFile.getPoints());
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void drawTrack(Canvas canvas, List<GPSCoordinate> trackPoints) {
|
||||
private void drawTrack(Canvas canvas, List<? extends GPSCoordinate> trackPoints) {
|
||||
double maxLat = (Collections.max(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
|
||||
double minLat = (Collections.min(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
|
||||
double maxLon = (Collections.max(trackPoints, new GPSCoordinate.compareLongitude())).getLongitude();
|
||||
double minLon = (Collections.min(trackPoints, new GPSCoordinate.compareLongitude())).getLongitude();
|
||||
double maxAlt = (Collections.max(trackPoints, new GPSCoordinate.compareElevation())).getAltitude();
|
||||
double minAlt = (Collections.min(trackPoints, new GPSCoordinate.compareElevation())).getAltitude();
|
||||
float scale_factor_w = (float) ((maxLat - minLat) / (maxLon - minLon));
|
||||
float scale_factor_h = (float) ((maxLon - minLon) / (maxLat - minLat));
|
||||
float scale_factor_w = (float) ((maxLon - minLon) / (maxLat - minLat));
|
||||
float scale_factor_h = (float) ((maxLat - minLat) / (maxLon - minLon));
|
||||
|
||||
if (scale_factor_h > scale_factor_w) { //scaling to draw proportionally
|
||||
scale_factor_h = 1;
|
||||
@ -122,7 +123,7 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
|
||||
float lon = (float) ((p.getLongitude() - minLon) / (maxLon - minLon));
|
||||
float alt = (float) ((p.getAltitude() - minAlt) / (maxAlt - minAlt));
|
||||
paint.setStrokeWidth(1 + alt); //make thicker with higher altitude, we could do more here
|
||||
canvas.drawPoint(CANVAS_SIZE * lat * scale_factor_w, CANVAS_SIZE * lon * scale_factor_h, paint);
|
||||
canvas.drawPoint(CANVAS_SIZE * lon * scale_factor_w, CANVAS_SIZE * lat * scale_factor_h, paint);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Comparator;
|
||||
|
||||
public final class GPSCoordinate {
|
||||
public class GPSCoordinate {
|
||||
private final double latitude;
|
||||
private final double longitude;
|
||||
private final double altitude;
|
||||
|
@ -1,99 +0,0 @@
|
||||
/* Copyright (C) 2020-2021 Petr Vaněk
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge 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.
|
||||
|
||||
Gadgetbridge 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 this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
|
||||
public class GpxParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GpxParser.class);
|
||||
private XmlPullParser parser;
|
||||
private List<GPSCoordinate> points = new ArrayList<>();
|
||||
private int eventType;
|
||||
|
||||
public GpxParser(InputStream stream) {
|
||||
points = new ArrayList<>();
|
||||
try {
|
||||
parser = createXmlParser(stream);
|
||||
parseGpx();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlPullParser createXmlParser(InputStream stream) throws XmlPullParserException {
|
||||
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
XmlPullParser parser = factory.newPullParser();
|
||||
parser.setInput(stream, null);
|
||||
return parser;
|
||||
}
|
||||
|
||||
private void parseGpx() throws XmlPullParserException, IOException {
|
||||
eventType = parser.getEventType();
|
||||
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG && parser.getName().equals("trkpt")) {
|
||||
points.add(parsePoint(parser));
|
||||
} else {
|
||||
eventType = parser.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double parseElevation(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
String eleString = "";
|
||||
while (eventType != XmlPullParser.END_TAG) {
|
||||
if (eventType == XmlPullParser.TEXT) {
|
||||
eleString = parser.getText();
|
||||
}
|
||||
eventType = parser.next();
|
||||
}
|
||||
return Double.parseDouble(eleString);
|
||||
}
|
||||
|
||||
private GPSCoordinate parsePoint(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
double lat;
|
||||
double lon;
|
||||
double ele = 0;
|
||||
String latString = parser.getAttributeValue(null, "lat");
|
||||
String lonString = parser.getAttributeValue(null, "lon");
|
||||
lat = latString != null ? Double.parseDouble(latString) : 0;
|
||||
lon = lonString != null ? Double.parseDouble(lonString) : 0;
|
||||
while (eventType != XmlPullParser.END_TAG) {
|
||||
if (parser.getEventType() == XmlPullParser.START_TAG && parser.getName().equals("ele")) {
|
||||
ele = parseElevation(parser);
|
||||
}
|
||||
eventType = parser.next();
|
||||
}
|
||||
return new GPSCoordinate(lat, lon, ele);
|
||||
}
|
||||
|
||||
public List<GPSCoordinate> getPoints() {
|
||||
return points;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/* Copyright (C) 2023 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge 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.
|
||||
|
||||
Gadgetbridge 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 this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util.gpx;
|
||||
|
||||
public class GpxParseException extends Exception {
|
||||
public GpxParseException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public GpxParseException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
/* Copyright (C) 2020-2023 Petr Vaněk, José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge 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.
|
||||
|
||||
Gadgetbridge 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 this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util.gpx;
|
||||
|
||||
import com.google.gson.internal.bind.util.ISO8601Utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Date;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrack;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackPoint;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackSegment;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxWaypoint;
|
||||
|
||||
public class GpxParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GpxParser.class);
|
||||
|
||||
private final XmlPullParser parser;
|
||||
private int eventType;
|
||||
|
||||
private final GpxFile.Builder fileBuilder;
|
||||
|
||||
public GpxParser(final InputStream stream) throws GpxParseException {
|
||||
this.fileBuilder = new GpxFile.Builder();
|
||||
|
||||
try {
|
||||
parser = createXmlParser(stream);
|
||||
parseGpx();
|
||||
} catch (final Exception e) {
|
||||
throw new GpxParseException("Failed to parse gpx", e);
|
||||
}
|
||||
}
|
||||
|
||||
public GpxFile getGpxFile() {
|
||||
return fileBuilder.build();
|
||||
}
|
||||
|
||||
private static XmlPullParser createXmlParser(InputStream stream) throws XmlPullParserException {
|
||||
final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
XmlPullParser parser = factory.newPullParser();
|
||||
parser.setInput(stream, null);
|
||||
return parser;
|
||||
}
|
||||
|
||||
private void parseGpx() throws Exception {
|
||||
eventType = parser.getEventType();
|
||||
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
switch (parser.getName()) {
|
||||
case "trk":
|
||||
final GpxTrack track = parseTrack();
|
||||
if (!track.isEmpty()) {
|
||||
fileBuilder.withTrack(track);
|
||||
}
|
||||
continue;
|
||||
case "wpt":
|
||||
fileBuilder.withWaypoints(parseWaypoint());
|
||||
continue;
|
||||
case "metadata":
|
||||
parseMetadata();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
eventType = parser.next();
|
||||
}
|
||||
}
|
||||
|
||||
private GpxTrack parseTrack() throws Exception {
|
||||
final GpxTrack.Builder trackBuilder = new GpxTrack.Builder();
|
||||
|
||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("trk")) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
switch (parser.getName()) {
|
||||
case "trkseg":
|
||||
final GpxTrackSegment segment = parseTrackSegment();
|
||||
if (!segment.getTrackPoints().isEmpty()) {
|
||||
trackBuilder.withTrackSegment(segment);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
eventType = parser.next();
|
||||
}
|
||||
|
||||
return trackBuilder.build();
|
||||
}
|
||||
|
||||
private GpxTrackSegment parseTrackSegment() throws Exception {
|
||||
final GpxTrackSegment.Builder segmentBuilder = new GpxTrackSegment.Builder();
|
||||
|
||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("trkseg")) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
switch (parser.getName()) {
|
||||
case "trkpt":
|
||||
final GpxTrackPoint trackPoint = parseTrackPoint();
|
||||
segmentBuilder.withTrackPoint(trackPoint);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
eventType = parser.next();
|
||||
}
|
||||
|
||||
return segmentBuilder.build();
|
||||
}
|
||||
|
||||
private String parseStringContent(final String tag) throws Exception {
|
||||
String retString = "";
|
||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals(tag)) {
|
||||
if (eventType == XmlPullParser.TEXT) {
|
||||
retString = parser.getText();
|
||||
}
|
||||
eventType = parser.next();
|
||||
}
|
||||
return retString;
|
||||
}
|
||||
|
||||
private double parseElevation() throws Exception {
|
||||
return Double.parseDouble(parseStringContent("ele"));
|
||||
}
|
||||
|
||||
private Date parseTime() throws Exception {
|
||||
return ISO8601Utils.parse(parseStringContent("time"), new ParsePosition(0));
|
||||
}
|
||||
|
||||
private GpxTrackPoint parseTrackPoint() throws Exception {
|
||||
final GpxTrackPoint.Builder trackPointBuilder = new GpxTrackPoint.Builder();
|
||||
|
||||
final String latString = parser.getAttributeValue(null, "lat");
|
||||
final String lonString = parser.getAttributeValue(null, "lon");
|
||||
trackPointBuilder.withLatitude(latString != null ? Double.parseDouble(latString) : 0);
|
||||
trackPointBuilder.withLongitude(lonString != null ? Double.parseDouble(lonString) : 0);
|
||||
|
||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("trkpt")) {
|
||||
if (parser.getEventType() == XmlPullParser.START_TAG) {
|
||||
switch (parser.getName()) {
|
||||
case "ele":
|
||||
trackPointBuilder.withAltitude(parseElevation());
|
||||
continue;
|
||||
case "time":
|
||||
trackPointBuilder.withTime(parseTime());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
eventType = parser.next();
|
||||
}
|
||||
|
||||
return trackPointBuilder.build();
|
||||
}
|
||||
|
||||
private GpxWaypoint parseWaypoint() throws Exception {
|
||||
final GpxWaypoint.Builder waypointBuilder = new GpxWaypoint.Builder();
|
||||
|
||||
final String latString = parser.getAttributeValue(null, "lat");
|
||||
final String lonString = parser.getAttributeValue(null, "lon");
|
||||
waypointBuilder.withLatitude(latString != null ? Double.parseDouble(latString) : 0);
|
||||
waypointBuilder.withLongitude(lonString != null ? Double.parseDouble(lonString) : 0);
|
||||
|
||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("wpt")) {
|
||||
if (parser.getEventType() == XmlPullParser.START_TAG) {
|
||||
switch (parser.getName()) {
|
||||
case "ele":
|
||||
waypointBuilder.withAltitude(parseElevation());
|
||||
continue;
|
||||
case "name":
|
||||
waypointBuilder.withName(parseStringContent("name"));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
eventType = parser.next();
|
||||
}
|
||||
|
||||
return waypointBuilder.build();
|
||||
}
|
||||
|
||||
private void parseMetadata() throws Exception {
|
||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("metadata")) {
|
||||
if (parser.getEventType() == XmlPullParser.START_TAG) {
|
||||
switch (parser.getName()) {
|
||||
case "name":
|
||||
fileBuilder.withName(parseStringContent("name"));
|
||||
continue;
|
||||
case "author":
|
||||
// TODO parse author
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
eventType = parser.next();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/* Copyright (C) 2023 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge 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.
|
||||
|
||||
Gadgetbridge 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 this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GpxFile {
|
||||
private final String name;
|
||||
private final String author;
|
||||
|
||||
private final List<GpxTrack> tracks;
|
||||
private final List<GpxWaypoint> waypoints;
|
||||
|
||||
public GpxFile(final String name, final String author, final List<GpxTrack> tracks, final List<GpxWaypoint> waypoints) {
|
||||
this.name = name;
|
||||
this.author = author;
|
||||
this.tracks = tracks;
|
||||
this.waypoints = waypoints;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public List<GpxTrack> getTracks() {
|
||||
return tracks;
|
||||
}
|
||||
|
||||
public List<GpxWaypoint> getWaypoints() {
|
||||
return waypoints;
|
||||
}
|
||||
|
||||
public List<GpxTrackPoint> getPoints() {
|
||||
final List<GpxTrackPoint> allPoints = new ArrayList<>();
|
||||
|
||||
for (final GpxTrack track : tracks) {
|
||||
for (final GpxTrackSegment trackSegment : track.getTrackSegments()) {
|
||||
allPoints.addAll(trackSegment.getTrackPoints());
|
||||
}
|
||||
}
|
||||
|
||||
return allPoints;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String name;
|
||||
private String author;
|
||||
|
||||
private List<GpxTrack> tracks = new ArrayList<>();
|
||||
private List<GpxWaypoint> waypoints = new ArrayList<>();
|
||||
|
||||
public Builder withName(final String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAuthor(final String author) {
|
||||
this.author = author;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTrack(final GpxTrack track) {
|
||||
this.tracks.add(track);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withWaypoints(final GpxWaypoint waypoint) {
|
||||
this.waypoints.add(waypoint);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GpxFile build() {
|
||||
return new GpxFile(name, author, tracks, waypoints);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/* Copyright (C) 2023 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge 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.
|
||||
|
||||
Gadgetbridge 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 this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GpxTrack {
|
||||
private final List<GpxTrackSegment> trackSegments;
|
||||
|
||||
public GpxTrack(final List<GpxTrackSegment> trackSegments) {
|
||||
this.trackSegments = trackSegments;
|
||||
}
|
||||
|
||||
public List<GpxTrackSegment> getTrackSegments() {
|
||||
return trackSegments;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
for (final GpxTrackSegment trackSegment : trackSegments) {
|
||||
if (!trackSegment.getTrackPoints().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final List<GpxTrackSegment> trackSegments = new ArrayList<>();
|
||||
|
||||
public Builder withTrackSegment(final GpxTrackSegment trackSegment) {
|
||||
trackSegments.add(trackSegment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GpxTrack build() {
|
||||
return new GpxTrack(trackSegments);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/* Copyright (C) 2023 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge 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.
|
||||
|
||||
Gadgetbridge 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 this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
|
||||
public class GpxTrackPoint extends GPSCoordinate {
|
||||
private final Date time;
|
||||
|
||||
public GpxTrackPoint(final double longitude, final double latitude, final double altitude, final Date time) {
|
||||
super(longitude, latitude, altitude);
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public Date getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof GpxTrackPoint)) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
final GpxTrackPoint that = (GpxTrackPoint) o;
|
||||
return Objects.equals(time, that.time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), time);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private double longitude;
|
||||
private double latitude;
|
||||
private double altitude;
|
||||
private Date time;
|
||||
|
||||
public Builder withLongitude(final double longitude) {
|
||||
this.longitude = longitude;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLatitude(final double latitude) {
|
||||
this.latitude = latitude;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAltitude(final double altitude) {
|
||||
this.altitude = altitude;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTime(final Date time) {
|
||||
this.time = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GpxTrackPoint build() {
|
||||
return new GpxTrackPoint(longitude, latitude, altitude, time);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/* Copyright (C) 2023 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge 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.
|
||||
|
||||
Gadgetbridge 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 this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GpxTrackSegment {
|
||||
private final List<GpxTrackPoint> trackPoints;
|
||||
|
||||
public GpxTrackSegment(final List<GpxTrackPoint> trackPoints) {
|
||||
this.trackPoints = trackPoints;
|
||||
}
|
||||
|
||||
public List<GpxTrackPoint> getTrackPoints() {
|
||||
return trackPoints;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final List<GpxTrackPoint> trackPoints = new ArrayList<>();
|
||||
|
||||
public Builder withTrackPoint(final GpxTrackPoint trackPoint) {
|
||||
trackPoints.add(trackPoint);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return trackPoints.isEmpty();
|
||||
}
|
||||
|
||||
public GpxTrackSegment build() {
|
||||
return new GpxTrackSegment(trackPoints);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* Copyright (C) 2023 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge 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.
|
||||
|
||||
Gadgetbridge 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 this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
|
||||
public class GpxWaypoint extends GPSCoordinate {
|
||||
@Nullable
|
||||
private final String name;
|
||||
|
||||
public GpxWaypoint(final double longitude, final double latitude, final double altitude, @Nullable final String name) {
|
||||
super(longitude, latitude, altitude);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private double longitude;
|
||||
private double latitude;
|
||||
private double altitude;
|
||||
private String name;
|
||||
|
||||
public Builder withLongitude(final double longitude) {
|
||||
this.longitude = longitude;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLatitude(final double latitude) {
|
||||
this.latitude = latitude;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withAltitude(final double altitude) {
|
||||
this.altitude = altitude;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withName(final String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GpxWaypoint build() {
|
||||
return new GpxWaypoint(longitude, latitude, altitude, name);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GpxParser;
|
||||
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class GPXParserTest extends TestBase {
|
||||
|
||||
@Test
|
||||
public void shouldReadGPXCorrectly() throws IOException {
|
||||
try (final InputStream inputStream = getClass().getResourceAsStream("/gpx-exporter-test-SampleTrack.gpx")) {
|
||||
GpxParser gpxParser = new GpxParser(inputStream);
|
||||
List<GPSCoordinate> trackPoints = gpxParser.getPoints();
|
||||
Assert.assertEquals(trackPoints.size(), 14);
|
||||
DecimalFormat df = new DecimalFormat("###.##");
|
||||
for (GPSCoordinate tp : trackPoints) {
|
||||
Assert.assertEquals(df.format(tp.getLongitude()), "44.15");
|
||||
Assert.assertEquals(df.format(tp.getLatitude()), "-68.2");
|
||||
Assert.assertThat(df.format(tp.getAltitude()), anyOf(is("40"), is("46")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util.gpx;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackPoint;
|
||||
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class GPXParserTest extends TestBase {
|
||||
|
||||
@Test
|
||||
public void shouldReadGPXCorrectly() throws IOException, GpxParseException {
|
||||
try (final InputStream inputStream = getClass().getResourceAsStream("/gpx-exporter-test-SampleTrack.gpx")) {
|
||||
GpxParser gpxParser = new GpxParser(inputStream);
|
||||
List<GpxTrackPoint> trackPoints = gpxParser.getGpxFile().getPoints();
|
||||
Assert.assertEquals(trackPoints.size(), 14);
|
||||
DecimalFormat df = new DecimalFormat("###.##");
|
||||
for (GPSCoordinate tp : trackPoints) {
|
||||
Assert.assertEquals(df.format(tp.getLongitude()), "-68.2");
|
||||
Assert.assertEquals(df.format(tp.getLatitude()), "44.15");
|
||||
Assert.assertThat(df.format(tp.getAltitude()), anyOf(is("40"), is("46")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldParseMultipleSegments() throws IOException, GpxParseException, ParseException {
|
||||
try (final InputStream inputStream = getClass().getResourceAsStream("/gpx-parser-test-multiple-segments.gpx")) {
|
||||
final GpxParser gpxParser = new GpxParser(inputStream);
|
||||
final GpxFile gpxFile = gpxParser.getGpxFile();
|
||||
Assert.assertEquals(1, gpxFile.getTracks().size());
|
||||
Assert.assertEquals(2, gpxFile.getTracks().get(0).getTrackSegments().size());
|
||||
|
||||
final List<GpxTrackPoint> segment1 = new ArrayList<GpxTrackPoint>() {{
|
||||
add(new GpxTrackPoint(-8.2695876, -70.6666343, 790.0, new Date(1680969788000L)));
|
||||
add(new GpxTrackPoint(-8.2653274, -70.6670617, 296.0, new Date(1680970639000L)));
|
||||
}};
|
||||
|
||||
final List<GpxTrackPoint> segment2 = new ArrayList<GpxTrackPoint>() {{
|
||||
add(new GpxTrackPoint(-8.2653274, -70.6670617, 205.0, new Date(1680971684000L)));
|
||||
add(new GpxTrackPoint(-8.2695876, -70.6666343, 209.0, new Date(1680973017000L)));
|
||||
}};
|
||||
|
||||
Assert.assertEquals(gpxFile.getTracks().get(0).getTrackSegments().get(0).getTrackPoints(), segment1);
|
||||
Assert.assertEquals(gpxFile.getTracks().get(0).getTrackSegments().get(1).getTrackPoints(), segment2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldParseOutOfOrder() throws IOException, GpxParseException {
|
||||
try (final InputStream inputStream = getClass().getResourceAsStream("/gpx-parser-test-order.gpx")) {
|
||||
final GpxParser gpxParser = new GpxParser(inputStream);
|
||||
final GpxFile gpxFile = gpxParser.getGpxFile();
|
||||
Assert.assertEquals(1, gpxFile.getTracks().size());
|
||||
Assert.assertEquals(1, gpxFile.getTracks().get(0).getTrackSegments().size());
|
||||
|
||||
final List<GpxTrackPoint> segment1 = new ArrayList<GpxTrackPoint>() {{
|
||||
add(new GpxTrackPoint(-8.2695876, -70.6666343, 790.0, new Date(1680969788000L)));
|
||||
add(new GpxTrackPoint(-8.2653274, -70.6670617, 296.0, new Date(1680970639000L)));
|
||||
}};
|
||||
|
||||
Assert.assertEquals(gpxFile.getTracks().get(0).getTrackSegments().get(0).getTrackPoints(), segment1);
|
||||
}
|
||||
}
|
||||
}
|
57
app/src/test/resources/gpx-parser-test-multiple-segments.gpx
Normal file
57
app/src/test/resources/gpx-parser-test-multiple-segments.gpx
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
|
||||
<gpx version="1.1" creator="GBApplication.app().getNameAndVersion()"
|
||||
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd
|
||||
http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
|
||||
xmlns="http://www.topografix.com/GPX/1/1"
|
||||
xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1">
|
||||
<metadata>
|
||||
<name>Test Multiple Segments</name>
|
||||
<author />
|
||||
<time>2023-04-09T17:17:52+01:00</time>
|
||||
</metadata>
|
||||
<trk>
|
||||
<trkseg>
|
||||
<trkpt lon="-8.2695876" lat="-70.6666343">
|
||||
<ele>790.000000</ele>
|
||||
<time>2023-04-08T16:03:08Z</time>
|
||||
<extensions>
|
||||
<gpxtpx:TrackPointExtension>
|
||||
<gpxtpx:hr>123</gpxtpx:hr>
|
||||
</gpxtpx:TrackPointExtension>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
<trkpt lon="-8.2653274" lat="-70.6670617">
|
||||
<ele>296.000000</ele>
|
||||
<time>2023-04-08T16:17:19Z</time>
|
||||
<extensions>
|
||||
<gpxtpx:TrackPointExtension>
|
||||
<gpxtpx:hr>56</gpxtpx:hr>
|
||||
</gpxtpx:TrackPointExtension>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
<trkseg/>
|
||||
<trkseg>
|
||||
<trkpt lon="-8.2653274" lat="-70.6670617">
|
||||
<time>2023-04-08T16:34:44Z</time>
|
||||
<ele>205.000000</ele>
|
||||
<extensions>
|
||||
<gpxtpx:TrackPointExtension>
|
||||
<gpxtpx:hr>85</gpxtpx:hr>
|
||||
</gpxtpx:TrackPointExtension>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
<trkpt lon="-8.2695876" lat="-70.6666343">
|
||||
<time>2023-04-08T16:56:57Z</time>
|
||||
<ele>209.000000</ele>
|
||||
<extensions>
|
||||
<gpxtpx:TrackPointExtension>
|
||||
<gpxtpx:hr>150</gpxtpx:hr>
|
||||
</gpxtpx:TrackPointExtension>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
36
app/src/test/resources/gpx-parser-test-order.gpx
Normal file
36
app/src/test/resources/gpx-parser-test-order.gpx
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
|
||||
<gpx version="1.1" creator="GBApplication.app().getNameAndVersion()"
|
||||
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd
|
||||
http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
|
||||
xmlns="http://www.topografix.com/GPX/1/1"
|
||||
xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1">
|
||||
<metadata>
|
||||
<name>Order Test</name>
|
||||
<author />
|
||||
<time>2023-04-07T16:15:20+01:00</time>
|
||||
</metadata>
|
||||
<trk>
|
||||
<trkseg>
|
||||
<trkpt lon="-8.2695876" lat="-70.6666343">
|
||||
<ele>790.000000</ele>
|
||||
<time>2023-04-08T16:03:08Z</time>
|
||||
<extensions>
|
||||
<gpxtpx:TrackPointExtension>
|
||||
<gpxtpx:hr>123</gpxtpx:hr>
|
||||
</gpxtpx:TrackPointExtension>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
<trkpt lon="-8.2653274" lat="-70.6670617">
|
||||
<time>2023-04-08T16:17:19Z</time>
|
||||
<ele>296.000000</ele>
|
||||
<extensions>
|
||||
<gpxtpx:TrackPointExtension>
|
||||
<gpxtpx:hr>56</gpxtpx:hr>
|
||||
</gpxtpx:TrackPointExtension>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
Loading…
Reference in New Issue
Block a user