Merge branch 'ubilabs--ios--develop' of ssh://sapsailing.com/home/trac/git into ubilabs--ios--develop--signup

This commit is contained in:
Raimund Wege
2017-08-10 17:01:01 +02:00
418 changed files with 175093 additions and 362 deletions
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ios</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>
@@ -0,0 +1,2 @@
CODE_SIGN_IDENTITY = iPhone Distribution
PROVISIONING_PROFILE_SPECIFIER = Q3U792Z354/SAP Sail InSight
+1 -2
View File
@@ -1,7 +1,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sap.sailing.mobile.ios</groupId>
<artifactId>SAPTracker</artifactId>
<version>1.0-SNAPSHOT</version>
@@ -21,4 +20,4 @@
</plugin>
</plugins>
</build>
</project>
</project>
@@ -19,6 +19,7 @@ public enum BoatClassMasterdata {
_470 ("470", true, 4.70, 1.68, BoatHullType.MONOHULL, true, "470er", "470M", "470W", "470 - M", "470 - W"),
_5O5 ("5O5", true, 5.03, 1.88, BoatHullType.MONOHULL, true, "505", "5o5", "505er", "5o5er"),
_5_5MR ("5.5mR", true, 9.50, 1.92, BoatHullType.MONOHULL, true, "5.5 Meter", "5.5 Metre", "5.5", "5.5M", "5.5-metre", "5.5 metre"),
_8MR ("8mR", true, 9.62, 2.45, BoatHullType.MONOHULL, true, "8 Meter", "8 Metre", "8M", "8-metre"),
A_CAT ("A-Catamaran", true, 5.49, 2.30, BoatHullType.CATAMARAN, false, "A-Cat", "ACat", "A-Class Catamaran"),
ALBIN_EXPRESS ("Albin Express", true, 7.77, 2.50, BoatHullType.MONOHULL, false),
ALBIN_BALLAD ("Albin Ballad", true, 9.12, 2.95, BoatHullType.MONOHULL, false),
@@ -38,13 +38,10 @@ import com.sap.sailing.domain.igtimiadapter.datatypes.SOG;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
import com.sap.sailing.domain.igtimiadapter.websocket.WebSocketConnectionManager;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.WindListener;
import com.sap.sailing.domain.tracking.impl.DynamicTrackImpl;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
import com.sap.sse.common.Util;
import com.sap.sse.common.scalablevalue.ScalableValue;
/**
* Receives Igtimi {@link Fix}es and tries to generate a {@link Wind} object from each {@link AWS} fix. For this to
@@ -181,16 +178,12 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
private Wind getWind(final TimePoint timePoint, String deviceSerialNumber) throws ClassNotFoundException, IOException, ParseException {
final Wind result;
com.sap.sse.common.Util.Pair<AWA, AWA> awaPair = getSurroundingFixes(getAwaTrack(deviceSerialNumber), timePoint);
Bearing awa = getAWA(timePoint, awaPair);
com.sap.sse.common.Util.Pair<AWS, AWS> awsPair = getSurroundingFixes(getAwsTrack(deviceSerialNumber), timePoint);
Speed aws = getAWS(timePoint, awsPair);
com.sap.sse.common.Util.Pair<GpsLatLong, GpsLatLong> gpsPair = getSurroundingFixes(getGpsTrack(deviceSerialNumber), timePoint);
Position pos = getPosition(timePoint, gpsPair);
Bearing awaFrom = getAwaTrack(deviceSerialNumber).getInterpolatedValue(timePoint, a->new ScalableBearing(a.getApparentWindAngle()));
Bearing awa = awaFrom==null?null:awaFrom.reverse();
Speed aws = getAwsTrack(deviceSerialNumber).getInterpolatedValue(timePoint, a->new ScalableSpeed(a.getApparentWindSpeed()));
Position pos = getGpsTrack(deviceSerialNumber).getInterpolatedValue(timePoint, g->new ScalablePosition(g.getPosition()));
if (pos != null) {
com.sap.sse.common.Util.Pair<HDG, HDG> hdgPair = getSurroundingFixes(getHdgTrack(deviceSerialNumber), timePoint);
com.sap.sse.common.Util.Pair<HDGM, HDGM> hdgmPair = getSurroundingFixes(getHdgmTrack(deviceSerialNumber), timePoint);
Bearing heading = getHeading(timePoint, hdgPair, hdgmPair, pos);
Bearing heading = getHeading(timePoint, deviceSerialNumber, pos);
if (awa != null && aws != null && heading != null) {
Bearing apparentWindDirection = heading.add(awa);
SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection);
@@ -218,10 +211,8 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
* So again, my personal preference would be to work with the data that should be the most accurate
* (COG/SOG) and consider algorithms that handle smoothing of that data best."
*/
com.sap.sse.common.Util.Pair<SOG, SOG> sogPair = getSurroundingFixes(getSogTrack(deviceSerialNumber), timePoint);
Speed sog = getSOG(timePoint, sogPair);
com.sap.sse.common.Util.Pair<COG, COG> cogPair = getSurroundingFixes(getCogTrack(deviceSerialNumber), timePoint);
Bearing cog = getCOG(timePoint, cogPair);
Speed sog = getSogTrack(deviceSerialNumber).getInterpolatedValue(timePoint, s->new ScalableSpeed(s.getSpeedOverGround()));
Bearing cog = getCogTrack(deviceSerialNumber).getInterpolatedValue(timePoint, c->new ScalableBearing(c.getCourseOverGround()));
if (sog != null && cog != null) {
SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog);
SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog);
@@ -238,153 +229,13 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
return result;
}
private Bearing getAWA(TimePoint timePoint, com.sap.sse.common.Util.Pair<AWA, AWA> awaPair) {
final Bearing awaFrom;
if (awaPair.getA() == null) {
if (awaPair.getB() == null) {
awaFrom = null;
} else {
awaFrom = awaPair.getB().getApparentWindAngle();
}
} else {
if (awaPair.getB() == null) {
awaFrom = awaPair.getA().getApparentWindAngle();
} else {
awaFrom = timeBasedAgerage(timePoint,
new ScalableBearing(awaPair.getA().getApparentWindAngle()), awaPair.getA().getTimePoint(),
new ScalableBearing(awaPair.getB().getApparentWindAngle()), awaPair.getB().getTimePoint());
}
}
return awaFrom == null ? null : awaFrom.reverse();
}
private Speed getAWS(TimePoint timePoint, com.sap.sse.common.Util.Pair<AWS, AWS> awsPair) {
final Speed aws;
if (awsPair.getA() == null) {
if (awsPair.getB() == null) {
aws = null;
} else {
aws = awsPair.getB().getApparentWindSpeed();
}
} else {
if (awsPair.getB() == null) {
aws = awsPair.getA().getApparentWindSpeed();
} else {
aws = timeBasedAgerage(timePoint,
new ScalableSpeed(awsPair.getA().getApparentWindSpeed()), awsPair.getA().getTimePoint(),
new ScalableSpeed(awsPair.getB().getApparentWindSpeed()), awsPair.getB().getTimePoint());
}
}
return aws;
}
private Speed getSOG(TimePoint timePoint, com.sap.sse.common.Util.Pair<SOG, SOG> sogPair) {
final Speed sog;
if (sogPair.getA() == null) {
if (sogPair.getB() == null) {
sog = null;
} else {
sog = sogPair.getB().getSpeedOverGround();
}
} else {
if (sogPair.getB() == null) {
sog = sogPair.getA().getSpeedOverGround();
} else {
sog = timeBasedAgerage(timePoint,
new ScalableSpeed(sogPair.getA().getSpeedOverGround()), sogPair.getA().getTimePoint(),
new ScalableSpeed(sogPair.getB().getSpeedOverGround()), sogPair.getB().getTimePoint());
}
}
return sog;
}
private Bearing getCOG(TimePoint timePoint, com.sap.sse.common.Util.Pair<COG, COG> cogPair) {
final Bearing sog;
if (cogPair.getA() == null) {
if (cogPair.getB() == null) {
sog = null;
} else {
sog = cogPair.getB().getCourseOverGround();
}
} else {
if (cogPair.getB() == null) {
sog = cogPair.getA().getCourseOverGround();
} else {
sog = timeBasedAgerage(timePoint,
new ScalableBearing(cogPair.getA().getCourseOverGround()), cogPair.getA().getTimePoint(),
new ScalableBearing(cogPair.getB().getCourseOverGround()), cogPair.getB().getTimePoint());
}
}
return sog;
}
private Position getPosition(TimePoint timePoint, com.sap.sse.common.Util.Pair<GpsLatLong, GpsLatLong> gpsPair) {
final Position pos;
if (gpsPair.getA() == null) {
if (gpsPair.getB() == null) {
pos = null;
} else {
pos = gpsPair.getB().getPosition();
}
} else {
if (gpsPair.getB() == null) {
pos = gpsPair.getA().getPosition();
} else {
pos = timeBasedAgerage(timePoint, new ScalablePosition(gpsPair.getA().getPosition()), gpsPair.getA()
.getTimePoint(), new ScalablePosition(gpsPair.getB().getPosition()), gpsPair.getB()
.getTimePoint());
}
}
return pos;
}
private Bearing getHDG(TimePoint timePoint, com.sap.sse.common.Util.Pair<HDG, HDG> hdgPair) {
final Bearing hdg;
if (hdgPair.getA() == null) {
if (hdgPair.getB() == null) {
hdg = null;
} else {
hdg = hdgPair.getB().getTrueHeading();
}
} else {
if (hdgPair.getB() == null) {
hdg = hdgPair.getA().getTrueHeading();
} else {
hdg = timeBasedAgerage(timePoint,
new ScalableBearing(hdgPair.getA().getTrueHeading()), hdgPair.getA().getTimePoint(),
new ScalableBearing(hdgPair.getB().getTrueHeading()), hdgPair.getB().getTimePoint());
}
}
return hdg;
}
private Bearing getHDGM(TimePoint timePoint, com.sap.sse.common.Util.Pair<HDGM, HDGM> hdgmPair) {
final Bearing hdgm;
if (hdgmPair.getA() == null) {
if (hdgmPair.getB() == null) {
hdgm = null;
} else {
hdgm = hdgmPair.getB().getMagnetigHeading();
}
} else {
if (hdgmPair.getB() == null) {
hdgm = hdgmPair.getA().getMagnetigHeading();
} else {
hdgm = timeBasedAgerage(timePoint,
new ScalableBearing(hdgmPair.getA().getMagnetigHeading()), hdgmPair.getA().getTimePoint(),
new ScalableBearing(hdgmPair.getB().getMagnetigHeading()), hdgmPair.getB().getTimePoint());
}
}
return hdgm;
}
private Bearing getHeading(TimePoint timePoint, com.sap.sse.common.Util.Pair<HDG, HDG> hdgPair, com.sap.sse.common.Util.Pair<HDGM, HDGM> hdgmPair, Position position) throws ClassNotFoundException, IOException, ParseException {
private Bearing getHeading(TimePoint timePoint, String deviceSerialNumber, Position position) throws ClassNotFoundException, IOException, ParseException {
final Bearing trueHeading;
Bearing hdg = getHDG(timePoint, hdgPair);
Bearing hdg = getHdgTrack(deviceSerialNumber).getInterpolatedValue(timePoint, h->new ScalableBearing(h.getTrueHeading()));
if (hdg != null) {
trueHeading = hdg;
} else {
Bearing hdgm = getHDGM(timePoint, hdgmPair);
Bearing hdgm = getHdgmTrack(deviceSerialNumber).getInterpolatedValue(timePoint, h->new ScalableBearing(h.getMagnetigHeading()));
if (hdgm != null) {
if (declinationService == null) {
trueHeading = hdgm;
@@ -405,25 +256,6 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
return trueHeading;
}
private <V, T> T timeBasedAgerage(TimePoint timePoint, ScalableValue<V, T> value1, TimePoint timePoint1, ScalableValue<V, T> value2, TimePoint timePoint2) {
final T acc;
if (timePoint1.equals(timePoint2)) {
acc = value1.add(value2).divide(2);
} else {
long timeDiff1 = Math.abs(timePoint1.asMillis() - timePoint.asMillis());
long timeDiff2 = Math.abs(timePoint2.asMillis() - timePoint.asMillis());
acc = value1.multiply(timeDiff2).add(value2.multiply(timeDiff1)).divide(timeDiff1 + timeDiff2);
}
return acc;
}
private <T extends Timed> com.sap.sse.common.Util.Pair<T, T> getSurroundingFixes(Track<T> track, TimePoint timePoint) {
T left = track.getLastFixAtOrBefore(timePoint);
T right = track.getFirstFixAtOrAfter(timePoint);
com.sap.sse.common.Util.Pair<T, T> result = new com.sap.sse.common.Util.Pair<>(left, right);
return result;
}
private DynamicTrack<AWA> getAwaTrack(String deviceSerialNumber) {
return getTrack(deviceSerialNumber, awaTrack);
}
@@ -17,6 +17,8 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Function;
import com.sap.sse.common.scalablevalue.ScalableValue;
/**
* Wrapper for a {@link RaceLog} which will ignore all calls trying to add an {@link RaceLogEvent}. All other
@@ -250,6 +252,12 @@ public class NoAddingRaceLogWrapper implements RaceLog {
public void revokeEvent(AbstractLogEventAuthor author, RaceLogEvent toRevoke) throws NotRevokableException {
innerRaceLog.revokeEvent(author, toRevoke);
}
@Override
public <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
Function<RaceLogEvent, ScalableValue<InternalType, ValueType>> converter) {
return innerRaceLog.getInterpolatedValue(timePoint, converter);
}
@Override
public int size() {
@@ -1,6 +1,10 @@
package com.sap.sailing.domain.statistics;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Speed;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Triple;
/**
* A {@link Statistics} object represents several statistical information.
@@ -41,4 +45,6 @@ public interface Statistics {
* @return the totally sailed {@link Distance}; never {@code null}
*/
Distance getDistanceTraveled();
Triple<Competitor, Speed, TimePoint> getMaxSpeed();
}
@@ -1,7 +1,11 @@
package com.sap.sailing.domain.statistics.impl;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.statistics.Statistics;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Triple;
public class StatisticsImpl implements Statistics {
@@ -12,6 +16,7 @@ public class StatisticsImpl implements Statistics {
private final long numberOfGPSFixes;
private final long numberOfWindFixes;
private final Distance distanceTraveled;
private final Triple<Competitor, Speed, TimePoint> maxSpeed;
/**
* @param distanceTraveled
@@ -20,7 +25,7 @@ public class StatisticsImpl implements Statistics {
* further {@code null} checks.
*/
public StatisticsImpl(int numberOfCompetitors, int numberOfRegattas, int numberOfRaces, int numberOfTrackedRaces,
long numberOfGPSFixes, long numberOfWindFixes, Distance distanceTraveled) {
long numberOfGPSFixes, long numberOfWindFixes, Distance distanceTraveled, Triple<Competitor, Speed, TimePoint> maxSpeed) {
super();
this.numberOfCompetitors = numberOfCompetitors;
this.numberOfRegattas = numberOfRegattas;
@@ -29,6 +34,7 @@ public class StatisticsImpl implements Statistics {
this.numberOfGPSFixes = numberOfGPSFixes;
this.numberOfWindFixes = numberOfWindFixes;
this.distanceTraveled = distanceTraveled == null ? Distance.NULL : distanceTraveled;
this.maxSpeed = maxSpeed;
}
@Override
@@ -65,4 +71,8 @@ public class StatisticsImpl implements Statistics {
public Distance getDistanceTraveled() {
return distanceTraveled;
}
public Triple<Competitor, Speed, TimePoint> getMaxSpeed() {
return maxSpeed;
}
}
@@ -4,9 +4,12 @@ import java.io.Serializable;
import java.util.Iterator;
import java.util.concurrent.locks.ReadWriteLock;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
import com.sap.sse.common.Util.Function;
import com.sap.sse.common.scalablevalue.ScalableValue;
/**
* A track records {@link Timed} items for an object of type <code>ItemType</code>. It allows clients to ask for a value
@@ -117,6 +120,27 @@ public interface Track<FixType extends Timed> extends Serializable {
*/
FixType getLastRawFix();
/**
* Interpolates an aspect of the fixes in this track for a given {@code timePoint}. If {@code timePoint} matches
* exactly a fix in this track, that fix is used. If this track is empty, {@code null} is returned. If the
* {@code timePoint} is after the last fix of this track, the last fix is used; if before the first fix, the first
* fix is used.
* <p>
*
* The fix(es) are converted to {@link ScalableValue}s using the {@code converter} which gives callers a choice
* which aspect of the fixes to project and interpolate. If more than one value results because two fixes (one
* before, one after) are used, linear interpolation based on the fixes' time points takes place.
* <p>
*
* Example: for a track of {@link GPSFixMoving} fixes the course over ground shall be determined for a given time
* point. The call would look like this:
* {@code getInterpolatedValue(timePoint, f->new ScalableBearing(f.getSpeed().getBearing()))}
*
* @return the projected interpolated value, typed by what the {@link ScalableValue#divide(double)} method returns.
*/
<InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
Function<FixType, ScalableValue<InternalType, ValueType>> converter);
/**
* Returns an iterator starting at the first fix after <code>startingAt</code> (or "at or after" in case
* <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the smoothened fixes (see
@@ -10,6 +10,9 @@ import com.sap.sailing.domain.tracking.Track;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
import com.sap.sse.common.Util.Function;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.scalablevalue.ScalableValue;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.impl.ArrayListNavigableSet;
@@ -251,6 +254,47 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
}
}
private Pair<FixType, FixType> getSurroundingFixes(TimePoint timePoint) {
FixType left = getLastFixAtOrBefore(timePoint);
FixType right = getFirstFixAtOrAfter(timePoint);
com.sap.sse.common.Util.Pair<FixType, FixType> result = new com.sap.sse.common.Util.Pair<>(left, right);
return result;
}
private <V, T> T timeBasedAverage(TimePoint timePoint, ScalableValue<V, T> value1, TimePoint timePoint1, ScalableValue<V, T> value2, TimePoint timePoint2) {
final T acc;
if (timePoint1.equals(timePoint2)) {
acc = value1.add(value2).divide(2);
} else {
long timeDiff1 = Math.abs(timePoint1.asMillis() - timePoint.asMillis());
long timeDiff2 = Math.abs(timePoint2.asMillis() - timePoint.asMillis());
acc = value1.multiply(timeDiff2).add(value2.multiply(timeDiff1)).divide(timeDiff1 + timeDiff2);
}
return acc;
}
@Override
public <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint, Function<FixType, ScalableValue<InternalType, ValueType>> converter) {
final ValueType result;
Pair<FixType, FixType> fixPair = getSurroundingFixes(timePoint);
if (fixPair.getA() == null) {
if (fixPair.getB() == null) {
result = null;
} else {
result = converter.get(fixPair.getB()).divide(1);
}
} else {
if (fixPair.getB() == null || fixPair.getA() == fixPair.getB()) {
result = converter.get(fixPair.getA()).divide(1);
} else {
result = timeBasedAverage(timePoint,
converter.get(fixPair.getA()), fixPair.getA().getTimePoint(),
converter.get(fixPair.getB()), fixPair.getB().getTimePoint());
}
}
return result;
}
@Override
public Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive) {
assertReadLock();
@@ -0,0 +1,19 @@
package com.sap.sailing.domain.common.test;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
public class ScalableBearingTest {
@Test
public void testTrivialScaling() {
Bearing b = new DegreeBearingImpl(123);
ScalableBearing sb = new ScalableBearing(b);
Bearing reducedScalableBearing = sb.divide(1);
assertEquals(b.getDegrees(), reducedScalableBearing.getDegrees(), 0.0001);
}
}
+17 -1
View File
@@ -41,12 +41,14 @@ import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.impl.MeterPerSecondSpeedWithDegreeBearingImpl;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableSpeed;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl;
import com.sap.sailing.domain.common.tracking.impl.CompactionNotPossibleException;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.impl.DistanceCache;
@@ -109,6 +111,20 @@ public class TrackTest {
track.addGPSFix(gpsFix5);
}
@Test
public void bearingInterpolationTest() {
TimePoint betweenFirstAndSecond = gpsFix1.getTimePoint().plus(gpsFix1.getTimePoint().until(gpsFix2.getTimePoint()).divide(2));
Bearing bearingBetweenFirstAndSecond = track.getInterpolatedValue(betweenFirstAndSecond, f->new ScalableBearing(f.getSpeed().getBearing()));
assertEquals(bearingBetweenFirstAndSecond.getDegrees(), gpsFix1.getSpeed().getBearing().middle(gpsFix2.getSpeed().getBearing()).getDegrees(), 0.00001);
}
@Test
public void speedInterpolationTest() {
TimePoint betweenFirstAndSecond = gpsFix1.getTimePoint().plus(gpsFix1.getTimePoint().until(gpsFix2.getTimePoint()).divide(2));
Speed speedBetweenFirstAndSecond = track.getInterpolatedValue(betweenFirstAndSecond, f->new ScalableSpeed(f.getSpeed()));
assertEquals(speedBetweenFirstAndSecond.getKnots(), (gpsFix1.getSpeed().getKnots()+gpsFix2.getSpeed().getKnots())/2, 0.01);
}
/**
* Tests for a incorrect method of estimating Positions
*/
@@ -147,4 +147,11 @@
version="1.9.4"
unpack="false"/>
<plugin
id="net.sf.marineapi"
download-size="0"
install-size="0"
version="0.5.0.qualifier"
unpack="false"/>
</feature>
+7
View File
@@ -388,4 +388,11 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al
version="0.0.0"
unpack="false"/>
<plugin
id="com.sap.sailing.nmeaconnector"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
</feature>
@@ -2,7 +2,6 @@ package com.sap.sailing.gwt.autoplay.client.places.screens.afterliveraceloop.boa
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.Label;
import com.sap.sailing.domain.common.Distance;
@@ -103,7 +102,6 @@ public class RaceEndWithBoatsPresenterImpl extends AutoPlayPresenterConfigured<A
raceColumn = column;
}
}
GWT.debugger();
List<CompetitorDTO> sortedCompetitors = leaderboardPanel.getLeaderboard()
.getCompetitorsFromBestToWorst(raceColumn);
@@ -34,6 +34,7 @@ public class BoatClassImageResolver {
boatClassIconsMap.put(BoatClassMasterdata._49ER.getDisplayName(), imageResources._49erIcon());
boatClassIconsMap.put(BoatClassMasterdata._5O5.getDisplayName(), imageResources._505mIcon());
boatClassIconsMap.put(BoatClassMasterdata._5_5MR.getDisplayName(), imageResources._5_5MRIcon());
boatClassIconsMap.put(BoatClassMasterdata._8MR.getDisplayName(), imageResources._8MRIcon());
boatClassIconsMap.put(BoatClassMasterdata.A_CAT.getDisplayName(), imageResources.A_CatIcon());
boatClassIconsMap.put(BoatClassMasterdata.ALBIN_EXPRESS.getDisplayName(), imageResources.AlbinExpressIcon());
boatClassIconsMap.put(BoatClassMasterdata.ALBIN_BALLAD.getDisplayName(), imageResources.AlbinBalladIcon());
@@ -40,6 +40,9 @@ public interface BoatClassImageResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/5.5mR.png")
ImageResource _5_5MRIcon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/8MR.png")
ImageResource _8MRIcon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/A_CAT.png")
ImageResource A_CatIcon();
@@ -60,14 +60,14 @@ public class GetEventOverviewStageAction implements SailingAction<ResultWithTTL<
@Override
@GwtIncompatible
public ResultWithTTL<EventOverviewStageDTO> execute(SailingDispatchContext context) {
TimePoint now = MillisecondsTimePoint.now();
Event event = context.getRacingEventService().getEvent(eventId);
EventState state = HomeServiceUtil.calculateEventState(event);
final TimePoint now = MillisecondsTimePoint.now();
final Event event = context.getRacingEventService().getEvent(eventId);
final EventState state = HomeServiceUtil.calculateEventState(event);
long ttl = Duration.ONE_MINUTE.times(5).asMillis();
if(state == EventState.RUNNING) {
ttl = Duration.ONE_MINUTE.times(2).asMillis();
}
if(state == EventState.UPCOMING || state == EventState.PLANNED) {
if (isUpcomingOrPlanned(event, state)) {
ttl = Math.min(ttl, now.until(event.getStartDate()).asMillis());
}
@@ -94,7 +94,7 @@ public class GetEventOverviewStageAction implements SailingAction<ResultWithTTL<
}
// Show countdown for planned or upcoming events
if(state == EventState.UPCOMING || state == EventState.PLANNED) {
if (isUpcomingOrPlanned(event, state)) {
return new EventOverviewTickerStageDTO(event.getStartDate().asDate(), event.getName(), imageUrl);
}
return new EventOverviewTickerStageDTO(null, null, imageUrl);
@@ -153,6 +153,12 @@ public class GetEventOverviewStageAction implements SailingAction<ResultWithTTL<
// return new EventOverviewTickerStageDTO(null, null, stageImageUrl);
}
@GwtIncompatible
private boolean isUpcomingOrPlanned(Event event, EventState state) {
final TimePoint startDate = event.getStartDate();
return startDate != null && (state == EventState.UPCOMING || state == EventState.PLANNED);
}
@Override
public void cacheInstanceKey(StringBuilder key) {
key.append(eventId).append("_").append(useTeaserImage);
@@ -4,6 +4,7 @@ import com.google.gwt.core.shared.GwtIncompatible;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorDTO;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.gwt.dispatch.shared.commands.DTO;
@@ -15,8 +16,8 @@ public class EventStatisticsDTO implements DTO {
private int trackedRacesCount;
private long numberOfGPSFixes;
private long numberOfWindFixes;
private String competitorInfo;
private Double competitorSpeedInKnots;
private SimpleCompetitorDTO fastestCompetitor;
private Double fastestCompetitorSpeedInKnots;
private Distance totalDistanceTraveled;
@SuppressWarnings("unused")
@@ -35,8 +36,8 @@ public class EventStatisticsDTO implements DTO {
this.numberOfGPSFixes = numberOfGPSFixes;
this.numberOfWindFixes = numberOfWindFixes;
if (maxSpeed != null) {
this.competitorInfo = maxSpeed.getA().getName();
this.competitorSpeedInKnots = maxSpeed.getB().getKnots();
this.fastestCompetitor = new SimpleCompetitorDTO(maxSpeed.getA());
this.fastestCompetitorSpeedInKnots = maxSpeed.getB().getKnots();
}
this.totalDistanceTraveled = totalDistanceTraveled;
}
@@ -80,13 +81,13 @@ public class EventStatisticsDTO implements DTO {
public long getNumberOfWindFixes() {
return numberOfWindFixes;
}
public String getCompetitorInfo() {
return competitorInfo;
public SimpleCompetitorDTO getFastestCompetitor() {
return fastestCompetitor;
}
public Double getCompetitorSpeed() {
return competitorSpeedInKnots;
public Double getFastestCompetitorSpeedInKnots() {
return fastestCompetitorSpeedInKnots;
}
public Distance getTotalDistanceTraveled() {
@@ -5,8 +5,13 @@ import java.util.List;
import com.google.gwt.core.shared.GwtIncompatible;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.statistics.Statistics;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorDTO;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Triple;
public class EventListYearDTO implements IsSerializable {
private int year;
@@ -18,6 +23,9 @@ public class EventListYearDTO implements IsSerializable {
private long numberOfGPSFixes;
private long numberOfWindFixes;
private Distance distanceTraveled;
private SimpleCompetitorDTO fastestCompetitor;
private Double fastestCompetitorSpeedInKnots;
private ArrayList<EventListEventDTO> events = new ArrayList<>();
@SuppressWarnings("unused")
@@ -57,6 +65,11 @@ public class EventListYearDTO implements IsSerializable {
numberOfGPSFixes = statistics.getNumberOfGPSFixes();
numberOfWindFixes = statistics.getNumberOfWindFixes();
distanceTraveled = statistics.getDistanceTraveled();
final Triple<Competitor, Speed, TimePoint> maxSpeed = statistics.getMaxSpeed();
if (maxSpeed != null) {
fastestCompetitor = new SimpleCompetitorDTO(maxSpeed.getA());
fastestCompetitorSpeedInKnots = maxSpeed.getB().getKnots();
}
}
public int getEventCount() {
@@ -90,4 +103,12 @@ public class EventListYearDTO implements IsSerializable {
public Distance getDistanceTraveled() {
return distanceTraveled;
}
public SimpleCompetitorDTO getFastestCompetitor() {
return fastestCompetitor;
}
public Double getFastestCompetitorSpeedInKnots() {
return fastestCompetitorSpeedInKnots;
}
}
@@ -156,4 +156,5 @@
font-size: 0.933333333333333rem;
padding: 0.714285714285714em 1.071428571428571em;
color: #666;
min-height: 2.533333333333333rem;
}
@@ -69,4 +69,9 @@ img.statisticsbox_item_icon {
padding: 0.2em;
border: 1px solid #dedede;
border-radius: 0.333333333333333em;
display: inline-block;
max-width: 35%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@@ -23,7 +23,9 @@ class StatisticsBoxItem extends UIObject {
setElement(uiBinder.createAndBindUi(this));
iconUi.setSrc(iconUrl);
nameUi.setInnerText(name);
valueUi.setInnerText(payload == null ? "n/a" : String.valueOf(payload));
final String text = payload == null ? "n/a" : String.valueOf(payload);
valueUi.setInnerText(text);
valueUi.setTitle(text);
}
}
@@ -56,4 +56,9 @@ img.statisticsbox_content_item_icon {
background: #ebebeb;
border: 1px solid #dedede;
border-radius: 0.384615384615385em;
display: inline-block;
max-width: 35%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@@ -23,7 +23,9 @@ class StatisticsBoxItem extends Widget {
setElement(uiBinder.createAndBindUi(this));
iconUi.setSrc(iconUrl);
nameUi.setInnerText(name);
valueUi.setInnerText(payload == null ? "n/a" : String.valueOf(payload));
final String text = payload == null ? "n/a" : String.valueOf(payload);
valueUi.setInnerText(text);
valueUi.setTitle(text);
}
}
@@ -143,18 +143,20 @@ public final class EventActionUtil {
* @return The calculated {@link Duration time to live}
*/
public static Duration calculateTtlForNonLiveEvent(Event event, EventState eventState) {
TimePoint now = MillisecondsTimePoint.now();
if(eventState == EventState.UPCOMING || eventState == EventState.PLANNED) {
Duration tillStart = now.until(event.getStartDate());
double hoursTillStart = tillStart.asHours();
long ttl = Duration.ONE_HOUR.asMillis();
if(hoursTillStart < 36) {
ttl = Duration.ONE_MINUTE.times(30).asMillis();
final TimePoint startDate = event.getStartDate();
if (startDate != null) {
final Duration tillStart = MillisecondsTimePoint.now().until(startDate);
final double hoursTillStart = tillStart.asHours();
if (hoursTillStart < 36) {
ttl = Duration.ONE_MINUTE.times(30).asMillis();
}
if (hoursTillStart < 3) {
ttl = Duration.ONE_MINUTE.times(15).asMillis();
}
ttl = Math.min(ttl, tillStart.asMillis());
}
if(hoursTillStart < 3) {
ttl = Duration.ONE_MINUTE.times(15).asMillis();
}
ttl = Math.min(ttl, tillStart.asMillis());
return new MillisecondsDurationImpl(ttl);
}
if(eventState == EventState.FINISHED) {
@@ -1,6 +1,7 @@
package com.sap.sailing.gwt.home.shared.partials.statistics;
import com.google.gwt.i18n.client.NumberFormat;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorDTO;
import com.sap.sailing.gwt.ui.client.StringMessages;
public abstract class AbstractStatisticsBoxPresenter {
@@ -12,6 +13,18 @@ public abstract class AbstractStatisticsBoxPresenter {
this.view = view;
}
public void addCompetitorItem(String iconUrl, String name, SimpleCompetitorDTO competitor) {
if (competitor != null) {
addItem(iconUrl, name, competitor.getSailID() != null ? competitor.getSailID() : competitor.getName());
}
}
public void addKnotsItem(String iconUrl, String name, Double speedInKnots) {
if (speedInKnots != null) {
addItem(iconUrl, name, StringMessages.INSTANCE.knotsValue(speedInKnots));
}
}
public void addItemWithCompactFormat(String iconUrl, String name, Double payload) {
if (payload != null && payload != 0) {
addItem(iconUrl, name, compactNumber(payload));
@@ -1,6 +1,13 @@
package com.sap.sailing.gwt.home.shared.partials.statistics;
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.*;
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_COMPATITORS_COUNT;
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_FASTEST_SAILOR;
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_RACES_COUNT;
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_RAW_GPS_FIX;
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_REGATTAS_FOUGHT;
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_SUM_MILES;
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_TRACKED_COUNT;
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_WIND_FIX;
import com.sap.sailing.gwt.home.communication.event.statistics.EventStatisticsDTO;
import com.sap.sailing.gwt.home.shared.refresh.RefreshableWidget;
@@ -29,7 +36,7 @@ public class EventStatisticsBoxPresenter extends AbstractStatisticsBoxPresenter
addItemWithCompactFormat(ICON_RAW_GPS_FIX, MSG.numberOfGPSFixes(), statistics.getNumberOfGPSFixes());
addItemWithCompactFormat(ICON_WIND_FIX, MSG.numberWindFixes(), statistics.getNumberOfWindFixes());
addItemWithCompactFormat(ICON_SUM_MILES, MSG.sailedMiles(), statistics.getTotalDistanceTraveled() == null ? null : statistics.getTotalDistanceTraveled().getSeaMiles());
addItemWithCompactFormat(ICON_FASTEST_SAILOR, MSG.fastestSailor() + ": "
+ statistics.getCompetitorInfo(), statistics.getCompetitorSpeed());
addCompetitorItem(ICON_FASTEST_SAILOR, MSG.fastestSailor(), statistics.getFastestCompetitor());
addKnotsItem(ICON_FASTEST_SAILOR, MSG.highestSpeed(), statistics.getFastestCompetitorSpeedInKnots());
}
}
@@ -22,5 +22,7 @@ public class YearStatisticsBoxPresenter extends AbstractStatisticsBoxPresenter {
addItemWithCompactFormat(ICON_RAW_GPS_FIX, MSG.numberOfGPSFixes(), statistics.getNumberOfGPSFixes());
addItemWithCompactFormat(ICON_WIND_FIX, MSG.numberWindFixes(), statistics.getNumberOfWindFixes());
addItemWithCompactFormat(ICON_SUM_MILES, MSG.sailedMiles(), statistics.getDistanceTraveled().getNauticalMiles());
addCompetitorItem(ICON_FASTEST_SAILOR, MSG.fastestSailor(), statistics.getFastestCompetitor());
addKnotsItem(ICON_FASTEST_SAILOR, MSG.highestSpeed(), statistics.getFastestCompetitorSpeedInKnots());
}
}
@@ -15,10 +15,10 @@ public class EventDatesFormatterUtil {
private final static DefaultEventDatesFormatter FORMATTER = GWT.create(DefaultEventDatesFormatter.class);
public static String formatDateRangeWithYear(Date from, Date to) {
return FORMATTER.formatDateRangeWithYear(from, to);
return (from == null || to == null) ? "" : FORMATTER.formatDateRangeWithYear(from, to);
}
public static String formatDateRangeWithoutYear(Date from, Date to) {
return FORMATTER.formatDateRangeWithoutYear(from, to);
return (from == null || to == null) ? "" : FORMATTER.formatDateRangeWithoutYear(from, to);
}
}
@@ -170,9 +170,11 @@ public class RegattaRaceStatesComponent extends AbstractCompositeComponent<Regat
@SuppressWarnings("unchecked")
private void updateTable(List<RegattaOverviewEntryDTO> newEntries) {
allEntries = newEntries;
String lastCourseAreaName = null;
String lastRegattaName = null;
String lastCourseName = null;
String firstCourseAreaName = null;
String firstRegattaName = null;
String firstCourseName = null;
RaceInfoDTO firstCourseRegattaOverviewEntry = null;
RegattaOverviewEntryDTO firstRegattaNameEntry = null;
String lastBoatClass = null;
boolean canRemoveCourseArea = true;
boolean canRemoveCourse = true;
@@ -180,68 +182,82 @@ public class RegattaRaceStatesComponent extends AbstractCompositeComponent<Regat
boolean canRemoveBoatClass = true;
boolean canRemoveLastUpdate = true;
boolean canRemoveProtestTime = true;
boolean isAppending = false;
RegattaOverviewEntryDTO firstEntry = null;
for (RegattaOverviewEntryDTO entryDTO : allEntries) {
if (firstEntry == null) {
firstEntry = entryDTO;
}
if (lastRegattaName == null) {
lastRegattaName = entryDTO.regattaDisplayName;
} else if (canRemoveRegatta && !lastRegattaName.equals(entryDTO.regattaDisplayName)) {
canRemoveRegatta = false;
}
if (canRemoveLastUpdate && timePassedInSeconds(entryDTO.raceInfo.lastUpdateTime) <= HIDE_COL_TIME_THRESHOLD) {
boolean first = true;
for (RegattaOverviewEntryDTO loopEntryDTO : allEntries) {
final RaceInfoDTO loopRaceInfo = loopEntryDTO.raceInfo;
if (canRemoveLastUpdate && timePassedInSeconds(loopRaceInfo.lastUpdateTime) <= HIDE_COL_TIME_THRESHOLD) {
canRemoveLastUpdate = false;
}
if (canRemoveProtestTime && entryDTO.raceInfo.protestFinishTime != null
&& timePassedInSeconds(entryDTO.raceInfo.protestFinishTime) <= HIDE_COL_TIME_THRESHOLD) {
if (canRemoveProtestTime && loopRaceInfo.protestFinishTime != null
&& timePassedInSeconds(loopRaceInfo.protestFinishTime) <= HIDE_COL_TIME_THRESHOLD) {
canRemoveProtestTime = false;
}
if (lastCourseAreaName == null) {
lastCourseAreaName = entryDTO.courseAreaName;
} else if (canRemoveCourseArea && !lastCourseAreaName.equals(entryDTO.courseAreaName)) {
canRemoveCourseArea = false;
}
if (lastCourseName == null) {
lastCourseName = entryDTO.raceInfo.lastCourseName;
} else if (canRemoveCourse && !lastCourseName.equals(entryDTO.raceInfo.lastCourseName)) {
canRemoveCourse = false;
}
if (lastBoatClass == null) {
lastBoatClass = entryDTO.boatClassName;
} else if (canRemoveBoatClass && !lastBoatClass.equals(entryDTO.boatClassName)) {
canRemoveBoatClass = false;
if (first) {
firstRegattaName = loopEntryDTO.regattaDisplayName;
firstRegattaNameEntry = loopEntryDTO;
firstCourseAreaName = loopEntryDTO.courseAreaName;
firstCourseName = loopRaceInfo.lastCourseName;
firstCourseRegattaOverviewEntry = loopRaceInfo;
lastBoatClass = loopEntryDTO.boatClassName;
} else {
if (canRemoveRegatta && !Util.equalsWithNull(firstRegattaName, loopEntryDTO.regattaDisplayName)) {
canRemoveRegatta = false;
}
if (canRemoveCourseArea && !Util.equalsWithNull(firstCourseAreaName, loopEntryDTO.courseAreaName)) {
canRemoveCourseArea = false;
}
if (canRemoveCourse && !Util.equalsWithNull(firstCourseName, loopRaceInfo.lastCourseName)
&& !Util.equalsWithNull(firstCourseRegattaOverviewEntry.lastCourseDesign,
loopRaceInfo.lastCourseDesign)) {
canRemoveCourse = false;
}
if (canRemoveBoatClass && !Util.equalsWithNull(lastBoatClass, loopEntryDTO.boatClassName)) {
canRemoveBoatClass = false;
}
}
first = false;
}
final RegattaOverviewEntryDTO entryForRepeatedInfos = firstEntry;
// final RegattaOverviewEntryDTO entryForRepeatedInfos = firstEntry;
repeatedInfoLabel.clear();
isAppending |= collectRepeatedInfos(stringMessages.regatta(), lastRegattaName, canRemoveRegatta,
boolean isAppending = false;
RegattaOverviewEntryDTO _firstRegattaNameEntry = firstRegattaNameEntry;
isAppending |= collectRepeatedInfos(stringMessages.regatta(), firstRegattaName, canRemoveRegatta,
repeatedInfoLabel, isAppending, new Command() {
@Override
public void execute() {
Window.open(createRegattaLink(entryForRepeatedInfos), "_blank", "");
Window.open(createRegattaLink(_firstRegattaNameEntry), "_blank", "");
}
});
// private boolean collectRepeatedInfos(String label, String info,
// boolean canRemove, FlowPanel panel, boolean append,
// final Command linkAction) {
isAppending |= collectRepeatedInfos(stringMessages.courseArea(), lastCourseAreaName, canRemoveCourseArea,
isAppending |= collectRepeatedInfos(stringMessages.courseArea(), firstCourseAreaName, canRemoveCourseArea,
repeatedInfoLabel,
isAppending, null);
isAppending |= collectRepeatedInfos(stringMessages.course(), lastCourseName, canRemoveCourse, repeatedInfoLabel,
final RaceInfoDTO _lastCourseEntry = firstCourseRegattaOverviewEntry;
isAppending |= collectRepeatedInfos(stringMessages.course(), firstCourseName, canRemoveCourse,
repeatedInfoLabel,
isAppending, new Command() {
@Override
public void execute() {
raceCourseClicked(entryForRepeatedInfos);
raceCourseClicked(_lastCourseEntry);
}
});
isAppending |= collectRepeatedInfos(stringMessages.boatClass(), lastBoatClass, canRemoveBoatClass,
repeatedInfoLabel,
isAppending, null);
isAppending |= collectRepeatedInfos("", "", canRemoveLastUpdate, repeatedInfoLabel, isAppending, null);
isAppending |= collectRepeatedInfos("", "", canRemoveProtestTime, repeatedInfoLabel, isAppending, null);
LinkedList<ColumnSortInfo> sortInfos = new LinkedList<ColumnSortList.ColumnSortInfo>();
if (table != null) {
ColumnSortList columnSortList = table.getColumnSortList();
@@ -551,7 +567,7 @@ public class RegattaRaceStatesComponent extends AbstractCompositeComponent<Regat
raceCourseColumn.setFieldUpdater(new FieldUpdater<RegattaOverviewEntryDTO, SafeHtml>() {
@Override
public void update(int index, RegattaOverviewEntryDTO object, SafeHtml value) {
raceCourseClicked(object);
raceCourseClicked(object.raceInfo);
}
});
// raceCourseColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
@@ -847,10 +863,10 @@ public class RegattaRaceStatesComponent extends AbstractCompositeComponent<Regat
return result;
}
private void raceCourseClicked(RegattaOverviewEntryDTO object) {
RaceCourseDTO courseDTO = object.raceInfo.lastCourseDesign;
private void raceCourseClicked(RaceInfoDTO raceInfo) {
RaceCourseDTO courseDTO = raceInfo.lastCourseDesign;
if (courseDTO != null && courseDTO.waypoints.size() > 0) {
DialogBox courseViewDialogBox = createCourseViewDialogBox(object.raceInfo);
DialogBox courseViewDialogBox = createCourseViewDialogBox(raceInfo);
courseViewDialogBox.center();
courseViewDialogBox.setGlassEnabled(true);
courseViewDialogBox.setAnimationEnabled(true);
@@ -28,16 +28,18 @@ public class EventListDataCalculator implements EventVisitor {
@Override
public void visit(EventBase event, boolean onRemoteServer, URL baseURL) {
EventListEventDTO eventDTO = HomeServiceUtil.convertToEventListDTO(event, baseURL, onRemoteServer, service);
if (HomeServiceUtil.calculateEventState(event) != EventState.UPCOMING && HomeServiceUtil.isFakeSeries(event)) {
String seriesName = HomeServiceUtil.getSeriesName(event);
EventListEventDTO latestEvent = lastestEventPerSeries.get(seriesName);
if (latestEvent == null || latestEvent.getStartDate().before(eventDTO.getStartDate())) {
lastestEventPerSeries.put(seriesName, eventDTO);
if (event.getStartDate() != null) {
EventListEventDTO eventDTO = HomeServiceUtil.convertToEventListDTO(event, baseURL, onRemoteServer, service);
if (HomeServiceUtil.calculateEventState(event) != EventState.UPCOMING && HomeServiceUtil.isFakeSeries(event)) {
String seriesName = HomeServiceUtil.getSeriesName(event);
EventListEventDTO latestEvent = lastestEventPerSeries.get(seriesName);
if (latestEvent == null || latestEvent.getStartDate().before(eventDTO.getStartDate())) {
lastestEventPerSeries.put(seriesName, eventDTO);
}
increaseNumberOfEvents(seriesName);
} else {
addEventToResults(eventDTO);
}
increaseNumberOfEvents(seriesName);
} else {
addEventToResults(eventDTO);
}
}
@@ -21,19 +21,20 @@ public class EventStageCandidateCalculator implements EventVisitor {
@Override
public void visit(EventBase event, boolean onRemoteServer, URL baseURL) {
EventHolder holder = new EventHolder(event, onRemoteServer, baseURL);
if (now.after(event.getStartDate()) && now.before(event.getEndDate())) {
featuredEvents.add(new Pair<StageEventType, EventHolder>(StageEventType.RUNNING, holder));
} else if (event.getStartDate().after(now) &&
event.getStartDate().before(now.plus(Duration.ONE_WEEK.times(4)))) {
// This ensures that no events appear on the stage that do not have their leaderboards configured
// as the event page only shows a placeholder message in this stage
if(HomeServiceUtil.hasRegattaData(event)) {
featuredEvents.add(new Pair<StageEventType, EventHolder>(StageEventType.UPCOMING_SOON, holder));
final TimePoint startDate = event.getStartDate(), endDate = event.getEndDate();
if (startDate != null) {
final EventHolder holder = new EventHolder(event, onRemoteServer, baseURL);
if (now.after(startDate) && (endDate == null || now.before(endDate))) {
featuredEvents.add(new Pair<StageEventType, EventHolder>(StageEventType.RUNNING, holder));
} else if (startDate.after(now) && startDate.before(now.plus(Duration.ONE_WEEK.times(4)))) {
// This ensures that no events appear on the stage that do not have their leaderboards configured
// as the event page only shows a placeholder message in this stage
if (HomeServiceUtil.hasRegattaData(event)) {
featuredEvents.add(new Pair<StageEventType, EventHolder>(StageEventType.UPCOMING_SOON, holder));
}
} else if (endDate != null && endDate.before(now) && endDate.after(now.minus(Duration.ONE_YEAR))) {
featuredEvents.add(new Pair<StageEventType, EventHolder>(StageEventType.POPULAR, holder));
}
} else if (event.getEndDate().before(now) &&
event.getEndDate().after(now.minus(Duration.ONE_YEAR))) {
featuredEvents.add(new Pair<StageEventType, EventHolder>(StageEventType.POPULAR, holder));
}
}
@@ -97,11 +97,16 @@ public final class HomeServiceUtil {
}
public static EventState calculateEventState(EventBase event) {
TimePoint now = MillisecondsTimePoint.now();
if (now.before(event.getStartDate())) {
final TimePoint startDate = event.getStartDate();
if (startDate == null) {
return EventState.PLANNED;
}
final TimePoint now = MillisecondsTimePoint.now();
if (now.before(startDate)) {
return event.isPublic() ? EventState.UPCOMING : EventState.PLANNED;
}
if (now.after(event.getEndDate())) {
final TimePoint endDate = event.getEndDate();
if (endDate != null && now.after(endDate)) {
return EventState.FINISHED;
}
return EventState.RUNNING;
@@ -354,8 +359,8 @@ public final class HomeServiceUtil {
public static void mapToMetadataDTO(EventBase event, EventMetadataDTO dto, RacingEventService service) {
mapToReferenceDTO(event, dto, service);
dto.setStartDate(event.getStartDate().asDate());
dto.setEndDate(event.getEndDate().asDate());
dto.setStartDate(event.getStartDate() == null ? null : event.getStartDate().asDate());
dto.setEndDate(event.getEndDate() == null ? null : event.getEndDate().asDate());
dto.setState(HomeServiceUtil.calculateEventState(event));
dto.setVenue(event.getVenue().getName());
if(HomeServiceUtil.isFakeSeries(event)) {
@@ -24,8 +24,9 @@ public class RecentEventsCalculator implements EventVisitor {
@Override
public void visit(EventBase event, boolean onRemoteServer, URL baseURL) {
EventHolder holder = new EventHolder(event, onRemoteServer, baseURL);
if (event.getEndDate().before(now) && event.getEndDate().after(now.minus(Duration.ONE_YEAR))) {
final EventHolder holder = new EventHolder(event, onRemoteServer, baseURL);
final TimePoint endDate = event.getEndDate();
if (endDate != null && endDate.before(now) && endDate.after(now.minus(Duration.ONE_YEAR))) {
recentEventsOfLast12Month.add(holder);
}
}
@@ -82,12 +82,13 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
*/
public class WindPanel extends FormPanel implements RegattasDisplayer, WindShower {
private static final String EXPEDITON_IMPORT_PARAMETER_RACES = "races";
private static final String WIND_IMPORT_PARAMETER_RACES = "races";
private static final String EXPEDITON_IMPORT_PARAMETER_BOAT_ID = "boatId";
private static final String URL_SAILINGSERVER_EXPEDITION_IMPORT = "/../../sailingserver/expedition-import";
private static final String URL_SAILINGSERVER_GRIB_IMPORT = "/../../sailingserver/grib-wind-import";
private static final String URL_SAILINGSERVER_NMEA_IMPORT = "/../../sailingserver/nmea-wind-import";
private final SailingServiceAsync sailingService;
private final ErrorReporter errorReporter;
@@ -260,8 +261,8 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
mainPanel.add(createExpeditionWindImportPanel());
mainPanel.add(createGribWindImportPanel());
mainPanel.add(createNmeaWindImportPanel());
mainPanel.add(createIgtimiWindImportPanel(mainPanel));
}
private CaptionPanel createIgtimiWindImportPanel(VerticalPanel mainPanel) {
@@ -345,7 +346,7 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
}
/**
* Creates a file upload form that works with {@code AbstractWindImportServlet} on the server side. In particular,
* the race selection is managed in a hidden field named {@link #EXPEDITON_IMPORT_PARAMETER_RACES}, and the
* the race selection is managed in a hidden field named {@link #WIND_IMPORT_PARAMETER_RACES}, and the
* {@code upload} element is the MIME multipart file upload form element. The form uses {@code POST} as its method.
*
* The submit button is initially disabled. Callers can implement their own logic based, e.g., on event handlers they
@@ -377,7 +378,7 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
if (multi) {
fileUpload.getElement().setAttribute("multiple", "multiple");
}
final Hidden hiddenRacesField = new Hidden(EXPEDITON_IMPORT_PARAMETER_RACES);
final Hidden hiddenRacesField = new Hidden(WIND_IMPORT_PARAMETER_RACES);
formContentPanel.add(hiddenRacesField);
formContentPanel.add(fileUpload);
formContentPanel.add(submitButton);
@@ -413,21 +414,24 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
public void onSubmitComplete(SubmitCompleteEvent event) {
importResultPanel.clear();
String windImportResultJson = event.getResults();
WindImportResult windImportResult = WindImportResult.fromJson(windImportResultJson);
JsArray<RaceEntry> raceEntries = windImportResult.getRaceEntries();
Label resultHeader = new Label(stringMessages.windImport_ResultHeader(String.valueOf(windImportResult.getFirst()),
String.valueOf(windImportResult.getLast()), raceEntries.length()));
importResultPanel.add(resultHeader);
if (windImportResult.getError() != null) {
Label errorText = new Label(stringMessages.windImport_ResultError(windImportResult.getError()));
importResultPanel.add(errorText);
}
for (int i = 0; i < raceEntries.length(); i++) {
RaceEntry raceEntry = raceEntries.get(i);
Label entryText = new Label(stringMessages.windImport_ResultEntry(raceEntry.getRaceName(), raceEntry.getRegattaName(),
raceEntry.getCount(), String.valueOf(raceEntry.getFirst()), String.valueOf(raceEntry.getLast())));
importResultPanel.add(entryText);
try {
WindImportResult windImportResult = WindImportResult.fromJson(windImportResultJson);
JsArray<RaceEntry> raceEntries = windImportResult.getRaceEntries();
Label resultHeader = new Label(stringMessages.windImport_ResultHeader(String.valueOf(windImportResult.getFirst()),
String.valueOf(windImportResult.getLast()), raceEntries.length()));
importResultPanel.add(resultHeader);
if (windImportResult.getError() != null) {
Label errorText = new Label(stringMessages.windImport_ResultError(windImportResult.getError()));
importResultPanel.add(errorText);
}
for (int i = 0; i < raceEntries.length(); i++) {
RaceEntry raceEntry = raceEntries.get(i);
Label entryText = new Label(stringMessages.windImport_ResultEntry(raceEntry.getRaceName(), raceEntry.getRegattaName(),
raceEntry.getCount(), String.valueOf(raceEntry.getFirst()), String.valueOf(raceEntry.getLast())));
importResultPanel.add(entryText);
}
} catch (Exception e) {
errorReporter.reportError(stringMessages.windImport_ResultError(e.getMessage()));
}
}
});
@@ -485,6 +489,25 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
return windImportRootPanel;
}
private CaptionPanel createNmeaWindImportPanel() {
final WindImportFileUploadForm formAndFileUploadAndSubmitButton = createWindImportFileUploadForm(URL_SAILINGSERVER_NMEA_IMPORT, /* multi */ true);
CaptionPanel windImportRootPanel = new CaptionPanel(stringMessages.nmeaWindImport_Title());
VerticalPanel windImportContentPanel = new VerticalPanel();
windImportRootPanel.add(windImportContentPanel);
final FormPanel form = formAndFileUploadAndSubmitButton.getFormPanel();
windImportContentPanel.add(form);
VerticalPanel formContentPanel = formAndFileUploadAndSubmitButton.getFormContentPanel();
HorizontalPanel inputPanel = new HorizontalPanel();
formContentPanel.add(inputPanel);
final Panel importResultPanel = new VerticalPanel();
windImportContentPanel.add(importResultPanel);
formAndFileUploadAndSubmitButton.getSubmitButton().setEnabled(true);
final FileUpload fileUpload = formAndFileUploadAndSubmitButton.getFileUpload();
inputPanel.setSpacing(5);
inputPanel.setCellVerticalAlignment(fileUpload, HasVerticalAlignment.ALIGN_MIDDLE);
return windImportRootPanel;
}
private CaptionPanel createGribWindImportPanel() {
/*
* To style the "browse" button of the file upload widget
@@ -508,7 +531,6 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe
return windImportRootPanel;
}
private void showWindSettingDialog(RaceDTO race, CoursePositionsDTO course) {
AddWindFixDialog windSettingDialog = new AddWindFixDialog(race, course, stringMessages, new DialogCallback<WindDTO>() {
@Override
@@ -1429,6 +1429,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String collapseX(String text);
String numberOfGPSFixes();
String fastestSailor();
String highestSpeed();
String numberWindFixes();
String sailedMiles();
String millionValue(double value);
@@ -1800,6 +1801,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String eventRegattaHeaderLegendAudioNo();
String set();
String gribWindImport_Title();
String nmeaWindImport_Title();
String angleInDegree();
String angleInRadian();
String centralAngleInRadian();
@@ -902,6 +902,7 @@ windImport_ResultHeader=Imported wind from: {0} to: {1}. {2} races affected.
windImport_ResultEntry=Race: {0} ({1}) - {2} fixes from: {3} to: {4}
windImport_ResultError=Error: {0}
gribWindImport_Title=Import wind from GRIB files
nmeaWindImport_Title=Import wind from NMEA files
showWindSpeedSeriesTooltip=Toggles the visibility of the wind speed for all data sources
showWindDirectionSeriesTooltip=Toggles the visibility of the wind direction for all data sources
combinedWindSourceTypeTooltip=Combines all other wind sources to one data set
@@ -1396,6 +1397,7 @@ collapseX=Collapse {0}
numberOfGPSFixes=GPS fixes
fastestSailor=Fastest
highestSpeed=Highest speed
numberWindFixes=Wind fixes
sailedMiles=Sailed nautical miles
millionValue={0,number,#0.0} mil
@@ -891,6 +891,7 @@ windImport_ResultHeader=Wind wurde importiert von: {0} bis: {1}. {2} Rennen betr
windImport_ResultEntry=Rennen: {0} ({1}) - {2} Messwerte von: {3} bis: {4}
windImport_ResultError=Fehler: {0}
gribWindImport_Title=Wind aus GRIB-Dateien importieren
nmeaWindImport_Title=Wind aus NMEA-Dateien importieren
showWindSpeedSeriesTooltip=Setzt die Sichtbarkeit der Windgeschwindigkeit aller Datenquellen
showWindDirectionSeriesTooltip=Setzt die Sichtbarkeit der Windrichtung aller Datenquellen
combinedWindSourceTypeTooltip=Kombiniert alle Datenquellen zu einem Datensatz
@@ -1379,6 +1380,7 @@ showAllX=Alle {0} anzeigen
collapseX={0} einklappen
numberOfGPSFixes=Positionswerte
fastestSailor=Schnellster
highestSpeed=Höchstgeschwindigkeit
numberWindFixes=Windwerte
sailedMiles=Erfasste Seemeilen
millionValue={0,number,#0.0} Mio
@@ -135,7 +135,7 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint {
final MetaLeaderboardViewer leaderboardViewer = new MetaLeaderboardViewer(null, context,
rootComponentLifeCycle, defaultSettings, sailingService, new AsyncActionsExecutor(),
timer, null, null, leaderboardGroupName, leaderboardName,
timer, null, leaderboardGroupName, leaderboardName,
LeaderboardEntryPoint.this, getStringMessages(),
getActualChartDetailType(defaultSettings));
createUi(leaderboardViewer, defaultSettings, timer, leaderboardContextDefinition);
@@ -2303,9 +2303,10 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
public void onSuccess(LeaderboardDTO result) {
LS potentiallyChangedSettings = overrideDefaultsForNamesOfRaceColumns(currentSettings, result);
try {
final boolean wasEmptyRaceColumnSelection = Util.isEmpty(raceColumnSelection.getSelectedRaceColumns());
updateLeaderboard(result);
// reapply, as columns might have changed
if (!potentiallyChangedSettings.equals(currentSettings)) {
if (wasEmptyRaceColumnSelection || !potentiallyChangedSettings.equals(currentSettings)) {
updateSettings(potentiallyChangedSettings);
}
} finally {
@@ -4,7 +4,6 @@ import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.settings.client.leaderboard.LeaderboardPanelLifecycle;
import com.sap.sailing.gwt.settings.client.leaderboard.LeaderboardPerspectiveOwnSettings;
import com.sap.sailing.gwt.settings.client.leaderboard.MetaLeaderboardPerspectiveLifecycle;
@@ -38,12 +37,12 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer<MetaLeaderb
MetaLeaderboardPerspectiveLifecycle lifecycle,
PerspectiveCompositeSettings<LeaderboardPerspectiveOwnSettings> settings,
SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
Timer timer, String preselectedLeaderboardName, RegattaAndRaceIdentifier preselectedRace,
Timer timer, String preselectedLeaderboardName,
String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter,
StringMessages stringMessages, DetailType chartDetailType) {
this(parent, componentContext, lifecycle, settings, new CompetitorSelectionModel(/* hasMultiSelection */true),
sailingService, asyncActionsExecutor, timer,
preselectedLeaderboardName, preselectedRace, leaderboardGroupName, metaLeaderboardName,
preselectedLeaderboardName, leaderboardGroupName, metaLeaderboardName,
errorReporter, stringMessages, chartDetailType);
}
@@ -53,25 +52,22 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer<MetaLeaderb
PerspectiveCompositeSettings<LeaderboardPerspectiveOwnSettings> settings,
CompetitorSelectionModel competitorSelectionModel, SailingServiceAsync sailingService,
AsyncActionsExecutor asyncActionsExecutor, Timer timer,
String preselectedLeaderboardName, RegattaAndRaceIdentifier preselectedRace, String leaderboardGroupName,
String preselectedLeaderboardName, String leaderboardGroupName,
String metaLeaderboardName, ErrorReporter errorReporter, StringMessages stringMessages,
DetailType chartDetailType) {
super(parent, componentContext, lifecycle, settings, competitorSelectionModel, asyncActionsExecutor, timer,
stringMessages);
/**
* Cleanup one java8 suppliers can be used
*/
init(new SingleRaceLeaderboardPanel(this, componentContext, sailingService, asyncActionsExecutor,
settings.findSettingsByComponentId(LeaderboardPanelLifecycle.ID),
preselectedRace != null, preselectedRace, competitorSelectionModel, timer,
leaderboardGroupName, metaLeaderboardName, errorReporter, stringMessages,
init(new MultiRaceLeaderboardPanel(this, componentContext, sailingService, asyncActionsExecutor,
settings.findSettingsByComponentId(LeaderboardPanelLifecycle.ID), /* isEmbedded */ false,
competitorSelectionModel, timer,
leaderboardGroupName, metaLeaderboardName, errorReporter, stringMessages,
settings.getPerspectiveOwnSettings().isShowRaceDetails(), /* competitorSearchTextBox */ null,
/* showSelectionCheckbox */ true, /* raceTimesInfoProvider */null,
settings.getPerspectiveOwnSettings().isAutoExpandLastRaceColumn(), /* adjustTimerDelay */ true,
/* autoApplyTopNFilter */ false,
/* showCompetitorFilterStatus */ false, /* enableSyncScroller */ false,new ClassicLeaderboardStyle()));
/* showCompetitorFilterStatus */ false, /* enableSyncScroller */ false, new ClassicLeaderboardStyle()));
final LeaderboardPerspectiveOwnSettings perspectiveSettings = settings.getPerspectiveOwnSettings();
final boolean showCharts = perspectiveSettings.isShowCharts();
@@ -56,7 +56,6 @@ public class MultiRaceLeaderboardViewer extends AbstractLeaderboardViewer<Leader
final StringMessages stringMessages, DetailType chartDetailType) {
super(parent, componentContext, lifecycle, settings, competitorSelectionModel, asyncActionsExecutor, timer,
stringMessages);
// FIXME: Cleanup with java8 using supplier
init(new MultiRaceLeaderboardPanel(this, getComponentContext(), sailingService, asyncActionsExecutor,
settings.findSettingsByComponentId(LeaderboardPanelLifecycle.ID), false,
competitorSelectionModel, timer, leaderboardGroupName, leaderboardName, errorReporter,
@@ -25,6 +25,11 @@ public class RaceCourseDTO implements IsSerializable {
this(waypoints, getMarksFromWaypoints(waypoints));
}
public RaceCourseDTO(List<WaypointDTO> waypoints, List<MarkDTO> allMarks) {
this.waypoints = waypoints;
this.allMarks = allMarks;
}
private static List<MarkDTO> getMarksFromWaypoints(List<WaypointDTO> waypoints) {
final LinkedHashSet<MarkDTO> marks = new LinkedHashSet<>();
for (final WaypointDTO waypoint : waypoints) {
@@ -39,11 +44,6 @@ public class RaceCourseDTO implements IsSerializable {
return result;
}
public RaceCourseDTO(List<WaypointDTO> waypoints, List<MarkDTO> allMarks) {
this.waypoints = waypoints;
this.allMarks = allMarks;
}
public List<ControlPointDTO> getControlPoints() {
List<ControlPointDTO> controlPoints = new ArrayList<ControlPointDTO>();
for (WaypointDTO waypoint : waypoints) {
@@ -69,4 +69,35 @@ public class RaceCourseDTO implements IsSerializable {
}
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((allMarks == null) ? 0 : allMarks.hashCode());
result = prime * result + ((waypoints == null) ? 0 : waypoints.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RaceCourseDTO other = (RaceCourseDTO) obj;
if (allMarks == null) {
if (other.allMarks != null)
return false;
} else if (!allMarks.equals(other.allMarks))
return false;
if (waypoints == null) {
if (other.waypoints != null)
return false;
} else if (!waypoints.equals(other.waypoints))
return false;
return true;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="resources"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.sap.sailing.nmeaconnector.test</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,12 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8
@@ -0,0 +1,3 @@
eclipse.preferences.version=1
pluginProject.extensions=false
resolve.requirebundle=false
@@ -0,0 +1,16 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Test
Bundle-SymbolicName: com.sap.sailing.nmeaconnector.test
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Fragment-Host: com.sap.sailing.nmeaconnector
Bundle-ActivationPolicy: lazy
Require-Bundle: org.junit;bundle-version="4.8.2",
com.sap.sailing.domain,
com.sap.sailing.domain.common,
net.sf.marineapi,
com.sap.sse.common,
routeconverter;bundle-version="2.8.0"
Bundle-ClassPath: .
+5
View File
@@ -0,0 +1,5 @@
source.. = src/,\
resources/
output.. = bin/
bin.includes = META-INF/,\
.
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.nmeaconnector.test</artifactId>
<packaging>eclipse-test-plugin</packaging>
</project>
@@ -0,0 +1,490 @@
<?xml version="1.0" encoding="UTF-8" ?><gpx version="1.0" creator="GPSLogger - http://gpslogger.mendhak.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/0" xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd"><time>2012-12-19T10:40:46Z</time><bounds /><trk><trkseg><trkpt lat="54.43041895" lon="10.16849843"><ele>56.70000076293945</ele><course>203.1</course><speed>0.75</speed><hdop>1.6970562</hdop><src>gps</src><sat>10</sat><time>2012-12-19T10:40:46Z</time></trkpt>
<trkpt lat="54.43041715" lon="10.16849687"><ele>56.400001525878906</ele><course>340.3</course><speed>0.75</speed><hdop>1.6970562</hdop><src>gps</src><sat>10</sat><time>2012-12-19T10:40:49Z</time></trkpt>
<trkpt lat="54.43051734" lon="10.16843823"><ele>52.5</ele><course>331.1</course><speed>4.0</speed><hdop>1.0</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:40:52Z</time></trkpt>
<trkpt lat="54.43058934" lon="10.16837067"><ele>51.70000076293945</ele><course>330.5</course><speed>4.0</speed><hdop>1.0</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:40:54Z</time></trkpt>
<trkpt lat="54.43065468" lon="10.16830576"><ele>50.099998474121094</ele><course>337.3</course><speed>3.75</speed><hdop>0.7211102</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:40:56Z</time></trkpt>
<trkpt lat="54.43071658" lon="10.16831836"><ele>49.20000076293945</ele><course>26.7</course><speed>4.5</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:40:58Z</time></trkpt>
<trkpt lat="54.43077917" lon="10.16841689"><ele>48.599998474121094</ele><course>54.1</course><speed>4.75</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:00Z</time></trkpt>
<trkpt lat="54.43082095" lon="10.16857047"><ele>48.599998474121094</ele><course>66.9</course><speed>5.75</speed><hdop>0.7211102</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:41:02Z</time></trkpt>
<trkpt lat="54.43084608" lon="10.16873799"><ele>46.400001525878906</ele><course>70.3</course><speed>5.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:04Z</time></trkpt>
<trkpt lat="54.43085174" lon="10.16889115"><ele>44.5</ele><course>81.3</course><speed>5.0</speed><hdop>0.8485281</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:06Z</time></trkpt>
<trkpt lat="54.43088122" lon="10.16900612"><ele>43.900001525878906</ele><course>56.6</course><speed>4.0</speed><hdop>0.8485281</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:08Z</time></trkpt>
<trkpt lat="54.43093128" lon="10.16904676"><ele>43.70000076293945</ele><course>2.2</course><speed>2.5</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:10Z</time></trkpt>
<trkpt lat="54.43096396" lon="10.16902715"><ele>42.29999923706055</ele><course>345.2</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:12Z</time></trkpt>
<trkpt lat="54.43096449" lon="10.16901984"><ele>41.79999923706055</ele><course>345.2</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:14Z</time></trkpt>
<trkpt lat="54.43096433" lon="10.16902004"><ele>41.79999923706055</ele><course>345.2</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:16Z</time></trkpt>
<trkpt lat="54.43097213" lon="10.1690027"><ele>42.400001525878906</ele><course>313.8</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:18Z</time></trkpt>
<trkpt lat="54.43097994" lon="10.16895738"><ele>42.79999923706055</ele><course>278.1</course><speed>1.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:20Z</time></trkpt>
<trkpt lat="54.43096834" lon="10.16890725"><ele>42.20000076293945</ele><course>231.9</course><speed>2.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:22Z</time></trkpt>
<trkpt lat="54.4309299" lon="10.1688647"><ele>42.20000076293945</ele><course>193.2</course><speed>2.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:24Z</time></trkpt>
<trkpt lat="54.43088634" lon="10.16886112"><ele>41.70000076293945</ele><course>151.0</course><speed>2.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:26Z</time></trkpt>
<trkpt lat="54.430857" lon="10.16890055"><ele>41.79999923706055</ele><course>116.8</course><speed>1.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:28Z</time></trkpt>
<trkpt lat="54.43085167" lon="10.16895548"><ele>42.0</ele><course>87.7</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:30Z</time></trkpt>
<trkpt lat="54.43085143" lon="10.16898998"><ele>42.099998474121094</ele><course>86.6</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:32Z</time></trkpt>
<trkpt lat="54.43085354" lon="10.16899234"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:34Z</time></trkpt>
<trkpt lat="54.43085325" lon="10.16899315"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:36Z</time></trkpt>
<trkpt lat="54.43085584" lon="10.16899607"><ele>41.79999923706055</ele><course>86.6</course><speed>0.0</speed><hdop>0.8944272</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:38Z</time></trkpt>
<trkpt lat="54.43085739" lon="10.16900078"><ele>41.900001525878906</ele><course>86.6</course><speed>0.0</speed><hdop>0.8944272</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:40Z</time></trkpt>
<trkpt lat="54.43085794" lon="10.16900229"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.8944272</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:42Z</time></trkpt>
<trkpt lat="54.43085825" lon="10.16900311"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.8944272</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:44Z</time></trkpt>
<trkpt lat="54.43085823" lon="10.16900318"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:46Z</time></trkpt>
<trkpt lat="54.43085822" lon="10.16900323"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:41:48Z</time></trkpt>
<trkpt lat="54.43085824" lon="10.16900325"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:41:50Z</time></trkpt>
<trkpt lat="54.43085823" lon="10.16900326"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:41:52Z</time></trkpt>
<trkpt lat="54.43085823" lon="10.16900326"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:41:54Z</time></trkpt>
<trkpt lat="54.43085822" lon="10.16900326"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:41:56Z</time></trkpt>
<trkpt lat="54.43085828" lon="10.1690032"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:41:58Z</time></trkpt>
<trkpt lat="54.4308585" lon="10.16900302"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:00Z</time></trkpt>
<trkpt lat="54.4308586" lon="10.16900292"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:02Z</time></trkpt>
<trkpt lat="54.43085863" lon="10.16900288"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:04Z</time></trkpt>
<trkpt lat="54.43085857" lon="10.16900291"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:06Z</time></trkpt>
<trkpt lat="54.43085858" lon="10.16900289"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:08Z</time></trkpt>
<trkpt lat="54.43085856" lon="10.16900288"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:10Z</time></trkpt>
<trkpt lat="54.43085856" lon="10.1690029"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:12Z</time></trkpt>
<trkpt lat="54.43085857" lon="10.16900289"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:14Z</time></trkpt>
<trkpt lat="54.43085861" lon="10.16900285"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:16Z</time></trkpt>
<trkpt lat="54.4308586" lon="10.16900282"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:18Z</time></trkpt>
<trkpt lat="54.43085863" lon="10.16900278"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:20Z</time></trkpt>
<trkpt lat="54.43085864" lon="10.16900278"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:22Z</time></trkpt>
<trkpt lat="54.43085866" lon="10.16900278"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:42:24Z</time></trkpt>
<trkpt lat="54.43085866" lon="10.16900279"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:26Z</time></trkpt>
<trkpt lat="54.43085864" lon="10.16900276"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:28Z</time></trkpt>
<trkpt lat="54.43085861" lon="10.16900272"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:30Z</time></trkpt>
<trkpt lat="54.43085859" lon="10.1690027"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:32Z</time></trkpt>
<trkpt lat="54.43085856" lon="10.16900269"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:34Z</time></trkpt>
<trkpt lat="54.43085837" lon="10.16900276"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>7</sat><time>2012-12-19T10:42:41Z</time></trkpt>
<trkpt lat="54.43085837" lon="10.16900276"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:43Z</time></trkpt>
<trkpt lat="54.43085836" lon="10.16900276"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:45Z</time></trkpt>
<trkpt lat="54.43085832" lon="10.16900281"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:48Z</time></trkpt>
<trkpt lat="54.4308583" lon="10.16900284"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:50Z</time></trkpt>
<trkpt lat="54.43085831" lon="10.16900285"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:52Z</time></trkpt>
<trkpt lat="54.4308583" lon="10.16900283"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:54Z</time></trkpt>
<trkpt lat="54.43085831" lon="10.16900278"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:57Z</time></trkpt>
<trkpt lat="54.43085831" lon="10.16900277"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:42:59Z</time></trkpt>
<trkpt lat="54.43085833" lon="10.1690028"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:01Z</time></trkpt>
<trkpt lat="54.43085835" lon="10.1690028"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:43:03Z</time></trkpt>
<trkpt lat="54.43085837" lon="10.16900279"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:05Z</time></trkpt>
<trkpt lat="54.43085839" lon="10.1690028"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:43:07Z</time></trkpt>
<trkpt lat="54.4308584" lon="10.16900281"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:09Z</time></trkpt>
<trkpt lat="54.4308584" lon="10.16900283"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:11Z</time></trkpt>
<trkpt lat="54.43085835" lon="10.16900285"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:14Z</time></trkpt>
<trkpt lat="54.43085832" lon="10.16900284"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:16Z</time></trkpt>
<trkpt lat="54.43085829" lon="10.16900281"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:18Z</time></trkpt>
<trkpt lat="54.43085828" lon="10.16900278"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:20Z</time></trkpt>
<trkpt lat="54.43085825" lon="10.16900276"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:43:22Z</time></trkpt>
<trkpt lat="54.43085824" lon="10.16900275"><ele>42.20000076293945</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:43:24Z</time></trkpt>
<trkpt lat="54.43085822" lon="10.16900275"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:43:26Z</time></trkpt>
<trkpt lat="54.4308582" lon="10.16900274"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:43:28Z</time></trkpt>
<trkpt lat="54.43085817" lon="10.16900275"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:43:30Z</time></trkpt>
<trkpt lat="54.43085812" lon="10.16900271"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>6</sat><time>2012-12-19T10:43:37Z</time></trkpt>
<trkpt lat="54.4308581" lon="10.16900269"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:39Z</time></trkpt>
<trkpt lat="54.4308581" lon="10.16900272"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:41Z</time></trkpt>
<trkpt lat="54.43085809" lon="10.16900271"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:43Z</time></trkpt>
<trkpt lat="54.43085809" lon="10.1690027"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:45Z</time></trkpt>
<trkpt lat="54.43085806" lon="10.16900276"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>8</sat><time>2012-12-19T10:43:53Z</time></trkpt>
<trkpt lat="54.43085807" lon="10.16900275"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:55Z</time></trkpt>
<trkpt lat="54.43085808" lon="10.16900274"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:57Z</time></trkpt>
<trkpt lat="54.43085807" lon="10.16900272"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:43:59Z</time></trkpt>
<trkpt lat="54.43085807" lon="10.16900267"><ele>42.099998474121094</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:02Z</time></trkpt>
<trkpt lat="54.43084639" lon="10.16899943"><ele>42.29999923706055</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>8</sat><time>2012-12-19T10:44:09Z</time></trkpt>
<trkpt lat="54.43083804" lon="10.16900454"><ele>42.5</ele><course>86.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:44:11Z</time></trkpt>
<trkpt lat="54.43084033" lon="10.16901537"><ele>42.099998474121094</ele><course>45.6</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:44:14Z</time></trkpt>
<trkpt lat="54.43087298" lon="10.1690397"><ele>41.79999923706055</ele><course>7.6</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:17Z</time></trkpt>
<trkpt lat="54.43090052" lon="10.16904274"><ele>41.70000076293945</ele><course>357.5</course><speed>1.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:19Z</time></trkpt>
<trkpt lat="54.43096382" lon="10.16904279"><ele>41.5</ele><course>0.0</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>4</sat><time>2012-12-19T10:44:27Z</time></trkpt>
<trkpt lat="54.43095837" lon="10.16904628"><ele>41.5</ele><course>0.0</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:29Z</time></trkpt>
<trkpt lat="54.43095725" lon="10.16904903"><ele>41.400001525878906</ele><course>0.0</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:31Z</time></trkpt>
<trkpt lat="54.43095709" lon="10.16905039"><ele>41.29999923706055</ele><course>0.0</course><speed>0.0</speed><hdop>1.3416407</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:33Z</time></trkpt>
<trkpt lat="54.43095747" lon="10.16904764"><ele>41.400001525878906</ele><course>0.0</course><speed>0.0</speed><hdop>2.529822</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:42Z</time></trkpt>
<trkpt lat="54.43095723" lon="10.16904737"><ele>41.400001525878906</ele><course>0.0</course><speed>0.0</speed><hdop>2.529822</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:44Z</time></trkpt>
<trkpt lat="54.43095724" lon="10.16904735"><ele>41.400001525878906</ele><course>0.0</course><speed>0.0</speed><hdop>3.2984843</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:46Z</time></trkpt>
<trkpt lat="54.43095498" lon="10.16904398"><ele>41.79999923706055</ele><course>0.0</course><speed>0.0</speed><hdop>1.3416407</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:48Z</time></trkpt>
<trkpt lat="54.43095496" lon="10.16904392"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.8944272</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:50Z</time></trkpt>
<trkpt lat="54.43095495" lon="10.16904388"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:52Z</time></trkpt>
<trkpt lat="54.43095493" lon="10.16904384"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:54Z</time></trkpt>
<trkpt lat="54.43095492" lon="10.16904379"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:57Z</time></trkpt>
<trkpt lat="54.43095493" lon="10.16904378"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:44:59Z</time></trkpt>
<trkpt lat="54.43095494" lon="10.16904375"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:01Z</time></trkpt>
<trkpt lat="54.43095493" lon="10.16904372"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:03Z</time></trkpt>
<trkpt lat="54.43095493" lon="10.16904369"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:05Z</time></trkpt>
<trkpt lat="54.43095493" lon="10.16904367"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:07Z</time></trkpt>
<trkpt lat="54.43095493" lon="10.16904366"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:09Z</time></trkpt>
<trkpt lat="54.43095493" lon="10.16904364"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:11Z</time></trkpt>
<trkpt lat="54.43095488" lon="10.16904361"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>6</sat><time>2012-12-19T10:45:18Z</time></trkpt>
<trkpt lat="54.43095486" lon="10.16904359"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:45:20Z</time></trkpt>
<trkpt lat="54.43095486" lon="10.16904357"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:45:22Z</time></trkpt>
<trkpt lat="54.43095487" lon="10.16904355"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:45:24Z</time></trkpt>
<trkpt lat="54.43095487" lon="10.16904352"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:26Z</time></trkpt>
<trkpt lat="54.43095487" lon="10.16904349"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:28Z</time></trkpt>
<trkpt lat="54.43095488" lon="10.16904346"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:30Z</time></trkpt>
<trkpt lat="54.43095489" lon="10.16904345"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:32Z</time></trkpt>
<trkpt lat="54.43095489" lon="10.16904345"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:34Z</time></trkpt>
<trkpt lat="54.43095488" lon="10.16904343"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:36Z</time></trkpt>
<trkpt lat="54.43095489" lon="10.16904341"><ele>41.70000076293945</ele><course>0.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:38Z</time></trkpt>
<trkpt lat="54.43097629" lon="10.16903671"><ele>41.70000076293945</ele><course>351.4</course><speed>3.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:40Z</time></trkpt>
<trkpt lat="54.43110353" lon="10.16900955"><ele>42.0</ele><course>350.9</course><speed>6.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:43Z</time></trkpt>
<trkpt lat="54.4312265" lon="10.16897948"><ele>42.5</ele><course>351.1</course><speed>7.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:45Z</time></trkpt>
<trkpt lat="54.43136299" lon="10.16894798"><ele>42.79999923706055</ele><course>351.8</course><speed>8.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:47Z</time></trkpt>
<trkpt lat="54.43150214" lon="10.16890568"><ele>42.599998474121094</ele><course>352.0</course><speed>7.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:49Z</time></trkpt>
<trkpt lat="54.43163351" lon="10.16887174"><ele>42.599998474121094</ele><course>351.9</course><speed>7.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:51Z</time></trkpt>
<trkpt lat="54.43176213" lon="10.16884066"><ele>42.70000076293945</ele><course>351.9</course><speed>7.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:53Z</time></trkpt>
<trkpt lat="54.43189147" lon="10.16880859"><ele>42.79999923706055</ele><course>352.1</course><speed>7.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:55Z</time></trkpt>
<trkpt lat="54.43202266" lon="10.16878099"><ele>42.79999923706055</ele><course>352.2</course><speed>7.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:45:57Z</time></trkpt>
<trkpt lat="54.43235573" lon="10.16869785"><ele>42.900001525878906</ele><course>352.1</course><speed>7.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:02Z</time></trkpt>
<trkpt lat="54.43256193" lon="10.1686463"><ele>42.599998474121094</ele><course>352.4</course><speed>7.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:05Z</time></trkpt>
<trkpt lat="54.43268055" lon="10.16862129"><ele>42.599998474121094</ele><course>351.8</course><speed>5.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:07Z</time></trkpt>
<trkpt lat="54.43275984" lon="10.16857723"><ele>42.29999923706055</ele><course>327.3</course><speed>3.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:09Z</time></trkpt>
<trkpt lat="54.43280069" lon="10.16851076"><ele>41.79999923706055</ele><course>307.5</course><speed>3.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:11Z</time></trkpt>
<trkpt lat="54.43279677" lon="10.16842614"><ele>41.70000076293945</ele><course>253.7</course><speed>2.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:13Z</time></trkpt>
<trkpt lat="54.43278766" lon="10.16836186"><ele>41.5</ele><course>244.4</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:15Z</time></trkpt>
<trkpt lat="54.43278498" lon="10.16834764"><ele>41.099998474121094</ele><course>244.4</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:17Z</time></trkpt>
<trkpt lat="54.43278473" lon="10.16835739"><ele>41.20000076293945</ele><course>84.5</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:20Z</time></trkpt>
<trkpt lat="54.43278689" lon="10.16838992"><ele>41.0</ele><course>73.0</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:22Z</time></trkpt>
<trkpt lat="54.43279503" lon="10.16840934"><ele>41.099998474121094</ele><course>46.2</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:24Z</time></trkpt>
<trkpt lat="54.43280387" lon="10.16842366"><ele>41.0</ele><course>37.8</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:46:26Z</time></trkpt>
<trkpt lat="54.43280834" lon="10.16842443"><ele>41.099998474121094</ele><course>37.8</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:46:28Z</time></trkpt>
<trkpt lat="54.43280795" lon="10.16842484"><ele>41.20000076293945</ele><course>37.8</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:30Z</time></trkpt>
<trkpt lat="54.43279374" lon="10.16845205"><ele>41.099998474121094</ele><course>127.3</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:32Z</time></trkpt>
<trkpt lat="54.43279493" lon="10.16854055"><ele>41.099998474121094</ele><course>74.2</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:35Z</time></trkpt>
<trkpt lat="54.43277704" lon="10.16859865"><ele>40.900001525878906</ele><course>146.8</course><speed>1.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:38Z</time></trkpt>
<trkpt lat="54.43273573" lon="10.16861124"><ele>40.29999923706055</ele><course>183.9</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:41Z</time></trkpt>
<trkpt lat="54.43272944" lon="10.16860831"><ele>40.29999923706055</ele><course>183.9</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:43Z</time></trkpt>
<trkpt lat="54.43274835" lon="10.16860513"><ele>40.20000076293945</ele><course>355.6</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:46:45Z</time></trkpt>
<trkpt lat="54.4327612" lon="10.16860165"><ele>40.29999923706055</ele><course>357.2</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:46:47Z</time></trkpt>
<trkpt lat="54.43276081" lon="10.16859911"><ele>40.400001525878906</ele><course>357.2</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:49Z</time></trkpt>
<trkpt lat="54.43276066" lon="10.16859796"><ele>40.5</ele><course>357.2</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:51Z</time></trkpt>
<trkpt lat="54.43276019" lon="10.16859656"><ele>40.5</ele><course>357.2</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:53Z</time></trkpt>
<trkpt lat="54.43276014" lon="10.1685954"><ele>40.5</ele><course>357.2</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:46:55Z</time></trkpt>
<trkpt lat="54.43275995" lon="10.16859492"><ele>40.5</ele><course>357.2</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:46:57Z</time></trkpt>
<trkpt lat="54.43275989" lon="10.16859467"><ele>40.5</ele><course>357.2</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:46:59Z</time></trkpt>
<trkpt lat="54.43275988" lon="10.16859465"><ele>40.5</ele><course>357.2</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:01Z</time></trkpt>
<trkpt lat="54.43275983" lon="10.16859466"><ele>40.5</ele><course>357.2</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:03Z</time></trkpt>
<trkpt lat="54.43259415" lon="10.16862721"><ele>40.5</ele><course>173.3</course><speed>5.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>7</sat><time>2012-12-19T10:47:10Z</time></trkpt>
<trkpt lat="54.43246312" lon="10.16866582"><ele>40.20000076293945</ele><course>172.2</course><speed>7.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:12Z</time></trkpt>
<trkpt lat="54.43232389" lon="10.16869694"><ele>40.29999923706055</ele><course>171.6</course><speed>8.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:14Z</time></trkpt>
<trkpt lat="54.43217478" lon="10.16873405"><ele>40.29999923706055</ele><course>171.4</course><speed>8.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:16Z</time></trkpt>
<trkpt lat="54.4320271" lon="10.1687714"><ele>39.900001525878906</ele><course>171.8</course><speed>8.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:18Z</time></trkpt>
<trkpt lat="54.43188236" lon="10.16880774"><ele>40.0</ele><course>172.0</course><speed>8.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:20Z</time></trkpt>
<trkpt lat="54.43135175" lon="10.16893504"><ele>40.20000076293945</ele><course>171.5</course><speed>8.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>8</sat><time>2012-12-19T10:47:27Z</time></trkpt>
<trkpt lat="54.43120703" lon="10.1689722"><ele>39.900001525878906</ele><course>171.3</course><speed>8.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:47:29Z</time></trkpt>
<trkpt lat="54.43107207" lon="10.16901409"><ele>39.70000076293945</ele><course>171.6</course><speed>8.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:31Z</time></trkpt>
<trkpt lat="54.430928" lon="10.16904807"><ele>39.900001525878906</ele><course>172.1</course><speed>8.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:33Z</time></trkpt>
<trkpt lat="54.43078781" lon="10.16907721"><ele>40.0</ele><course>171.3</course><speed>7.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:35Z</time></trkpt>
<trkpt lat="54.43062383" lon="10.16912503"><ele>41.20000076293945</ele><course>170.3</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>6</sat><time>2012-12-19T10:47:43Z</time></trkpt>
<trkpt lat="54.43063151" lon="10.16913212"><ele>40.70000076293945</ele><course>170.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:45Z</time></trkpt>
<trkpt lat="54.43063239" lon="10.16913238"><ele>40.79999923706055</ele><course>170.3</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:47Z</time></trkpt>
<trkpt lat="54.4306132" lon="10.16912534"><ele>41.20000076293945</ele><course>200.0</course><speed>2.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:49Z</time></trkpt>
<trkpt lat="54.43057387" lon="10.16908"><ele>41.599998474121094</ele><course>216.8</course><speed>3.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:51Z</time></trkpt>
<trkpt lat="54.43052683" lon="10.16905874"><ele>41.599998474121094</ele><course>156.2</course><speed>2.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:53Z</time></trkpt>
<trkpt lat="54.43050702" lon="10.16916153"><ele>40.900001525878906</ele><course>64.2</course><speed>2.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:56Z</time></trkpt>
<trkpt lat="54.43054238" lon="10.16920719"><ele>40.79999923706055</ele><course>4.2</course><speed>2.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:47:58Z</time></trkpt>
<trkpt lat="54.43058343" lon="10.16918601"><ele>40.20000076293945</ele><course>327.6</course><speed>2.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:00Z</time></trkpt>
<trkpt lat="54.43060387" lon="10.16915942"><ele>40.0</ele><course>337.9</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:02Z</time></trkpt>
<trkpt lat="54.43061203" lon="10.16915529"><ele>39.900001525878906</ele><course>337.9</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:04Z</time></trkpt>
<trkpt lat="54.43061942" lon="10.16915049"><ele>39.900001525878906</ele><course>337.9</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:06Z</time></trkpt>
<trkpt lat="54.43063369" lon="10.16914315"><ele>39.20000076293945</ele><course>352.2</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:08Z</time></trkpt>
<trkpt lat="54.43068829" lon="10.16912844"><ele>39.29999923706055</ele><course>347.9</course><speed>5.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:11Z</time></trkpt>
<trkpt lat="54.43084109" lon="10.16907817"><ele>39.20000076293945</ele><course>349.7</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:13Z</time></trkpt>
<trkpt lat="54.43103454" lon="10.16902536"><ele>39.29999923706055</ele><course>350.8</course><speed>11.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:15Z</time></trkpt>
<trkpt lat="54.43123519" lon="10.16897075"><ele>39.20000076293945</ele><course>351.1</course><speed>11.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:17Z</time></trkpt>
<trkpt lat="54.43153299" lon="10.16889721"><ele>39.20000076293945</ele><course>351.3</course><speed>11.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:20Z</time></trkpt>
<trkpt lat="54.43182139" lon="10.16882499"><ele>39.20000076293945</ele><course>351.7</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:23Z</time></trkpt>
<trkpt lat="54.43252182" lon="10.1686609"><ele>39.70000076293945</ele><course>352.3</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>9</sat><time>2012-12-19T10:48:30Z</time></trkpt>
<trkpt lat="54.43267304" lon="10.16862423"><ele>39.79999923706055</ele><course>352.3</course><speed>6.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:48:32Z</time></trkpt>
<trkpt lat="54.43275593" lon="10.16858829"><ele>39.900001525878906</ele><course>327.5</course><speed>3.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:34Z</time></trkpt>
<trkpt lat="54.43279684" lon="10.16850877"><ele>40.0</ele><course>284.6</course><speed>3.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:36Z</time></trkpt>
<trkpt lat="54.43278935" lon="10.16841764"><ele>40.70000076293945</ele><course>233.9</course><speed>2.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:38Z</time></trkpt>
<trkpt lat="54.43277503" lon="10.16840132"><ele>40.29999923706055</ele><course>225.2</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:40Z</time></trkpt>
<trkpt lat="54.432777" lon="10.16841027"><ele>40.400001525878906</ele><course>72.9</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:42Z</time></trkpt>
<trkpt lat="54.43278491" lon="10.16843576"><ele>40.400001525878906</ele><course>45.7</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:44Z</time></trkpt>
<trkpt lat="54.43279135" lon="10.16844885"><ele>40.400001525878906</ele><course>36.6</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:46Z</time></trkpt>
<trkpt lat="54.43279623" lon="10.16844986"><ele>40.79999923706055</ele><course>36.6</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:48Z</time></trkpt>
<trkpt lat="54.43278423" lon="10.16846145"><ele>41.099998474121094</ele><course>136.1</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:48:50Z</time></trkpt>
<trkpt lat="54.43270545" lon="10.16860144"><ele>39.900001525878906</ele><course>174.5</course><speed>1.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>8</sat><time>2012-12-19T10:48:58Z</time></trkpt>
<trkpt lat="54.43272947" lon="10.16859944"><ele>39.79999923706055</ele><course>350.7</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>10</sat><time>2012-12-19T10:49:00Z</time></trkpt>
<trkpt lat="54.43275714" lon="10.16860063"><ele>39.900001525878906</ele><course>359.3</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:49:02Z</time></trkpt>
<trkpt lat="54.43277324" lon="10.16859357"><ele>40.0</ele><course>356.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>6</sat><time>2012-12-19T10:49:10Z</time></trkpt>
<trkpt lat="54.43277326" lon="10.16859184"><ele>40.0</ele><course>356.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:49:12Z</time></trkpt>
<trkpt lat="54.432773" lon="10.16859145"><ele>40.099998474121094</ele><course>356.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:49:14Z</time></trkpt>
<trkpt lat="54.43277281" lon="10.16859176"><ele>40.0</ele><course>356.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:49:16Z</time></trkpt>
<trkpt lat="54.43277231" lon="10.16859228"><ele>40.099998474121094</ele><course>356.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:49:18Z</time></trkpt>
<trkpt lat="54.43277225" lon="10.16859246"><ele>40.0</ele><course>356.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:49:20Z</time></trkpt>
<trkpt lat="54.43277226" lon="10.16859244"><ele>40.0</ele><course>356.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:49:22Z</time></trkpt>
<trkpt lat="54.43277225" lon="10.16859246"><ele>40.0</ele><course>356.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:49:24Z</time></trkpt>
<trkpt lat="54.43277225" lon="10.16859251"><ele>40.0</ele><course>356.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>10</sat><time>2012-12-19T10:49:31Z</time></trkpt>
<trkpt lat="54.43277224" lon="10.16859252"><ele>40.0</ele><course>356.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:49:34Z</time></trkpt>
<trkpt lat="54.4306172" lon="10.1691843"><ele>24.700000762939453</ele><course>168.7</course><speed>11.5</speed><hdop>2.0</hdop><src>gps</src><sat>6</sat><time>2012-12-19T10:50:10Z</time></trkpt>
<trkpt lat="54.43052511" lon="10.16927846"><ele>37.20000076293945</ele><course>142.8</course><speed>5.75</speed><hdop>1.3416407</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:50:12Z</time></trkpt>
<trkpt lat="54.43052319" lon="10.16926748"><ele>40.0</ele><course>195.3</course><speed>1.118034</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:50:14Z</time></trkpt>
<trkpt lat="54.43054108" lon="10.16921228"><ele>41.20000076293945</ele><course>236.2</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:50:16Z</time></trkpt>
<trkpt lat="54.43054004" lon="10.16919875"><ele>39.0</ele><course>236.2</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:50:18Z</time></trkpt>
<trkpt lat="54.43060163" lon="10.16916831"><ele>42.599998474121094</ele><course>348.1</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>8</sat><time>2012-12-19T10:50:25Z</time></trkpt>
<trkpt lat="54.43059537" lon="10.16916402"><ele>41.20000076293945</ele><course>348.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>4</sat><time>2012-12-19T10:50:32Z</time></trkpt>
<trkpt lat="54.43059412" lon="10.16915923"><ele>40.70000076293945</ele><course>348.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:50:34Z</time></trkpt>
<trkpt lat="54.43058806" lon="10.16914525"><ele>41.0</ele><course>348.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>4</sat><time>2012-12-19T10:50:42Z</time></trkpt>
<trkpt lat="54.43058797" lon="10.16914527"><ele>41.0</ele><course>348.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:50:44Z</time></trkpt>
<trkpt lat="54.43058935" lon="10.16914576"><ele>41.0</ele><course>348.1</course><speed>0.0</speed><hdop>1.7888544</hdop><src>gps</src><sat>6</sat><time>2012-12-19T10:51:02Z</time></trkpt>
<trkpt lat="54.43068619" lon="10.16913652"><ele>41.099998474121094</ele><course>347.4</course><speed>3.75</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:51:04Z</time></trkpt>
<trkpt lat="54.43071539" lon="10.16910724"><ele>38.599998474121094</ele><course>348.0</course><speed>5.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:51:06Z</time></trkpt>
<trkpt lat="54.43063439" lon="10.16903059"><ele>40.099998474121094</ele><course>176.7</course><speed>1.0</speed><hdop>1.6970562</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:18Z</time></trkpt>
<trkpt lat="54.43058142" lon="10.16911856"><ele>40.400001525878906</ele><course>342.9</course><speed>0.25</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:20Z</time></trkpt>
<trkpt lat="54.43065995" lon="10.16914006"><ele>40.5</ele><course>348.0</course><speed>4.5</speed><hdop>1.6970562</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:22Z</time></trkpt>
<trkpt lat="54.43076071" lon="10.16910091"><ele>40.599998474121094</ele><course>348.6</course><speed>9.25</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:24Z</time></trkpt>
<trkpt lat="54.43093204" lon="10.16904044"><ele>41.5</ele><course>349.6</course><speed>10.25</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:26Z</time></trkpt>
<trkpt lat="54.43122113" lon="10.1689527"><ele>41.099998474121094</ele><course>351.5</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:29Z</time></trkpt>
<trkpt lat="54.43141688" lon="10.16890985"><ele>40.900001525878906</ele><course>351.8</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:31Z</time></trkpt>
<trkpt lat="54.43160814" lon="10.16887291"><ele>40.900001525878906</ele><course>352.0</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:33Z</time></trkpt>
<trkpt lat="54.43179799" lon="10.16882999"><ele>41.0</ele><course>352.3</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:35Z</time></trkpt>
<trkpt lat="54.43199036" lon="10.16878216"><ele>41.099998474121094</ele><course>352.0</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:37Z</time></trkpt>
<trkpt lat="54.43227885" lon="10.16871896"><ele>41.400001525878906</ele><course>352.1</course><speed>10.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:40Z</time></trkpt>
<trkpt lat="54.43244356" lon="10.16868361"><ele>41.20000076293945</ele><course>352.8</course><speed>7.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:42Z</time></trkpt>
<trkpt lat="54.43253996" lon="10.16867204"><ele>41.20000076293945</ele><course>355.0</course><speed>4.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:44Z</time></trkpt>
<trkpt lat="54.43259048" lon="10.168666"><ele>41.29999923706055</ele><course>353.2</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:46Z</time></trkpt>
<trkpt lat="54.43270615" lon="10.16862918"><ele>41.400001525878906</ele><course>345.1</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>6</sat><time>2012-12-19T10:55:53Z</time></trkpt>
<trkpt lat="54.43274805" lon="10.16861734"><ele>40.900001525878906</ele><course>349.4</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:55Z</time></trkpt>
<trkpt lat="54.43278369" lon="10.1685677"><ele>40.599998474121094</ele><course>313.3</course><speed>2.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:55:58Z</time></trkpt>
<trkpt lat="54.43280992" lon="10.16850865"><ele>40.0</ele><course>306.3</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:01Z</time></trkpt>
<trkpt lat="54.43280142" lon="10.16849173"><ele>40.5</ele><course>306.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:03Z</time></trkpt>
<trkpt lat="54.43279523" lon="10.16849658"><ele>40.900001525878906</ele><course>306.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:05Z</time></trkpt>
<trkpt lat="54.43279472" lon="10.16849617"><ele>41.099998474121094</ele><course>306.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:07Z</time></trkpt>
<trkpt lat="54.43279283" lon="10.16849546"><ele>41.0</ele><course>306.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:09Z</time></trkpt>
<trkpt lat="54.432793" lon="10.16849452"><ele>41.29999923706055</ele><course>306.3</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:12Z</time></trkpt>
<trkpt lat="54.43278981" lon="10.16847729"><ele>41.29999923706055</ele><course>306.3</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:14Z</time></trkpt>
<trkpt lat="54.43275907" lon="10.16841823"><ele>41.0</ele><course>197.9</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>1</sat><time>2012-12-19T10:56:21Z</time></trkpt>
<trkpt lat="54.43275867" lon="10.16841811"><ele>41.0</ele><course>36.4</course><speed>0.75</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:23Z</time></trkpt>
<trkpt lat="54.43278175" lon="10.16844743"><ele>41.20000076293945</ele><course>34.3</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:25Z</time></trkpt>
<trkpt lat="54.43277477" lon="10.16845457"><ele>41.20000076293945</ele><course>34.3</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:27Z</time></trkpt>
<trkpt lat="54.43276857" lon="10.16846928"><ele>41.70000076293945</ele><course>137.5</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:29Z</time></trkpt>
<trkpt lat="54.43275738" lon="10.16850234"><ele>41.599998474121094</ele><course>105.2</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:31Z</time></trkpt>
<trkpt lat="54.43275559" lon="10.16854259"><ele>41.70000076293945</ele><course>89.8</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:33Z</time></trkpt>
<trkpt lat="54.43275148" lon="10.16856383"><ele>40.79999923706055</ele><course>104.0</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:35Z</time></trkpt>
<trkpt lat="54.43274495" lon="10.16858048"><ele>40.70000076293945</ele><course>140.0</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:37Z</time></trkpt>
<trkpt lat="54.4327189" lon="10.16859558"><ele>40.599998474121094</ele><course>177.5</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:39Z</time></trkpt>
<trkpt lat="54.43270725" lon="10.16859652"><ele>40.400001525878906</ele><course>185.5</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:56:41Z</time></trkpt>
<trkpt lat="54.43277063" lon="10.16859882"><ele>40.5</ele><course>9.3</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>5</sat><time>2012-12-19T10:56:49Z</time></trkpt>
<trkpt lat="54.43277215" lon="10.16860856"><ele>40.099998474121094</ele><course>18.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:56:51Z</time></trkpt>
<trkpt lat="54.43276905" lon="10.16860432"><ele>39.900001525878906</ele><course>18.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:56:53Z</time></trkpt>
<trkpt lat="54.43277017" lon="10.16860465"><ele>39.900001525878906</ele><course>18.0</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:56:55Z</time></trkpt>
<trkpt lat="54.43240684" lon="10.168697"><ele>39.400001525878906</ele><course>171.8</course><speed>9.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>8</sat><time>2012-12-19T10:57:03Z</time></trkpt>
<trkpt lat="54.43211051" lon="10.16878066"><ele>39.29999923706055</ele><course>171.6</course><speed>11.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:06Z</time></trkpt>
<trkpt lat="54.431909" lon="10.16882937"><ele>39.20000076293945</ele><course>172.1</course><speed>11.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:08Z</time></trkpt>
<trkpt lat="54.43171703" lon="10.16887316"><ele>40.099998474121094</ele><course>171.9</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:10Z</time></trkpt>
<trkpt lat="54.43152387" lon="10.16891041"><ele>40.599998474121094</ele><course>172.0</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:12Z</time></trkpt>
<trkpt lat="54.43133275" lon="10.16895111"><ele>40.400001525878906</ele><course>172.1</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:14Z</time></trkpt>
<trkpt lat="54.4311431" lon="10.1689936"><ele>40.70000076293945</ele><course>171.7</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:16Z</time></trkpt>
<trkpt lat="54.43095469" lon="10.1690416"><ele>40.5</ele><course>171.7</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:18Z</time></trkpt>
<trkpt lat="54.4305972" lon="10.16907812"><ele>40.20000076293945</ele><course>192.8</course><speed>4.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:23Z</time></trkpt>
<trkpt lat="54.43053767" lon="10.16907023"><ele>40.20000076293945</ele><course>155.0</course><speed>2.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:25Z</time></trkpt>
<trkpt lat="54.43051954" lon="10.16916176"><ele>40.400001525878906</ele><course>65.0</course><speed>2.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:28Z</time></trkpt>
<trkpt lat="54.43054701" lon="10.16920438"><ele>40.79999923706055</ele><course>15.1</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:30Z</time></trkpt>
<trkpt lat="54.43054778" lon="10.16920633"><ele>41.29999923706055</ele><course>15.1</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:33Z</time></trkpt>
<trkpt lat="54.43053699" lon="10.16918965"><ele>41.400001525878906</ele><course>217.1</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:35Z</time></trkpt>
<trkpt lat="54.43052616" lon="10.16917976"><ele>41.29999923706055</ele><course>217.1</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:37Z</time></trkpt>
<trkpt lat="54.43053032" lon="10.16917992"><ele>41.20000076293945</ele><course>217.1</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:39Z</time></trkpt>
<trkpt lat="54.43056778" lon="10.16915999"><ele>41.0</ele><course>340.6</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:42Z</time></trkpt>
<trkpt lat="54.43058656" lon="10.16914156"><ele>40.79999923706055</ele><course>339.8</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:44Z</time></trkpt>
<trkpt lat="54.43058398" lon="10.16914639"><ele>40.900001525878906</ele><course>339.8</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:46Z</time></trkpt>
<trkpt lat="54.43058342" lon="10.16914877"><ele>41.0</ele><course>339.8</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:48Z</time></trkpt>
<trkpt lat="54.43057814" lon="10.16916175"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>5</sat><time>2012-12-19T10:57:55Z</time></trkpt>
<trkpt lat="54.43057821" lon="10.16916181"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:57:57Z</time></trkpt>
<trkpt lat="54.43057824" lon="10.16916197"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>0.8485281</hdop><src>gps</src><sat>6</sat><time>2012-12-19T10:58:05Z</time></trkpt>
<trkpt lat="54.43057824" lon="10.16916197"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:07Z</time></trkpt>
<trkpt lat="54.43057823" lon="10.16916196"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:09Z</time></trkpt>
<trkpt lat="54.43057823" lon="10.16916192"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:11Z</time></trkpt>
<trkpt lat="54.43057823" lon="10.16916188"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:13Z</time></trkpt>
<trkpt lat="54.4305782" lon="10.1691619"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:15Z</time></trkpt>
<trkpt lat="54.43057819" lon="10.16916188"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:17Z</time></trkpt>
<trkpt lat="54.43057819" lon="10.16916188"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:19Z</time></trkpt>
<trkpt lat="54.4305782" lon="10.16916187"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>1.3416407</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:21Z</time></trkpt>
<trkpt lat="54.4305782" lon="10.16916189"><ele>40.599998474121094</ele><course>339.8</course><speed>0.0</speed><hdop>1.4422204</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:23Z</time></trkpt>
<trkpt lat="54.4305782" lon="10.1691619"><ele>40.599998474121094</ele><course>339.8</course><speed>0.25</speed><hdop>1.4422204</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:26Z</time></trkpt>
<trkpt lat="54.43065327" lon="10.16913958"><ele>42.20000076293945</ele><course>344.0</course><speed>4.75</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:28Z</time></trkpt>
<trkpt lat="54.43076575" lon="10.1690925"><ele>41.099998474121094</ele><course>344.9</course><speed>7.25</speed><hdop>1.0</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:58:30Z</time></trkpt>
<trkpt lat="54.43101094" lon="10.16902712"><ele>38.29999923706055</ele><course>351.6</course><speed>9.5</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:33Z</time></trkpt>
<trkpt lat="54.43119097" lon="10.16898797"><ele>39.0</ele><course>351.5</course><speed>10.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:35Z</time></trkpt>
<trkpt lat="54.43136935" lon="10.16894085"><ele>40.5</ele><course>351.7</course><speed>10.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:37Z</time></trkpt>
<trkpt lat="54.43155057" lon="10.16890451"><ele>41.0</ele><course>352.1</course><speed>10.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:39Z</time></trkpt>
<trkpt lat="54.43278529" lon="10.16846199"><ele>37.70000076293945</ele><course>333.4</course><speed>5.0</speed><hdop>1.7888544</hdop><src>gps</src><sat>5</sat><time>2012-12-19T10:58:55Z</time></trkpt>
<trkpt lat="54.43278302" lon="10.16844972"><ele>41.20000076293945</ele><course>284.6</course><speed>1.5</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:57Z</time></trkpt>
<trkpt lat="54.43278727" lon="10.16843158"><ele>40.400001525878906</ele><course>284.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:58:59Z</time></trkpt>
<trkpt lat="54.43279092" lon="10.16843429"><ele>39.0</ele><course>85.8</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:01Z</time></trkpt>
<trkpt lat="54.43279582" lon="10.16846366"><ele>39.0</ele><course>66.3</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:03Z</time></trkpt>
<trkpt lat="54.4328013" lon="10.16847698"><ele>39.20000076293945</ele><course>66.3</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:05Z</time></trkpt>
<trkpt lat="54.43280009" lon="10.16847955"><ele>38.599998474121094</ele><course>66.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:07Z</time></trkpt>
<trkpt lat="54.43279137" lon="10.1684796"><ele>38.70000076293945</ele><course>66.3</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:09Z</time></trkpt>
<trkpt lat="54.43277218" lon="10.16849121"><ele>39.0</ele><course>147.7</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:11Z</time></trkpt>
<trkpt lat="54.43278905" lon="10.16849708"><ele>38.599998474121094</ele><course>147.7</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>6</sat><time>2012-12-19T10:59:18Z</time></trkpt>
<trkpt lat="54.43279361" lon="10.16850682"><ele>38.29999923706055</ele><course>147.7</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:20Z</time></trkpt>
<trkpt lat="54.43278201" lon="10.16855043"><ele>37.70000076293945</ele><course>104.1</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:22Z</time></trkpt>
<trkpt lat="54.43276411" lon="10.16859568"><ele>37.900001525878906</ele><course>135.6</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:24Z</time></trkpt>
<trkpt lat="54.43274075" lon="10.16861847"><ele>37.599998474121094</ele><course>169.9</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:26Z</time></trkpt>
<trkpt lat="54.43271886" lon="10.16861587"><ele>38.29999923706055</ele><course>185.2</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:28Z</time></trkpt>
<trkpt lat="54.43271343" lon="10.16861855"><ele>38.20000076293945</ele><course>185.2</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:30Z</time></trkpt>
<trkpt lat="54.43273357" lon="10.16861491"><ele>38.099998474121094</ele><course>3.1</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:32Z</time></trkpt>
<trkpt lat="54.43275637" lon="10.16860787"><ele>38.400001525878906</ele><course>359.7</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:34Z</time></trkpt>
<trkpt lat="54.43276803" lon="10.16860498"><ele>38.5</ele><course>359.7</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:36Z</time></trkpt>
<trkpt lat="54.43277054" lon="10.16860702"><ele>39.5</ele><course>359.7</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:39Z</time></trkpt>
<trkpt lat="54.43277017" lon="10.16860993"><ele>39.79999923706055</ele><course>359.7</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:41Z</time></trkpt>
<trkpt lat="54.4327693" lon="10.16861122"><ele>40.29999923706055</ele><course>359.7</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:43Z</time></trkpt>
<trkpt lat="54.4327689" lon="10.16861318"><ele>40.5</ele><course>359.7</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:45Z</time></trkpt>
<trkpt lat="54.43276845" lon="10.16861436"><ele>40.70000076293945</ele><course>359.7</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:47Z</time></trkpt>
<trkpt lat="54.43276859" lon="10.16861487"><ele>40.79999923706055</ele><course>359.7</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:49Z</time></trkpt>
<trkpt lat="54.43276861" lon="10.16861491"><ele>40.79999923706055</ele><course>359.7</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:51Z</time></trkpt>
<trkpt lat="54.43276862" lon="10.16861492"><ele>40.79999923706055</ele><course>359.7</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:53Z</time></trkpt>
<trkpt lat="54.43276862" lon="10.16861492"><ele>40.79999923706055</ele><course>359.7</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:55Z</time></trkpt>
<trkpt lat="54.43276861" lon="10.16861492"><ele>40.79999923706055</ele><course>359.7</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T10:59:57Z</time></trkpt>
<trkpt lat="54.43274964" lon="10.1686157"><ele>40.70000076293945</ele><course>171.0</course><speed>3.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T10:59:59Z</time></trkpt>
<trkpt lat="54.4325596" lon="10.16865154"><ele>40.400001525878906</ele><course>173.3</course><speed>9.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:02Z</time></trkpt>
<trkpt lat="54.4323853" lon="10.16868722"><ele>40.70000076293945</ele><course>172.9</course><speed>10.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:04Z</time></trkpt>
<trkpt lat="54.43220118" lon="10.16872275"><ele>40.599998474121094</ele><course>171.7</course><speed>10.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:06Z</time></trkpt>
<trkpt lat="54.432014" lon="10.16877518"><ele>40.400001525878906</ele><course>172.2</course><speed>10.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:08Z</time></trkpt>
<trkpt lat="54.43182945" lon="10.16881879"><ele>40.400001525878906</ele><course>172.9</course><speed>10.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:10Z</time></trkpt>
<trkpt lat="54.4315441" lon="10.16888412"><ele>39.79999923706055</ele><course>170.8</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:13Z</time></trkpt>
<trkpt lat="54.43135388" lon="10.16893583"><ele>40.099998474121094</ele><course>171.0</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:15Z</time></trkpt>
<trkpt lat="54.4311616" lon="10.16898167"><ele>40.0</ele><course>172.0</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:17Z</time></trkpt>
<trkpt lat="54.43096739" lon="10.1690221"><ele>40.099998474121094</ele><course>172.8</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:19Z</time></trkpt>
<trkpt lat="54.43077774" lon="10.16905566"><ele>39.70000076293945</ele><course>175.9</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:21Z</time></trkpt>
<trkpt lat="54.43061311" lon="10.16906607"><ele>39.79999923706055</ele><course>180.0</course><speed>6.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:23Z</time></trkpt>
<trkpt lat="54.43053702" lon="10.16909519"><ele>40.599998474121094</ele><course>143.2</course><speed>3.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:25Z</time></trkpt>
<trkpt lat="54.43053809" lon="10.16916912"><ele>40.0</ele><course>65.4</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:27Z</time></trkpt>
<trkpt lat="54.43056289" lon="10.16921317"><ele>39.900001525878906</ele><course>33.3</course><speed>1.3462912</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:29Z</time></trkpt>
<trkpt lat="54.43056196" lon="10.16921754"><ele>40.0</ele><course>33.3</course><speed>0.559017</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:31Z</time></trkpt>
<trkpt lat="54.43055681" lon="10.16919264"><ele>40.0</ele><course>242.8</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:33Z</time></trkpt>
<trkpt lat="54.43053437" lon="10.1691685"><ele>40.29999923706055</ele><course>215.4</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:35Z</time></trkpt>
<trkpt lat="54.4305321" lon="10.16916101"><ele>39.79999923706055</ele><course>215.4</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:37Z</time></trkpt>
<trkpt lat="54.43057429" lon="10.1691503"><ele>40.599998474121094</ele><course>345.8</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:41Z</time></trkpt>
<trkpt lat="54.43058174" lon="10.16914358"><ele>41.29999923706055</ele><course>345.8</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:00:44Z</time></trkpt>
<trkpt lat="54.43057884" lon="10.16914588"><ele>41.900001525878906</ele><course>345.8</course><speed>0.0</speed><hdop>2.0</hdop><src>gps</src><sat>5</sat><time>2012-12-19T11:01:03Z</time></trkpt>
<trkpt lat="54.43057811" lon="10.16914808"><ele>42.400001525878906</ele><course>345.8</course><speed>0.0</speed><hdop>1.6970562</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:05Z</time></trkpt>
<trkpt lat="54.43057725" lon="10.16915055"><ele>42.900001525878906</ele><course>345.8</course><speed>0.0</speed><hdop>1.4422204</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:07Z</time></trkpt>
<trkpt lat="54.43057826" lon="10.16915167"><ele>42.900001525878906</ele><course>345.8</course><speed>0.0</speed><hdop>1.4422204</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:09Z</time></trkpt>
<trkpt lat="54.4305795" lon="10.16915316"><ele>43.0</ele><course>345.8</course><speed>0.0</speed><hdop>1.4422204</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:11Z</time></trkpt>
<trkpt lat="54.43057962" lon="10.16915314"><ele>42.900001525878906</ele><course>345.8</course><speed>0.0</speed><hdop>1.4422204</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:14Z</time></trkpt>
<trkpt lat="54.43057955" lon="10.16915308"><ele>43.0</ele><course>345.8</course><speed>0.0</speed><hdop>1.3416407</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:16Z</time></trkpt>
<trkpt lat="54.43057951" lon="10.16915304"><ele>43.0</ele><course>345.8</course><speed>0.0</speed><hdop>1.3416407</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:18Z</time></trkpt>
<trkpt lat="54.43057948" lon="10.16915305"><ele>43.0</ele><course>345.8</course><speed>0.0</speed><hdop>0.8485281</hdop><src>gps</src><sat>8</sat><time>2012-12-19T11:01:25Z</time></trkpt>
<trkpt lat="54.43057946" lon="10.16915304"><ele>43.0</ele><course>345.8</course><speed>0.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:27Z</time></trkpt>
<trkpt lat="54.43057947" lon="10.16915302"><ele>43.0</ele><course>345.8</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:29Z</time></trkpt>
<trkpt lat="54.43058981" lon="10.16917751"><ele>61.20000076293945</ele><course>345.8</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:31Z</time></trkpt>
<trkpt lat="54.43064048" lon="10.16917911"><ele>59.29999923706055</ele><course>347.1</course><speed>3.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:33Z</time></trkpt>
<trkpt lat="54.43133425" lon="10.16896403"><ele>51.79999923706055</ele><course>351.3</course><speed>11.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>8</sat><time>2012-12-19T11:01:41Z</time></trkpt>
<trkpt lat="54.43152674" lon="10.16891544"><ele>51.5</ele><course>351.9</course><speed>10.75</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:43Z</time></trkpt>
<trkpt lat="54.43172641" lon="10.16885122"><ele>50.20000076293945</ele><course>352.1</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:45Z</time></trkpt>
<trkpt lat="54.43191932" lon="10.1688012"><ele>49.29999923706055</ele><course>352.1</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:47Z</time></trkpt>
<trkpt lat="54.43220643" lon="10.16874243"><ele>48.20000076293945</ele><course>352.0</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:50Z</time></trkpt>
<trkpt lat="54.43248952" lon="10.16867042"><ele>47.70000076293945</ele><course>351.3</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:53Z</time></trkpt>
<trkpt lat="54.43261463" lon="10.16865407"><ele>47.0</ele><course>352.6</course><speed>5.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:55Z</time></trkpt>
<trkpt lat="54.43267959" lon="10.16863804"><ele>46.900001525878906</ele><course>351.9</course><speed>2.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:57Z</time></trkpt>
<trkpt lat="54.43271603" lon="10.16862845"><ele>46.5</ele><course>350.7</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:01:59Z</time></trkpt>
<trkpt lat="54.43274715" lon="10.1686111"><ele>46.20000076293945</ele><course>346.4</course><speed>2.0615528</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:02:01Z</time></trkpt>
<trkpt lat="54.43277648" lon="10.16857157"><ele>45.29999923706055</ele><course>320.2</course><speed>2.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:02:03Z</time></trkpt>
<trkpt lat="54.43279967" lon="10.16851235"><ele>44.20000076293945</ele><course>301.0</course><speed>2.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:02:05Z</time></trkpt>
<trkpt lat="54.43280927" lon="10.16843886"><ele>43.70000076293945</ele><course>257.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>8</sat><time>2012-12-19T11:02:12Z</time></trkpt>
<trkpt lat="54.43281042" lon="10.16844258"><ele>43.79999923706055</ele><course>257.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:02:14Z</time></trkpt>
<trkpt lat="54.43281189" lon="10.16844306"><ele>43.400001525878906</ele><course>257.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:02:16Z</time></trkpt>
<trkpt lat="54.43281353" lon="10.1684401"><ele>42.900001525878906</ele><course>257.6</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:02:18Z</time></trkpt>
<trkpt lat="54.43065418" lon="10.1691878"><ele>30.100000381469727</ele><course>351.8</course><speed>6.823672</speed><hdop>2.0</hdop><src>gps</src><sat>11</sat><time>2012-12-19T11:04:47Z</time></trkpt>
<trkpt lat="54.43091658" lon="10.16904786"><ele>42.5</ele><course>350.5</course><speed>11.0</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:04:49Z</time></trkpt>
<trkpt lat="54.43118157" lon="10.16894269"><ele>54.599998474121094</ele><course>351.0</course><speed>11.0</speed><hdop>3.5777087</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:04:51Z</time></trkpt>
<trkpt lat="54.43128582" lon="10.1689261"><ele>39.29999923706055</ele><course>352.1</course><speed>11.25</speed><hdop>1.4422204</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:04:53Z</time></trkpt>
<trkpt lat="54.43148227" lon="10.16887666"><ele>38.900001525878906</ele><course>352.1</course><speed>11.0</speed><hdop>0.8485281</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:04:55Z</time></trkpt>
<trkpt lat="54.43167487" lon="10.16883291"><ele>39.099998474121094</ele><course>352.5</course><speed>10.5</speed><hdop>0.8485281</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:04:57Z</time></trkpt>
<trkpt lat="54.43187644" lon="10.16878558"><ele>39.70000076293945</ele><course>352.1</course><speed>11.0</speed><hdop>0.8485281</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:04:59Z</time></trkpt>
<trkpt lat="54.43206744" lon="10.16873821"><ele>39.79999923706055</ele><course>351.7</course><speed>10.75</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:01Z</time></trkpt>
<trkpt lat="54.43224878" lon="10.16869757"><ele>40.29999923706055</ele><course>351.9</course><speed>10.75</speed><hdop>1.0</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:03Z</time></trkpt>
<trkpt lat="54.43244945" lon="10.16865159"><ele>40.70000076293945</ele><course>352.0</course><speed>10.75</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:05Z</time></trkpt>
<trkpt lat="54.43270678" lon="10.16858865"><ele>41.099998474121094</ele><course>349.0</course><speed>9.0</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:08Z</time></trkpt>
<trkpt lat="54.43276861" lon="10.1683779"><ele>42.0</ele><course>229.6</course><speed>1.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>5</sat><time>2012-12-19T11:05:16Z</time></trkpt>
<trkpt lat="54.43277331" lon="10.16841148"><ele>41.70000076293945</ele><course>226.0</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T11:05:18Z</time></trkpt>
<trkpt lat="54.43278154" lon="10.16843509"><ele>41.5</ele><course>54.4</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:20Z</time></trkpt>
<trkpt lat="54.43279011" lon="10.16846136"><ele>41.70000076293945</ele><course>48.6</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T11:05:22Z</time></trkpt>
<trkpt lat="54.43278873" lon="10.1685124"><ele>39.79999923706055</ele><course>106.7</course><speed>0.75</speed><hdop>0.7211102</hdop><src>gps</src><sat>6</sat><time>2012-12-19T11:05:31Z</time></trkpt>
<trkpt lat="54.43276554" lon="10.16856626"><ele>39.0</ele><course>133.2</course><speed>1.5</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:33Z</time></trkpt>
<trkpt lat="54.4327323" lon="10.16857875"><ele>39.20000076293945</ele><course>186.6</course><speed>1.5</speed><hdop>0.7211102</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:35Z</time></trkpt>
<trkpt lat="54.43271732" lon="10.16858878"><ele>40.099998474121094</ele><course>186.6</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:37Z</time></trkpt>
<trkpt lat="54.43272232" lon="10.16858727"><ele>40.20000076293945</ele><course>347.7</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:39Z</time></trkpt>
<trkpt lat="54.43274513" lon="10.16859435"><ele>40.29999923706055</ele><course>5.3</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:41Z</time></trkpt>
<trkpt lat="54.43275521" lon="10.16859123"><ele>40.900001525878906</ele><course>3.5</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:43Z</time></trkpt>
<trkpt lat="54.43276063" lon="10.16858591"><ele>40.29999923706055</ele><course>3.5</course><speed>0.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:45Z</time></trkpt>
<trkpt lat="54.43276232" lon="10.16858767"><ele>40.20000076293945</ele><course>3.5</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:47Z</time></trkpt>
<trkpt lat="54.43276336" lon="10.16858918"><ele>40.0</ele><course>3.5</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:49Z</time></trkpt>
<trkpt lat="54.43276266" lon="10.16859"><ele>39.70000076293945</ele><course>3.5</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:51Z</time></trkpt>
<trkpt lat="54.43276198" lon="10.16858955"><ele>39.900001525878906</ele><course>3.5</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:53Z</time></trkpt>
<trkpt lat="54.43276171" lon="10.16859016"><ele>40.0</ele><course>3.5</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:55Z</time></trkpt>
<trkpt lat="54.43276159" lon="10.16859004"><ele>40.0</ele><course>3.5</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:05:57Z</time></trkpt>
<trkpt lat="54.43254266" lon="10.16864678"><ele>39.70000076293945</ele><course>172.2</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:03Z</time></trkpt>
<trkpt lat="54.43234557" lon="10.16869936"><ele>39.5</ele><course>171.8</course><speed>11.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:05Z</time></trkpt>
<trkpt lat="54.43214547" lon="10.16874793"><ele>39.20000076293945</ele><course>171.7</course><speed>11.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:07Z</time></trkpt>
<trkpt lat="54.43194885" lon="10.1687933"><ele>39.0</ele><course>172.0</course><speed>11.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:09Z</time></trkpt>
<trkpt lat="54.43175468" lon="10.16883969"><ele>38.900001525878906</ele><course>171.6</course><speed>11.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:11Z</time></trkpt>
<trkpt lat="54.43104911" lon="10.1689969"><ele>40.0</ele><course>173.2</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>9</sat><time>2012-12-19T11:06:18Z</time></trkpt>
<trkpt lat="54.43085977" lon="10.16904165"><ele>39.599998474121094</ele><course>174.5</course><speed>10.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:20Z</time></trkpt>
<trkpt lat="54.43061221" lon="10.16907294"><ele>39.599998474121094</ele><course>177.9</course><speed>6.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:23Z</time></trkpt>
<trkpt lat="54.43053229" lon="10.16901271"><ele>40.20000076293945</ele><course>249.8</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:26Z</time></trkpt>
<trkpt lat="54.43054174" lon="10.16895685"><ele>40.5</ele><course>303.5</course><speed>1.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:28Z</time></trkpt>
<trkpt lat="54.43056338" lon="10.16892448"><ele>40.400001525878906</ele><course>339.8</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:30Z</time></trkpt>
<trkpt lat="54.43057773" lon="10.16891857"><ele>40.20000076293945</ele><course>350.9</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:32Z</time></trkpt>
<trkpt lat="54.43058108" lon="10.16892524"><ele>40.400001525878906</ele><course>350.9</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:34Z</time></trkpt>
<trkpt lat="54.43058096" lon="10.1689278"><ele>40.29999923706055</ele><course>350.9</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:36Z</time></trkpt>
<trkpt lat="54.43058073" lon="10.16892979"><ele>40.29999923706055</ele><course>350.9</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:38Z</time></trkpt>
<trkpt lat="54.43057965" lon="10.16893046"><ele>40.400001525878906</ele><course>350.9</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:40Z</time></trkpt>
<trkpt lat="54.43057595" lon="10.16893252"><ele>41.0</ele><course>350.9</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:42Z</time></trkpt>
<trkpt lat="54.43057528" lon="10.16893326"><ele>41.099998474121094</ele><course>350.9</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:44Z</time></trkpt>
<trkpt lat="54.43057516" lon="10.16893343"><ele>41.099998474121094</ele><course>350.9</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:46Z</time></trkpt>
<trkpt lat="54.43057517" lon="10.16893341"><ele>41.099998474121094</ele><course>350.9</course><speed>0.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:48Z</time></trkpt>
<trkpt lat="54.43060046" lon="10.16895587"><ele>41.5</ele><course>35.1</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>11</sat><time>2012-12-19T11:06:51Z</time></trkpt>
<trkpt lat="54.43061676" lon="10.16898573"><ele>41.400001525878906</ele><course>54.3</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:53Z</time></trkpt>
<trkpt lat="54.43062859" lon="10.16901584"><ele>41.5</ele><course>61.1</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:55Z</time></trkpt>
<trkpt lat="54.43063596" lon="10.16903848"><ele>41.20000076293945</ele><course>68.9</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:57Z</time></trkpt>
<trkpt lat="54.43063844" lon="10.16907118"><ele>41.599998474121094</ele><course>71.9</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:06:59Z</time></trkpt>
<trkpt lat="54.43064561" lon="10.16910394"><ele>41.900001525878906</ele><course>74.3</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:01Z</time></trkpt>
<trkpt lat="54.4306482" lon="10.16911858"><ele>41.70000076293945</ele><course>74.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:03Z</time></trkpt>
<trkpt lat="54.43064933" lon="10.16911628"><ele>41.599998474121094</ele><course>74.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:05Z</time></trkpt>
<trkpt lat="54.43065079" lon="10.16911431"><ele>41.29999923706055</ele><course>74.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:07Z</time></trkpt>
<trkpt lat="54.43065102" lon="10.1691115"><ele>41.099998474121094</ele><course>74.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:09Z</time></trkpt>
<trkpt lat="54.43065147" lon="10.16910908"><ele>41.099998474121094</ele><course>74.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:11Z</time></trkpt>
<trkpt lat="54.43065446" lon="10.16911726"><ele>41.20000076293945</ele><course>74.3</course><speed>0.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:13Z</time></trkpt>
<trkpt lat="54.43067991" lon="10.16913114"><ele>41.400001525878906</ele><course>3.5</course><speed>2.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:15Z</time></trkpt>
<trkpt lat="54.43071181" lon="10.16911823"><ele>41.29999923706055</ele><course>336.0</course><speed>1.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:17Z</time></trkpt>
<trkpt lat="54.43073026" lon="10.16906781"><ele>42.0</ele><course>298.6</course><speed>2.0615528</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:19Z</time></trkpt>
<trkpt lat="54.4307148" lon="10.16902511"><ele>41.70000076293945</ele><course>229.3</course><speed>1.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:21Z</time></trkpt>
<trkpt lat="54.4306923" lon="10.16898712"><ele>41.70000076293945</ele><course>219.0</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:23Z</time></trkpt>
<trkpt lat="54.4306707" lon="10.16895125"><ele>41.70000076293945</ele><course>230.3</course><speed>1.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:26Z</time></trkpt>
<trkpt lat="54.43066591" lon="10.16893753"><ele>42.0</ele><course>230.3</course><speed>0.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:28Z</time></trkpt>
<trkpt lat="54.43066558" lon="10.16893666"><ele>42.0</ele><course>222.9</course><speed>1.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:30Z</time></trkpt>
<trkpt lat="54.4306212" lon="10.16890469"><ele>42.70000076293945</ele><course>177.3</course><speed>3.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:32Z</time></trkpt>
<trkpt lat="54.43055872" lon="10.16896914"><ele>42.599998474121094</ele><course>128.7</course><speed>4.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:34Z</time></trkpt>
<trkpt lat="54.43050817" lon="10.16914235"><ele>42.099998474121094</ele><course>108.4</course><speed>4.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:37Z</time></trkpt>
<trkpt lat="54.43048602" lon="10.16926825"><ele>41.70000076293945</ele><course>104.4</course><speed>4.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:39Z</time></trkpt>
<trkpt lat="54.4304711" lon="10.16939046"><ele>41.599998474121094</ele><course>102.2</course><speed>3.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:41Z</time></trkpt>
<trkpt lat="54.4304639" lon="10.16950136"><ele>41.400001525878906</ele><course>92.1</course><speed>3.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:43Z</time></trkpt>
<trkpt lat="54.43047806" lon="10.16959772"><ele>41.29999923706055</ele><course>64.4</course><speed>3.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:45Z</time></trkpt>
<trkpt lat="54.43052418" lon="10.16961685"><ele>41.099998474121094</ele><course>354.2</course><speed>2.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:47Z</time></trkpt>
<trkpt lat="54.43051058" lon="10.16939816"><ele>41.099998474121094</ele><course>207.7</course><speed>2.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>6</sat><time>2012-12-19T11:07:55Z</time></trkpt>
<trkpt lat="54.43050815" lon="10.16952128"><ele>40.79999923706055</ele><course>84.9</course><speed>3.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:57Z</time></trkpt>
<trkpt lat="54.43051757" lon="10.16961082"><ele>40.70000076293945</ele><course>55.9</course><speed>2.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:07:59Z</time></trkpt>
<trkpt lat="54.43055012" lon="10.16964109"><ele>40.400001525878906</ele><course>5.4</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:08:01Z</time></trkpt>
<trkpt lat="54.43058612" lon="10.16962381"><ele>40.70000076293945</ele><course>329.4</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:08:03Z</time></trkpt>
<trkpt lat="54.4306113" lon="10.16957624"><ele>40.79999923706055</ele><course>303.1</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:08:05Z</time></trkpt>
<trkpt lat="54.43060325" lon="10.1695141"><ele>40.900001525878906</ele><course>235.6</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:08:07Z</time></trkpt>
<trkpt lat="54.43056712" lon="10.16948906"><ele>41.29999923706055</ele><course>185.0</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:08:09Z</time></trkpt>
<trkpt lat="54.43052946" lon="10.16951748"><ele>41.099998474121094</ele><course>129.6</course><speed>2.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:08:11Z</time></trkpt>
<trkpt lat="54.43053857" lon="10.16962929"><ele>40.79999923706055</ele><course>24.9</course><speed>2.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:08:15Z</time></trkpt>
<trkpt lat="54.43056676" lon="10.16964339"><ele>41.0</ele><course>2.0</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:08:17Z</time></trkpt>
<trkpt lat="54.43057082" lon="10.16940228"><ele>40.70000076293945</ele><course>171.4</course><speed>2.0615528</speed><hdop>2.0</hdop><src>gps</src><sat>7</sat><time>2012-12-19T11:10:42Z</time></trkpt>
<trkpt lat="54.43054445" lon="10.16948303"><ele>33.099998474121094</ele><course>104.2</course><speed>1.3462912</speed><hdop>1.4422204</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:10:44Z</time></trkpt>
<trkpt lat="54.4304964" lon="10.16953782"><ele>38.70000076293945</ele><course>100.0</course><speed>1.5811388</speed><hdop>2.6832814</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:10:46Z</time></trkpt>
<trkpt lat="54.43048754" lon="10.16958998"><ele>42.5</ele><course>91.8</course><speed>1.5811388</speed><hdop>1.1313708</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:10:48Z</time></trkpt>
<trkpt lat="54.43046409" lon="10.16965521"><ele>41.79999923706055</ele><course>127.4</course><speed>3.0</speed><hdop>0.8485281</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:10:50Z</time></trkpt>
<trkpt lat="54.43043131" lon="10.16972244"><ele>41.79999923706055</ele><course>141.6</course><speed>3.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:10:52Z</time></trkpt>
<trkpt lat="54.43039158" lon="10.16977536"><ele>40.79999923706055</ele><course>151.7</course><speed>3.2882366</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:10:54Z</time></trkpt>
<trkpt lat="54.43021246" lon="10.16987227"><ele>38.599998474121094</ele><course>169.0</course><speed>3.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>7</sat><time>2012-12-19T11:11:02Z</time></trkpt>
<trkpt lat="54.43016291" lon="10.16989913"><ele>38.29999923706055</ele><course>164.8</course><speed>3.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:11:04Z</time></trkpt>
<trkpt lat="54.43011186" lon="10.16992503"><ele>38.79999923706055</ele><course>168.0</course><speed>3.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:11:06Z</time></trkpt>
<trkpt lat="54.43005522" lon="10.16993589"><ele>39.599998474121094</ele><course>182.2</course><speed>2.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:11:08Z</time></trkpt>
<trkpt lat="54.43000737" lon="10.1699276"><ele>40.79999923706055</ele><course>197.2</course><speed>2.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:11:10Z</time></trkpt>
<trkpt lat="54.42997965" lon="10.16989719"><ele>41.099998474121094</ele><course>217.2</course><speed>1.5</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:11:12Z</time></trkpt>
<trkpt lat="54.42995738" lon="10.16986028"><ele>41.5</ele><course>231.0</course><speed>1.75</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:11:14Z</time></trkpt>
<trkpt lat="54.42994569" lon="10.16976584"><ele>41.70000076293945</ele><course>269.0</course><speed>3.25</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:11:16Z</time></trkpt>
<trkpt lat="54.42996078" lon="10.16965723"><ele>41.5</ele><course>296.5</course><speed>4.0</speed><hdop>0.5656854</hdop><src>gps</src><sat>12</sat><time>2012-12-19T11:11:18Z</time></trkpt>
</trkseg></trk></gpx>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,108 @@
package com.sap.sailing.nmeaconnector.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import com.sap.sailing.nmeaconnector.BATSentence;
import com.sap.sailing.nmeaconnector.BATSentence.WindVaneBatteryStatus;
import com.sap.sailing.nmeaconnector.NmeaFactory;
import com.sap.sailing.nmeaconnector.impl.BATParser;
import net.sf.marineapi.nmea.event.AbstractSentenceListener;
import net.sf.marineapi.nmea.io.SentenceReader;
import net.sf.marineapi.nmea.sentence.TalkerId;
public class BATTest {
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(10 * 1000);
public static final String EXAMPLE = "$WIBAT,1,5";
BATSentence empty;
BATSentence bat;
@Before
public void setUp() throws Exception {
empty = new BATParser(TalkerId.WI);
bat = new BATParser(EXAMPLE);
}
@Test
public void testBATParser() {
assertEquals(TalkerId.WI, empty.getTalkerId());
assertEquals("BAT", empty.getSentenceId());
assertEquals(2, empty.getFieldCount());
}
@Test
public void testFindingBATParserThroughFactory() throws UnsupportedEncodingException, InterruptedException {
NmeaFactory.INSTANCE.getUtil().registerAdditionalParsers();
final BATSentence[] sentences = new BATSentence[1];
SentenceReader reader = new SentenceReader(new ByteArrayInputStream(EXAMPLE.getBytes("UTF-8")));
final Object monitor = new Object();
final boolean[] paused = new boolean[1];
reader.addSentenceListener(new AbstractSentenceListener<BATSentence>() {
@Override
public void sentenceRead(BATSentence sentence) {
sentences[0] = sentence;
}
@Override
public void readingPaused() {
super.readingPaused();
setPausedAndTriggerWaiters();
}
private void setPausedAndTriggerWaiters() {
synchronized (monitor) {
paused[0] = true;
monitor.notifyAll();
}
}
/**
* When reading stops without having been paused before, e.g., because the stream was
* exhausted and closed, we will also set the {@code paused[0]} value to true and notify
* waiters.
*/
@Override
public void readingStopped() {
super.readingStopped();
setPausedAndTriggerWaiters();
}
});
reader.setPauseTimeout(100);
reader.start();
synchronized (monitor) {
while (!paused[0]) {
monitor.wait(1000);
}
}
assertNotNull(sentences[0]);
assertEquals(WindVaneBatteryStatus.GOOD, sentences[0].getWindVaneBatteryStatus());
}
@Test
public void testBATParserString() {
assertEquals(TalkerId.WI, bat.getTalkerId());
assertEquals("BAT", bat.getSentenceId());
assertEquals(2, bat.getFieldCount());
}
@Test
public void testGetBaseUnitBatteryLevel() {
assertEquals(5, bat.getBaseUnitBatteryLevel());
}
@Test
public void testWindVaneBatteryStatus() {
assertEquals(WindVaneBatteryStatus.GOOD, bat.getWindVaneBatteryStatus());
}
}
@@ -0,0 +1,113 @@
package com.sap.sailing.nmeaconnector.test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.nmeaconnector.NmeaFactory;
import com.sap.sse.common.Util;
import net.sf.marineapi.nmea.event.AbstractSentenceListener;
import net.sf.marineapi.nmea.event.SentenceListener;
import net.sf.marineapi.nmea.io.SentenceReader;
import net.sf.marineapi.nmea.parser.DataNotAvailableException;
import net.sf.marineapi.nmea.sentence.PositionSentence;
import net.sf.marineapi.nmea.sentence.Sentence;
import net.sf.marineapi.nmea.sentence.TimeSentence;
import net.sf.marineapi.nmea.util.Position;
import net.sf.marineapi.nmea.util.Time;
public class GeneralizedSentenceListenerTest {
private SentenceReader sentenceReader;
private SentenceReaderSupport readerSupport;
@Before
public void setUp() throws FileNotFoundException {
sentenceReader = new SentenceReader(new FileInputStream("resources/LogSS.txt"));
readerSupport = new SentenceReaderSupport(sentenceReader);
}
@Test
public void testRegisterPositionListener() throws InterruptedException {
final List<Position> positions = new ArrayList<>();
SentenceListener positionListener = new AbstractSentenceListener<PositionSentence>() {
@Override
public void sentenceRead(PositionSentence sentence) {
try {
final Position position = sentence.getPosition();
positions.add(position);
} catch (DataNotAvailableException e) {
// this can happen and simply means we don't have a position; so we
// can safely ignore this exception
}
}
};
sentenceReader.addSentenceListener(positionListener);
readerSupport.startReading();
readerSupport.waitUntilAllMessagesHaveBeenRead();
assertFalse(positions.isEmpty());
}
@Test
public void testRegisterTimeListener() throws InterruptedException {
final List<Time> times = new ArrayList<>();
SentenceListener timeListener = new AbstractSentenceListener<TimeSentence>() {
@Override
public void sentenceRead(TimeSentence sentence) {
try {
final Time time = sentence.getTime();
times.add(time);
} catch (DataNotAvailableException e) {
// this can happen and simply means we don't have a position; so we
// can safely ignore this exception
}
}
};
sentenceReader.addSentenceListener(timeListener);
readerSupport.startReading();
readerSupport.waitUntilAllMessagesHaveBeenRead();
assertFalse(times.isEmpty());
}
@Test
public void testSimpleNmeaWindReceiverScenario() throws FileNotFoundException, InterruptedException {
final Iterable<Wind> wind = NmeaFactory.INSTANCE.readWind(new FileInputStream("resources/LogSS.txt"));
assertFalse(Util.isEmpty(wind));
}
private static class MyGenericSentenceListener<A, B extends Sentence> extends AbstractSentenceListener<B> {
private B lastSentence;
@Override
public void sentenceRead(B sentence) {
lastSentence = sentence;
}
public B getLastSentence() {
return lastSentence;
}
}
private static class MyOtherGenericSentenceListener<A> extends MyGenericSentenceListener<A, TimeSentence> {}
private static class MyConcreteSentenceListener extends MyOtherGenericSentenceListener<String> {}
private static class MyConcreteSentenceListenerSubclass extends MyConcreteSentenceListener {}
@Test
public void testComplicatedGenericSentenceListenerSubclass() throws InterruptedException {
MyConcreteSentenceListenerSubclass listener = new MyConcreteSentenceListenerSubclass();
sentenceReader.addSentenceListener(listener);
readerSupport.startReading();
readerSupport.waitUntilAllMessagesHaveBeenRead();
assertNotNull(listener.getLastSentence());
}
}
@@ -0,0 +1,30 @@
package com.sap.sailing.nmeaconnector.test;
import static org.junit.Assert.assertFalse;
import java.util.Arrays;
import org.junit.Test;
import net.sf.marineapi.nmea.event.AbstractSentenceListener;
/**
* Asserting that Class.getInterfaces() does not consider transitively implemented
* interfaces. Based on this observation it seems advisable to use
* {@link Class#isAssignableFrom(Class)} instead of a contains check with the
* directly implemented interfaces when using the {@link AbstractSentenceListener}
* pattern. See commit 746440cae18dd55bf46df8207a03da70466f9e02.
*
* @author Axel Uhl (d043530)
*
*/
public class GetInterfacesTest {
private interface I2 {}
private interface I1 extends I2 {}
private static class A implements I1 {}
@Test
public void testTransitiveInterface() {
assertFalse(Arrays.asList(A.class.getInterfaces()).contains(I2.class));
}
}
@@ -0,0 +1,36 @@
package com.sap.sailing.nmeaconnector.test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.nmeaconnector.NmeaFactory;
import com.sap.sse.common.Util;
public class NMEAWindImportTest {
Iterable<Wind> windFixes;
@Before
public void setUp() throws FileNotFoundException, InterruptedException {
NmeaFactory.INSTANCE.getUtil().registerAdditionalParsers();
windFixes = NmeaFactory.INSTANCE.readWind(new FileInputStream("resources/ExcerptFromLogJB010815.txt"));
}
@Test
public void testThatSomeWindWasFound() {
assertNotNull(windFixes);
assertTrue(!Util.isEmpty(windFixes));
}
@After
public void tearDown() {
NmeaFactory.INSTANCE.getUtil().unregisterAdditionalParsers();
}
}
@@ -0,0 +1,51 @@
package com.sap.sailing.nmeaconnector.test;
import static org.junit.Assert.assertTrue;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.nmeaconnector.NmeaFactory;
import com.sap.sse.common.Util;
public class NmeaFromZipImportTest {
private ZipInputStream zipInputStream;
@Before
public void setUp() throws FileNotFoundException {
NmeaFactory.INSTANCE.getUtil().registerAdditionalParsers();
}
@Test
public void testReadOneZipEntry() throws IOException, InterruptedException {
zipInputStream = new ZipInputStream(new FileInputStream("resources/LogSS.txt.zip"));
ZipEntry entry;
while ((entry=zipInputStream.getNextEntry()) != null) {
if (entry.getName().toLowerCase().endsWith(".txt")) {
Iterable<Wind> windFixes = NmeaFactory.INSTANCE.readWind(zipInputStream);
assertTrue(!Util.isEmpty(windFixes));
}
}
}
@Ignore("This test only makes sense if the large file resources/Log210417.zip is present locally")
@Test
public void testOtherZipFile() throws IOException, InterruptedException {
zipInputStream = new ZipInputStream(new FileInputStream("resources/Log210417.zip"));
ZipEntry entry;
while ((entry=zipInputStream.getNextEntry()) != null) {
if (entry.getName().toLowerCase().endsWith(".txt")) {
Iterable<Wind> windFixes = NmeaFactory.INSTANCE.readWind(zipInputStream);
assertTrue(!Util.isEmpty(windFixes));
}
}
}
}
@@ -0,0 +1,57 @@
package com.sap.sailing.nmeaconnector.test;
import java.util.concurrent.CountDownLatch;
import com.sap.sailing.nmeaconnector.NmeaFactory;
import net.sf.marineapi.nmea.event.AbstractSentenceListener;
import net.sf.marineapi.nmea.event.SentenceListener;
import net.sf.marineapi.nmea.io.SentenceReader;
import net.sf.marineapi.nmea.sentence.Sentence;
/**
* Abstract test support class that adds a listener to a {@link SentenceReader} passed to the constructor. This listener
* will react to the reading being {@link SentenceListener#readingPaused() paused and unblocks all calls to
* {@link #waitUntilAllMessagesHaveBeenRead()}.
* <p>
*
* Subclasses can start the reading process by calling {@link #startReading()} which is shorthand for calling
* {@link SentenceReader#start()} on the {@link SentenceReader} passed to the constructor.
*
* @author Axel Uhl (d043530)
*
*/
public class SentenceReaderSupport {
private final SentenceReader reader;
private final CountDownLatch finishedLatch;
protected SentenceReaderSupport(SentenceReader reader) {
finishedLatch = new CountDownLatch(1);
this.reader = reader;
NmeaFactory.INSTANCE.getUtil().registerAdditionalParsers();
reader.addSentenceListener(new AbstractSentenceListener<Sentence>() {
@Override
public void readingPaused() {
finishedLatch.countDown();
}
@Override
public void readingStopped() {
finishedLatch.countDown();
}
@Override
public void sentenceRead(Sentence sentence) {
}
});
reader.setPauseTimeout(100);
}
protected void startReading() {
reader.start();
}
protected void waitUntilAllMessagesHaveBeenRead() throws InterruptedException {
finishedLatch.await();
}
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.sap.sailing.nmeaconnector</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
line.separator=\r\n
@@ -0,0 +1,12 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8
@@ -0,0 +1,3 @@
eclipse.preferences.version=1
pluginProject.extensions=false
resolve.requirebundle=false
@@ -0,0 +1,17 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Nmeaconnector
Bundle-SymbolicName: com.sap.sailing.nmeaconnector
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ClassPath: .
Export-Package: com.sap.sailing.nmeaconnector
Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.domain.common,
com.sap.sse.common,
net.sf.marineapi,
com.sap.sailing.declination
Import-Package: org.osgi.framework
Bundle-ActivationPolicy: lazy
Bundle-Activator: com.sap.sailing.nmeaconnector.impl.Activator
+4
View File
@@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.nmeaconnector</artifactId>
<packaging>eclipse-plugin</packaging>
</project>
@@ -0,0 +1,45 @@
/*
* MWVSentence.java
* Copyright (C) 2011 Kimmo Tuukkanen
*
* This file is part of Java Marine API.
* <http://sourceforge.net/projects/marineapi/>
*
* Java Marine API is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Java Marine API 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Java Marine API. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sap.sailing.nmeaconnector;
import net.sf.marineapi.nmea.sentence.Sentence;
/**
* This sentence is obsolete over most of its former coverage area.
* The US/Canadian/Russian Loran-C network was shut down in 2010; it is still as of 2015 in limited use in Europe.
* Loran-C operations in Norway will cease from 1st Jan 2016. [NORWAY]
* The format of the sentence is:<pre>
* 1 2 3 4 5 6
* | | | | | |
* $--AAM,A,A,x.x,N,c--c*hh<CR><LF>
* Field Number:
* Status, BOOLEAN, A = Arrival circle entered, V = not passed
* Status, BOOLEAN, A = perpendicular passed at waypoint, V = not passed
* Arrival circle radius
* Units of radius, nautical miles
* Waypoint ID
* Checksum
* Example: GPAAM,A,A,0.10,N,WPTNME*43
* WPTNME is the waypoint name.
* </pre>
*/
public interface AAMSentence extends Sentence {
}
@@ -0,0 +1,61 @@
/*
* MWVSentence.java
* Copyright (C) 2011 Kimmo Tuukkanen
*
* This file is part of Java Marine API.
* <http://sourceforge.net/projects/marineapi/>
*
* Java Marine API is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Java Marine API 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Java Marine API. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sap.sailing.nmeaconnector;
import net.sf.marineapi.nmea.sentence.Sentence;
/**
* BAT sentence. Used for the proprietary NMEA extension of the SailTimer WindVane's battery indicator message.
* The format of the sentence is:<pre>
*
* $WIBAT,a,b*hh
*
* 1) Wind Vane Battery (0= Low, 1= Good)
* 2) Base Unit battery level (0 = Low, 9 = Good)
* 3) Checksum
* </pre>
*/
public interface BATSentence extends Sentence {
public static enum WindVaneBatteryStatus {
LOW(0), GOOD(1);
private final int value;
private WindVaneBatteryStatus(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
WindVaneBatteryStatus getWindVaneBatteryStatus();
void setWindVaneBatteryStatus(WindVaneBatteryStatus status);
/**
* Battery status is reported as value ranve 0..9 where 0 means "Low" and 9 means "Good."
*/
int getBaseUnitBatteryLevel();
void setBaseUnitBatteryLevel(int level);
}
@@ -0,0 +1,48 @@
/*
* MWVSentence.java
* Copyright (C) 2011 Kimmo Tuukkanen
*
* This file is part of Java Marine API.
* <http://sourceforge.net/projects/marineapi/>
*
* Java Marine API is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Java Marine API 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Java Marine API. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sap.sailing.nmeaconnector;
import net.sf.marineapi.nmea.sentence.Sentence;
/**
* BWC sentence. Bearing and Distance to Waypoint Latitude, N/S, Longitude, E/W, UTC, Status.
* The format of the sentence is:<pre>
*
* 1 2 3 4 5 6 7 8 9 10 11 12 13
* | | | | | | | | | | | | |
* $--BWC,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x.x,T,x.x,M,x.x,N,c--c*hh
* 1) Time (UTC)
* 2) Waypoint Latitude
* 3) N = North, S = South
* 4) Waypoint Longitude
* 5) E = East, W = West
* 6) Bearing, True
* 7) T = True
* 8) Bearing, Magnetic
* 9) M = Magnetic
* 10) Nautical Miles
* 11) N = Nautical Miles
* 12) Waypoint ID
* 13) Checksum
* </pre>
*/
public interface BWCSentence extends Sentence {
}
@@ -0,0 +1,48 @@
/*
* MWVSentence.java
* Copyright (C) 2011 Kimmo Tuukkanen
*
* This file is part of Java Marine API.
* <http://sourceforge.net/projects/marineapi/>
*
* Java Marine API is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Java Marine API 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Java Marine API. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sap.sailing.nmeaconnector;
import net.sf.marineapi.nmea.sentence.Sentence;
/**
* BWR sentence. Bearing and Distance to Waypoint Rhumb Line Latitude, N/S, Longitude, E/W, UTC, Status.
* The format of the sentence is:<pre>
*
* 1 2 3 4 5 6 7 8 9 10 11 12 13
* | | | | | | | | | | | | |
* $--BWR,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x.x,T,x.x,M,x.x,N,c--c*hh
* 1) Time (UTC)
* 2) Waypoint Latitude
* 3) N = North, S = South
* 4) Waypoint Longitude
* 5) E = East, W = West
* 6) Bearing, True
* 7) T = True
* 8) Bearing, Magnetic
* 9) M = Magnetic
* 10) Nautical Miles
* 11) N = Nautical Miles
* 12) Waypoint ID
* 13) Checksum
* </pre>
*/
public interface BWRSentence extends Sentence {
}
@@ -0,0 +1,52 @@
/*
* MWVSentence.java
* Copyright (C) 2011 Kimmo Tuukkanen
*
* This file is part of Java Marine API.
* <http://sourceforge.net/projects/marineapi/>
*
* Java Marine API is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Java Marine API 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Java Marine API. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sap.sailing.nmeaconnector;
import net.sf.marineapi.nmea.sentence.Sentence;
/**
* This sentence is obsolete over most of its former coverage area.
* The US/Canadian/Russian Loran-C network was shut down in 2010; it is still as of 2015 in limited use in Europe.
* Loran-C operations in Norway will cease from 1st Jan 2016. [NORWAY]
* The format of the sentence is:<pre>
* 12 14
* 1 2 3 4 5 6 7 8 9 10 11| 13|
* | | | | | | | | | | | | | |
* $--GLC,xxxx,x.x,a,x.x,a,x.x,a.x,x,a,x.x,a,x.x,a*hh<CR><LF>
* Field Number:
* GRI Microseconds/10
* Master TOA Microseconds
* Master TOA Signal Status
* Time Difference 1 Microseconds
* Time Difference 1 Signal Status
* Time Difference 2 Microseconds
* Time Difference 2 Signal Status
* Time Difference 3 Microseconds
* Time Difference 3 Signal Status
* Time Difference 4 Microseconds
* Time Difference 4 Signal Status
* Time Difference 5 Microseconds
* Time Difference 5 Signal Status
* Checksum
* </pre>
*/
public interface GLCSentence extends Sentence {
}
@@ -0,0 +1,53 @@
package com.sap.sailing.nmeaconnector;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.tracking.WindListener;
import com.sap.sse.common.TimePoint;
import net.sf.marineapi.nmea.event.SentenceListener;
import net.sf.marineapi.nmea.io.SentenceReader;
import net.sf.marineapi.nmea.sentence.DateSentence;
import net.sf.marineapi.nmea.sentence.MWVSentence;
import net.sf.marineapi.nmea.sentence.SentenceId;
import net.sf.marineapi.nmea.sentence.TimeSentence;
/**
* A stateful receiver that consumes sentences from an NMEA {@link SentenceReader}
* for which it acts as a {@link SentenceListener} for sentences providing information
* about wind measurements, date/time reference, location of measurements and data
* that may be required to convert apparent wind speeds and angles into true wind
* speeds and directions. Clients can {@link #addWindListener(WindListener) register}
* as a {@link WindListener} and receive {@link WindListener#windDataReceived} callbacks
* whenever a {@link Wind} fix was assembled out of the various NMEA sentences.<p>
*
* The NMEA 0183 protocol specifies that a {@link SentenceId#MWV} sentence may provide
* "true" (relative to true north) or "relative" (relative to bow) wind angles. However,
* it is not entirely clear in all cases if those angles have the magnetic declination
* already corrected. Only with {@link SentenceId#MWD} sentences does this become clear,
* and a value for the magnetic declination could be inferred. For now, we will assume
* that {@link SentenceId#MWV} values really provide angles based on true north if
* {@link MWVSentence#isTrue()} is {@code true}.<p>
*
* This receiver assumes that the sentences do not necessarily have to be entirely
* time-synchronous. But different sentence types can provide "hints" as to the timing.
* GPS fixes (@link SentenceId#GGA} and other position fixes {@link SentenceId#GLL} have
* time of day but no date information. Other messages such as {@link SentenceId#RMC} and
* {@link SentenceId#ZDA} contain date and optionally time zone information. When analyzing
* the entire sequence, under certain assumptions such as fixes with time information being
* less than 24h apart from each other and with at least one {@link DateSentence} per day
* it is possible to combine {@link TimeSentence}s with the last date information into
* a full {@link TimePoint}.<p>
*
* In order to also work in streaming mode and to provide {@link Wind} fixes as soon as
* all data relevant for a wind fix has been received, this receiver won't wait for the
* next timing information to interpolate, but instead the last timing data received
* before the wind measurement will be used for the {@link Wind} fix.
*
* @author Axel Uhl (d043530)
*
*/
public interface NMEAWindReceiver {
void addWindListener(WindListener listener);
void removeWindListener(WindListener listener);
}
@@ -0,0 +1,32 @@
package com.sap.sailing.nmeaconnector;
import java.io.InputStream;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.nmeaconnector.impl.NmeaFactoryImpl;
import net.sf.marineapi.nmea.sentence.MWVSentence;
import net.sf.marineapi.nmea.sentence.ZDASentence;
public interface NmeaFactory {
static NmeaFactory INSTANCE = new NmeaFactoryImpl();
NmeaUtil getUtil();
/**
* Reads from a series of NMEA 0183 sentences and looks for timing information in the form of {@link ZDASentence}s
* as well as for weather records ({@link MWVSentence} sentences). If multiple {@code MWV} sentences are found between
* two time stamps, linear interpolation is used to assign time points to each wind reading. The information is
* furthermore combined with position and motion data. If the wind readings are apparent readings, the speed of
* the wind sensor is taken into account to transform into a true reading. NMEA specifies that a "true" reading
* is relative to "true north" which we can only believe; it means that the instrument needs to have an understanding
* of the magnetic declination at the time and position of the measurement.
*
* @param inputStream
* will be consumed up to its end but will not be closed; the caller remains responsible for closing the
* input stream.
* @return a sequence of {@link Wind} objects as extracted from the NMEA 0183 sentences found in the contents of the
* {@code reader}; never {@code null}, but possibly empty
*/
Iterable<Wind> readWind(InputStream inputStream) throws InterruptedException;
}
@@ -0,0 +1,38 @@
package com.sap.sailing.nmeaconnector;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Wind;
import com.sap.sse.common.TimePoint;
import net.sf.marineapi.nmea.parser.SentenceFactory;
import net.sf.marineapi.nmea.sentence.MWVSentence;
import net.sf.marineapi.nmea.util.Units;
public interface NmeaUtil {
Wind getWind(TimePoint timePoint, Position position, MWVSentence mwvSentence);
Speed getSpeed(double magnitude, Units unit);
SpeedWithBearing getSpeedWithBearing(double speedMagnitude, Units speedUnit, double bearingInDegrees);
/**
* Replaces all occurrences of <code>sequenceToFind</code> by <code>replaceWith</code>, as {@link String#replace(CharSequence, CharSequence)},
* but assuming that <code>nmeaSentence</code> has a trailing NMEA checksum, adjusts the checksum accordingly. This will turn a valid
* checksum into a valid checksum again, and an invalid checksum into an invalid checksum.
*/
String replace(String nmeaSentence, String sequenceToFind, String replaceWith);
/**
* Call this to register proprietary parsers provided by this bundle with the
* {@link SentenceFactory} {@link SentenceFactory#getInstance() default instance}.
*/
void registerAdditionalParsers();
/**
* Call this to unregister proprietary parsers provided by this bundle from the
* {@link SentenceFactory} {@link SentenceFactory#getInstance() default instance}.
*/
void unregisterAdditionalParsers();
}
@@ -0,0 +1,7 @@
package com.sap.sailing.nmeaconnector;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sse.common.Timed;
public interface TimedBearing extends Timed, Bearing {
}
@@ -0,0 +1,8 @@
package com.sap.sailing.nmeaconnector;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sse.common.Timed;
public interface TimedSpeedWithBearing extends Timed, SpeedWithBearing {
}
@@ -0,0 +1,62 @@
/*
* GSVParser.java
* Copyright (C) 2010 Kimmo Tuukkanen
*
* This file is part of Java Marine API.
* <http://ktuukkan.github.io/marine-api/>
*
* Java Marine API is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Java Marine API 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Java Marine API. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sap.sailing.nmeaconnector.impl;
import com.sap.sailing.nmeaconnector.AAMSentence;
import net.sf.marineapi.nmea.parser.SentenceParser;
import net.sf.marineapi.nmea.sentence.TalkerId;
/**
* GSV sentence parser.
*
* @author Kimmo Tuukkanen
* @author Axel Uhl
*/
public class AAMParser extends SentenceParser implements AAMSentence {
// field indices, in case someone plans to use them:
// private static final int STATUS_1 = 0;
// private static final int STATUS_2 = 1;
// private static final int ARRIVAL_CIRCLE_RADIUS = 2;
// private static final int UNIT_OF_RADIUS = 3;
// private static final int WAYPOINT_ID = 4;
/**
* Constructor.
*
* @param nmea
* GLC Sentence
*/
public AAMParser(String nmea) {
super(nmea, "AAM");
}
/**
* Creates an GSV parser with empty sentence.
*
* @param talker
* TalkerId to set
*/
public AAMParser(TalkerId talker) {
super(talker, "AAM", 5);
}
}
@@ -0,0 +1,27 @@
package com.sap.sailing.nmeaconnector.impl;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import com.sap.sailing.nmeaconnector.NmeaFactory;
import net.sf.marineapi.nmea.parser.SentenceFactory;
/**
* Registers and unregisters additional proprietary NMEA sentence parsers with the {@link SentenceFactory}
* {@link SentenceFactory#getInstance() default instance}.
*
* @author Axel Uhl (d043530)
*
*/
public class Activator implements BundleActivator {
@Override
public void start(BundleContext context) throws Exception {
NmeaFactory.INSTANCE.getUtil().registerAdditionalParsers();
}
@Override
public void stop(BundleContext context) throws Exception {
NmeaFactory.INSTANCE.getUtil().unregisterAdditionalParsers();
}
}
@@ -0,0 +1,92 @@
/*
* MWVParser.java
* Copyright (C) 2011 Kimmo Tuukkanen
*
* This file is part of Java Marine API.
* <http://sourceforge.net/projects/marineapi/>
*
* Java Marine API is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Java Marine API 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Java Marine API. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sap.sailing.nmeaconnector.impl;
import com.sap.sailing.nmeaconnector.BATSentence;
import net.sf.marineapi.nmea.parser.SentenceParser;
import net.sf.marineapi.nmea.sentence.TalkerId;
/**
* BAT sentence parser. Used for the proprietary NMEA extension of the SailTimer WindVane's battery indicator message.
* The format of the sentence is:<pre>
*
* $WIBAT,a,b*hh
*
* 1) Wind Vane Battery (0= Low, 1= Good)
* 2) Base Unit battery level (0 = Low, 9 = Good)
* 3) Checksum
* </pre>
*
* @author Axel Uhl
*/
public class BATParser extends SentenceParser implements BATSentence {
private static final int WIND_VANE_BATTERY_STATUS = 0;
private static final int BASE_UNIT_BATTERY_LEVEL = 1;
/**
* Creates a new instance of BATParser.
*
* @param nmea BAT sentence String
*/
public BATParser(String nmea) {
super(nmea, /* Proprietary extension for SailTimer WindVane's battery indicator message */ "BAT");
}
/**
* Creates a new empty instance of BATParser.
*
* @param talker Talker id to set
*/
public BATParser(TalkerId talker) {
super(talker, /* Proprietary extension for SailTimer WindVane's battery indicator message */ "BAT", 2);
}
@Override
public WindVaneBatteryStatus getWindVaneBatteryStatus() {
WindVaneBatteryStatus result = null;
final int intValue = getIntValue(WIND_VANE_BATTERY_STATUS);
for (BATSentence.WindVaneBatteryStatus status : BATSentence.WindVaneBatteryStatus.values()) {
if (status.getValue() == intValue) {
result = status;
}
}
return result;
}
@Override
public int getBaseUnitBatteryLevel() {
return getIntValue(BASE_UNIT_BATTERY_LEVEL);
}
@Override
public void setWindVaneBatteryStatus(WindVaneBatteryStatus status) {
setIntValue(WIND_VANE_BATTERY_STATUS, status.getValue());
}
@Override
public void setBaseUnitBatteryLevel(int level) {
if (level < 0 || level > 9) {
throw new IllegalArgumentException("Level must be in the range 0..9 but was "+level);
}
setIntValue(BASE_UNIT_BATTERY_LEVEL, level);
}
}
@@ -0,0 +1,46 @@
/*
* MWVParser.java
* Copyright (C) 2011 Kimmo Tuukkanen
*
* This file is part of Java Marine API.
* <http://sourceforge.net/projects/marineapi/>
*
* Java Marine API is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Java Marine API 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Java Marine API. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sap.sailing.nmeaconnector.impl;
import com.sap.sailing.nmeaconnector.BWCSentence;
import net.sf.marineapi.nmea.parser.SentenceParser;
import net.sf.marineapi.nmea.sentence.TalkerId;
public class BWCParser extends SentenceParser implements BWCSentence {
/**
* Creates a new instance of BWCParser.
*
* @param nmea BWC sentence String
*/
public BWCParser(String nmea) {
super(nmea, "BWC");
}
/**
* Creates a new empty instance of BWCParser.
*
* @param talker Talker id to set
*/
public BWCParser(TalkerId talker) {
super(talker, "BWC", 12);
}
}
@@ -0,0 +1,46 @@
/*
* MWVParser.java
* Copyright (C) 2011 Kimmo Tuukkanen
*
* This file is part of Java Marine API.
* <http://sourceforge.net/projects/marineapi/>
*
* Java Marine API is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Java Marine API 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Java Marine API. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sap.sailing.nmeaconnector.impl;
import com.sap.sailing.nmeaconnector.BWCSentence;
import net.sf.marineapi.nmea.parser.SentenceParser;
import net.sf.marineapi.nmea.sentence.TalkerId;
public class BWRParser extends SentenceParser implements BWCSentence {
/**
* Creates a new instance of BWRParser.
*
* @param nmea BWR sentence String
*/
public BWRParser(String nmea) {
super(nmea, "BWR");
}
/**
* Creates a new empty instance of BWRParser.
*
* @param talker Talker id to set
*/
public BWRParser(TalkerId talker) {
super(talker, "BWR", 12);
}
}
@@ -0,0 +1,20 @@
package com.sap.sailing.nmeaconnector.impl;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.nmeaconnector.TimedBearing;
import com.sap.sse.common.TimePoint;
public class DegreeBearingWithTimepoint extends DegreeBearingImpl implements TimedBearing {
private static final long serialVersionUID = 8704478920647067862L;
private final TimePoint timePoint;
public DegreeBearingWithTimepoint(TimePoint timePoint, double bearingDeg) {
super(bearingDeg);
this.timePoint = timePoint;
}
@Override
public TimePoint getTimePoint() {
return timePoint;
}
}
@@ -0,0 +1,70 @@
/*
* GSVParser.java
* Copyright (C) 2010 Kimmo Tuukkanen
*
* This file is part of Java Marine API.
* <http://ktuukkan.github.io/marine-api/>
*
* Java Marine API is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Java Marine API 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Java Marine API. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sap.sailing.nmeaconnector.impl;
import com.sap.sailing.nmeaconnector.GLCSentence;
import net.sf.marineapi.nmea.parser.SentenceParser;
import net.sf.marineapi.nmea.sentence.TalkerId;
/**
* GSV sentence parser.
*
* @author Kimmo Tuukkanen
* @author Axel Uhl
*/
public class GLCParser extends SentenceParser implements GLCSentence {
// field indices, in case someone plans to use them:
// private static final int GRI_MICROSECONDS_BY_10 = 0;
// private static final int MASTER_TOA_MICROSECONDS = 1;
// private static final int MASTER_TOA_SIGNAL_STATUS = 2;
// private static final int TIME_DIFF_1_MICROSECONDS = 3;
// private static final int TIME_DIFF_1_SIGNAL_STATUS = 4;
// private static final int TIME_DIFF_2_MICROSECONDS = 5;
// private static final int TIME_DIFF_2_SIGNAL_STATUS = 6;
// private static final int TIME_DIFF_3_MICROSECONDS = 7;
// private static final int TIME_DIFF_3_SIGNAL_STATUS = 8;
// private static final int TIME_DIFF_4_MICROSECONDS = 9;
// private static final int TIME_DIFF_4_SIGNAL_STATUS = 10;
// private static final int TIME_DIFF_5_MICROSECONDS = 11;
// private static final int TIME_DIFF_5_SIGNAL_STATUS = 12;
/**
* Constructor.
*
* @param nmea
* GLC Sentence
*/
public GLCParser(String nmea) {
super(nmea, "GLC");
}
/**
* Creates an GSV parser with empty sentence.
*
* @param talker
* TalkerId to set
*/
public GLCParser(TalkerId talker) {
super(talker, "GLC", 19);
}
}
@@ -0,0 +1,33 @@
package com.sap.sailing.nmeaconnector.impl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.nmeaconnector.TimedSpeedWithBearing;
import com.sap.sse.common.TimePoint;
/**
* Can be used as a wind vector in a {@link Track} because it has a {@link TimePoint}.
*
* @author Axel Uhl (d043530)
*
*/
public class KnotSpeedWithBearingAndTimepoint extends KnotSpeedWithBearingImpl implements TimedSpeedWithBearing {
private static final long serialVersionUID = -627441174825774742L;
private final TimePoint timePoint;
public KnotSpeedWithBearingAndTimepoint(TimePoint timePoint, double speedInKnots, Bearing bearing) {
super(speedInKnots, bearing);
this.timePoint = timePoint;
}
@Override
public TimePoint getTimePoint() {
return timePoint;
}
@Override
public String toString() {
return getTimePoint().toString()+": "+super.toString();
}
}
@@ -0,0 +1,407 @@
package com.sap.sailing.nmeaconnector.impl;
import java.io.IOException;
import java.text.ParseException;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.declination.DeclinationService;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.MeterPerSecondSpeedImpl;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableSpeedWithBearing;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.WindListener;
import com.sap.sailing.domain.tracking.impl.DynamicTrackImpl;
import com.sap.sailing.nmeaconnector.NMEAWindReceiver;
import com.sap.sailing.nmeaconnector.TimedBearing;
import com.sap.sailing.nmeaconnector.TimedSpeedWithBearing;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import net.sf.marineapi.nmea.event.AbstractSentenceListener;
import net.sf.marineapi.nmea.io.SentenceReader;
import net.sf.marineapi.nmea.parser.DataNotAvailableException;
import net.sf.marineapi.nmea.sentence.DateSentence;
import net.sf.marineapi.nmea.sentence.HeadingSentence;
import net.sf.marineapi.nmea.sentence.MDASentence;
import net.sf.marineapi.nmea.sentence.MWDSentence;
import net.sf.marineapi.nmea.sentence.MWVSentence;
import net.sf.marineapi.nmea.sentence.PositionSentence;
import net.sf.marineapi.nmea.sentence.SentenceId;
import net.sf.marineapi.nmea.sentence.TimeSentence;
import net.sf.marineapi.nmea.sentence.VTGSentence;
import net.sf.marineapi.nmea.sentence.VWRSentence;
import net.sf.marineapi.nmea.sentence.ZDASentence;
import net.sf.marineapi.nmea.util.CompassPoint;
import net.sf.marineapi.nmea.util.Date;
import net.sf.marineapi.nmea.util.Direction;
import net.sf.marineapi.nmea.util.Time;
import net.sf.marineapi.nmea.util.Units;
public class NMEAWindReceiverImpl implements NMEAWindReceiver {
private static final Logger logger = Logger.getLogger(NMEAWindReceiverImpl.class.getName());
private final ConcurrentHashMap<WindListener, WindListener> listeners;
private final DynamicTrack<TimedSpeedWithBearing> trueWind;
private final DynamicTrack<TimedSpeedWithBearing> apparentWind;
private final DynamicTrack<GPSFix> sensorPositions;
private final DynamicTrack<TimedSpeedWithBearing> sensorSpeeds;
private final DynamicTrack<TimedBearing> trueHeadings;
/**
* Don't use the {@link GregorianCalendar#getTime() time point} of this calendar unless a {@link DateSentence} and a
* {@link TimeSentence} have been received which is indicated by the {@link #dateReceived} and the
* {@link #timeReceived} fields, respectively.
*/
private final GregorianCalendar utcCalendar;
private final DeclinationService declinationService = DeclinationService.INSTANCE;
/**
* Until a {@link DateSentence} has been received, the {@link #utcCalendar}'s {@link GregorianCalendar#getTime()}
* must not be used.
*/
private boolean dateReceived;
private boolean timeReceived;
private class MWVSentenceListener extends AbstractSentenceListener<MWVSentence> {
@Override
public void sentenceRead(MWVSentence sentence) {
final TimePoint timePoint = getLastTimePoint();
if (timePoint != null) {
try {
final Speed speed = createSpeed(sentence.getSpeedUnit(), sentence.getSpeed());
final KnotSpeedWithBearingAndTimepoint fix;
if (sentence.isTrue()) {
final Bearing trueHeading = trueHeadings.getInterpolatedValue(getLastTimePoint(), f->new ScalableBearing(f));
if (trueHeading != null) {
// need to take heading into account as MWV is still relative to bow, even if "true" is provided;
// it is what you would see on the wind display when set to "true wind"
fix = new KnotSpeedWithBearingAndTimepoint(timePoint,
speed.getKnots(), new DegreeBearingImpl(sentence.getAngle()).reverse().add(trueHeading));
trueWind.add(fix);
tryToCreateWindFixFromTrueWind(fix);
}
} else {
fix = new KnotSpeedWithBearingAndTimepoint(timePoint,
speed.getKnots(), new DegreeBearingImpl(sentence.getAngle()).reverse());
apparentWind.add(fix);
tryToCreateWindFixFromApparentWind(fix);
}
// use the newly gained wind data to try to assemble a Wind fix if
// other values such as heading and position are known
} catch (DataNotAvailableException e) {
// can happen... simply ignore this incomplete sentence
}
}
}
}
private class MWDSentenceListener extends AbstractSentenceListener<MWDSentence> {
@Override
public void sentenceRead(MWDSentence sentence) {
// the message provides data about the true wind direction (TWD) and true wind speed (TWS);
// here, "true" means "not apparent"; the direction may still be provided in degrees true
// or degrees magnetic, and we need to find out which is available
final TimePoint timePoint = getLastTimePoint();
if (timePoint != null) {
final com.sap.sailing.domain.common.Position position = getPosition(timePoint);
if (position != null) {
final Speed speed;
if (!Double.isNaN(sentence.getWindSpeed())) {
speed = new MeterPerSecondSpeedImpl(sentence.getWindSpeed());
} else if (!Double.isNaN(sentence.getWindSpeedKnots())) {
speed = new KnotSpeedImpl(sentence.getWindSpeedKnots());
} else {
speed = null;
}
if (speed != null) {
final Bearing direction;
if (!Double.isNaN(sentence.getTrueWindDirection())) {
direction = new DegreeBearingImpl(sentence.getTrueWindDirection());
} else {
Declination declination;
try {
declination = declinationService.getDeclination(timePoint, position, /* timeoutForOnlineFetchInMilliseconds */ 1000);
if (declination != null) {
direction = new DegreeBearingImpl(sentence.getMagneticWindDirection()).add(declination.getBearingCorrectedTo(timePoint));
} else {
direction = null;
}
if (direction != null) {
final KnotSpeedWithBearingAndTimepoint fix = new KnotSpeedWithBearingAndTimepoint(timePoint,
speed.getKnots(), direction);
trueWind.add(fix);
tryToCreateWindFixFromTrueWind(fix);
}
} catch (ParseException | IOException e) {
logger.log(Level.WARNING, "Couldn't correct magnetic TWD reading by declination; ignoring", e);
}
}
}
}
}
}
}
private class VTGSentenceListener extends AbstractSentenceListener<VTGSentence> {
@Override
public void sentenceRead(VTGSentence sentence) {
try {
sensorSpeeds.add(new KnotSpeedWithBearingAndTimepoint(getLastTimePoint(), sentence.getSpeedKnots(), new DegreeBearingImpl(sentence.getTrueCourse())));
} catch (DataNotAvailableException e) {
// no sensor speed will be recorded if data is not available
}
}
}
private class VWRSentenceListener extends AbstractSentenceListener<VWRSentence> {
@Override
public void sentenceRead(VWRSentence sentence) {
apparentWind.add(new KnotSpeedWithBearingAndTimepoint(getLastTimePoint(), sentence.getSpeedKnots(),
new DegreeBearingImpl(sentence.getWindAngle() * (sentence.getDirectionLeftRight()==Direction.LEFT ? -1 : 1))));
}
}
private class MDASentenceListener extends AbstractSentenceListener<MDASentence> {
@Override
public void sentenceRead(MDASentence sentence) {
if (!Double.isNaN(sentence.getTrueWindDirection())) {
if (!Double.isNaN(sentence.getWindSpeed())) {
trueWind.add(new KnotSpeedWithBearingAndTimepoint(getLastTimePoint(), new MeterPerSecondSpeedImpl(sentence.getWindSpeed()).getKnots(),
new DegreeBearingImpl(sentence.getTrueWindDirection())));
} else if (!Double.isNaN(sentence.getWindSpeedKnots())) {
trueWind.add(new KnotSpeedWithBearingAndTimepoint(getLastTimePoint(), sentence.getWindSpeedKnots(),
new DegreeBearingImpl(sentence.getTrueWindDirection())));
}
}
}
}
private class HeadingSentenceListener extends AbstractSentenceListener<HeadingSentence> {
@Override
public void sentenceRead(HeadingSentence sentence) {
if (getLastTimePoint() != null) {
if (sentence.isTrue()) {
try {
trueHeadings.add(new DegreeBearingWithTimepoint(getLastTimePoint(), sentence.getHeading()));
} catch (DataNotAvailableException e) {
// ignore; in this case, no true heading will be recorded
}
} else {
final Position position = getPosition(getLastTimePoint());
if (position != null) {
try {
final Declination declination = declinationService.getDeclination(getLastTimePoint(), position, /* timeoutForOnlineFetchInMilliseconds */ 1000);
if (declination != null) {
try {
trueHeadings.add(new DegreeBearingWithTimepoint(getLastTimePoint(),
new DegreeBearingImpl(sentence.getHeading()).add(declination.getBearingCorrectedTo(getLastTimePoint())).getDegrees()));
} catch (DataNotAvailableException e) {
// no true heading will be created if data is not available
}
}
} catch (IOException | ParseException e) {
logger.log(Level.WARNING, "Error trying to obtain magnetic declination while converting magnetic heading to true heading", e);
}
}
}
}
}
}
private class DateListener extends AbstractSentenceListener<DateSentence> {
@Override
public void sentenceRead(DateSentence sentence) {
try {
final Date date = sentence.getDate();
utcCalendar.set(date.getYear(), date.getMonth() - 1, date.getDay());
dateReceived = true;
} catch (DataNotAvailableException | IllegalArgumentException e) {
// can happen; ignore sentence
}
}
}
private class TimeListener extends AbstractSentenceListener<TimeSentence> {
@Override
public void sentenceRead(TimeSentence sentence) {
try {
final Time time = sentence.getTime();
utcCalendar.set(utcCalendar.get(Calendar.YEAR), utcCalendar.get(Calendar.MONTH), utcCalendar.get(Calendar.DAY_OF_MONTH),
time.getHour(), time.getMinutes(), (int) time.getSeconds());
final int millisInSecond = (int) ((time.getSeconds()-(int) time.getSeconds())*1000);
utcCalendar.set(Calendar.MILLISECOND, millisInSecond);
timeReceived = true;
} catch (DataNotAvailableException e) {
// can happen; ignore sentence
}
}
}
private class PositionListener extends AbstractSentenceListener<PositionSentence> {
@Override
public void sentenceRead(PositionSentence sentence) {
if (sentence instanceof TimeSentence) { // all PositionSentence implementations currently also implement TimeSentence
TimeSentence timeSentence = (TimeSentence) sentence;
// now make sure we work with the latest time from this record:
new TimeListener().sentenceRead(timeSentence);
final TimePoint timePoint = getLastTimePoint();
if (timePoint != null) {
try {
final GPSFix fix = new GPSFixImpl(getPosition(sentence), timePoint);
sensorPositions.add(fix);
} catch (DataNotAvailableException e) {
// no sensor position is recorded in this case
}
}
}
}
private Position getPosition(PositionSentence sentence) throws DataNotAvailableException {
return new DegreePosition(
Math.abs(sentence.getPosition().getLatitude())*(sentence.getPosition().getLatitudeHemisphere()==CompassPoint.NORTH?1:-1),
Math.abs(sentence.getPosition().getLongitude())*(sentence.getPosition().getLongitudeHemisphere()==CompassPoint.EAST?1:-1));
}
}
/**
* @param sentenceReader
* The reader that this receiver will listen to for those NMEA sentences of interest for the construction
* of wind fixes
*/
public NMEAWindReceiverImpl(SentenceReader sentenceReader) {
super();
utcCalendar = new GregorianCalendar();
utcCalendar.setTimeZone(TimeZone.getTimeZone(ZoneId.of("UTC")));
this.listeners = new ConcurrentHashMap<>();
this.trueWind = new DynamicTrackImpl<>("trueWind in "+getClass().getName());
this.apparentWind = new DynamicTrackImpl<>("apparentWind in "+getClass().getName());
this.sensorPositions = new DynamicTrackImpl<>("measurementPositions in "+getClass().getName());
this.sensorSpeeds = new DynamicTrackImpl<>("sensorSpeeds in "+getClass().getName());
this.trueHeadings = new DynamicTrackImpl<>("trueHeadings in "+getClass().getName());
sentenceReader.addSentenceListener(new MWVSentenceListener(), SentenceId.MWV);
sentenceReader.addSentenceListener(new MWDSentenceListener(), SentenceId.MWD);
sentenceReader.addSentenceListener(new VWRSentenceListener(), SentenceId.VWR);
sentenceReader.addSentenceListener(new MDASentenceListener(), SentenceId.MDA);
sentenceReader.addSentenceListener(new VTGSentenceListener(), SentenceId.VTG);
sentenceReader.addSentenceListener(new HeadingSentenceListener());
sentenceReader.addSentenceListener(new DateListener());
sentenceReader.addSentenceListener(new TimeListener());
sentenceReader.addSentenceListener(new PositionListener());
}
private Speed createSpeed(Units speedUnit, double amount) {
final Speed result;
switch (speedUnit) {
case KMH:
result = new KilometersPerHourSpeedImpl(amount);
break;
case KNOT:
result = new KnotSpeedImpl(amount);
break;
case METER:
result = new MeterPerSecondSpeedImpl(amount);
break;
default:
throw new RuntimeException("Unknown speed unit: "+speedUnit);
}
return result;
}
/**
* To be called when new true wind evidence has been received for {@code timePoint}. Creates a {@link Wind} fix when
* enough data is available, in particular position data. If this is the case, the wind fix generated will be passed
* to {@link #notifyListeners(Wind)}.
*/
private void tryToCreateWindFixFromTrueWind(TimedSpeedWithBearing trueWind) {
final com.sap.sailing.domain.common.Position position = getPosition(trueWind.getTimePoint());
if (position != null) {
final Wind wind = new WindImpl(position, trueWind.getTimePoint(), trueWind);
notifyListeners(wind);
}
}
/**
* To be called when new apparent wind evidence has been received for {@code timePoint}. Creates a {@link Wind} fix
* when enough data is available, such as motion and heading data required to turn the apparent wind fix into a true
* wind fix, as well as position data, motion and heading. If this is the case, the wind fix generated will be
* passed to {@link #notifyListeners(Wind)}.
*/
private void tryToCreateWindFixFromApparentWind(TimedSpeedWithBearing apparentWind) {
final com.sap.sailing.domain.common.Position position = getPosition(apparentWind.getTimePoint());
if (position != null) {
final SpeedWithBearing sensorSpeed = sensorSpeeds.getInterpolatedValue(getLastTimePoint(), fix->new ScalableSpeedWithBearing(fix));
if (sensorSpeed != null) {
final Bearing trueHeading = trueHeadings.getInterpolatedValue(getLastTimePoint(), fix->new ScalableBearing(fix));
if (trueHeading != null) {
final SpeedWithBearing apparentWindTrue = new KnotSpeedWithBearingImpl(apparentWind.getKnots(), apparentWind.getBearing().add(trueHeading));
final SpeedWithBearing trueWind = apparentWindTrue.add(sensorSpeed); // this subtracts the induced wind which is the sensorSpeed reversed
final Wind wind = new WindImpl(position, apparentWind.getTimePoint(), trueWind);
notifyListeners(wind);
}
}
}
}
/**
* Based on the sequence of messages received from the reader, a "current" time point is approximated whenever a
* {@link TimeSentence} or {@link DateSentence} or some time zone information as in a {@link ZDASentence} is
* received. Several messages that are of interest for wind fix construction do not carry their own time stamp
* information. For those, the time point inferred from the last {@link TimeSentence} is used which is what this
* method returns.
*
* @return the last {@link TimePoint} inferred from the combination of {@link TimeSentence}, {@link DateSentence}
* and {@link ZDASentence}, or {@code null} in case not enough information about timing has been received
* yet.
*/
private TimePoint getLastTimePoint() {
final TimePoint result;
if (dateReceived && timeReceived) {
result = new MillisecondsTimePoint(utcCalendar.getTime());
} else {
result = null;
}
return result;
}
private com.sap.sailing.domain.common.Position getPosition(TimePoint timePoint) {
return sensorPositions.getInterpolatedValue(timePoint, gpsFix->new ScalablePosition(gpsFix.getPosition()));
}
@Override
public void addWindListener(WindListener listener) {
listeners.put(listener, listener);
}
@Override
public void removeWindListener(WindListener listener) {
listeners.remove(listener);
}
private void notifyListeners(Wind wind) {
for (WindListener listener : listeners.values()) {
listener.windDataReceived(wind);
}
}
}
@@ -0,0 +1,52 @@
package com.sap.sailing.nmeaconnector.impl;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.tracking.WindListener;
import com.sap.sailing.nmeaconnector.NMEAWindReceiver;
import com.sap.sailing.nmeaconnector.NmeaFactory;
import com.sap.sailing.nmeaconnector.NmeaUtil;
import net.sf.marineapi.nmea.event.AbstractSentenceListener;
import net.sf.marineapi.nmea.io.SentenceReader;
import net.sf.marineapi.nmea.sentence.Sentence;
public class NmeaFactoryImpl implements NmeaFactory {
@Override
public NmeaUtil getUtil() {
return new NmeaUtilImpl();
}
@Override
public Iterable<Wind> readWind(InputStream inputStream) throws InterruptedException {
final CountDownLatch finishedLatch = new CountDownLatch(1);
final List<Wind> result = new ArrayList<>();
final SentenceReader sentenceReader = new SentenceReader(inputStream);
sentenceReader.addSentenceListener(new AbstractSentenceListener<Sentence>() {
@Override
public void readingStopped() {
finishedLatch.countDown();
}
@Override
public void sentenceRead(Sentence sentence) {}
});
final NMEAWindReceiver receiver = new NMEAWindReceiverImpl(sentenceReader);
receiver.addWindListener(new WindListener() {
@Override
public void windDataReceived(Wind wind) {
result.add(wind);
}
@Override public void windDataRemoved(Wind wind) {}
@Override public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {}
});
sentenceReader.start();
finishedLatch.await();
return result;
}
}
@@ -0,0 +1,118 @@
package com.sap.sailing.nmeaconnector.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.sap.sailing.domain.base.impl.KilometersPerHourSpeedWithBearingImpl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.nmeaconnector.NmeaUtil;
import com.sap.sse.common.TimePoint;
import net.sf.marineapi.nmea.parser.SentenceFactory;
import net.sf.marineapi.nmea.parser.SentenceParser;
import net.sf.marineapi.nmea.sentence.MWVSentence;
import net.sf.marineapi.nmea.util.Units;
public class NmeaUtilImpl implements NmeaUtil {
private static Map<String, Class<? extends SentenceParser>> proprietaryParsers;
static {
proprietaryParsers = new HashMap<>();
proprietaryParsers.put("BAT", BATParser.class);
proprietaryParsers.put("AAM", AAMParser.class);
proprietaryParsers.put("GLC", GLCParser.class);
proprietaryParsers.put("BWC", BWCParser.class);
proprietaryParsers.put("BWR", BWRParser.class);
}
@Override
public void registerAdditionalParsers() {
SentenceFactory sentenceFactory = SentenceFactory.getInstance();
for (Entry<String, Class<? extends SentenceParser>> e : proprietaryParsers.entrySet()) {
if (!sentenceFactory.hasParser(e.getKey())) {
sentenceFactory.registerParser(e.getKey(), e.getValue());
}
}
}
@Override
public void unregisterAdditionalParsers() {
SentenceFactory sentenceFactory = SentenceFactory.getInstance();
for (Entry<String, Class<? extends SentenceParser>> e : proprietaryParsers.entrySet()) {
if (!sentenceFactory.hasParser(e.getKey())) {
sentenceFactory.unregisterParser(e.getValue());
}
}
}
@Override
public Wind getWind(TimePoint timePoint, Position position, MWVSentence mwvSentence) {
return new WindImpl(position, timePoint, new KnotSpeedWithBearingImpl(mwvSentence.getSpeed(),
new DegreeBearingImpl(mwvSentence.getAngle())));
}
@Override
public Speed getSpeed(double magnitude, Units unit) {
switch (unit) {
case KMH:
return new KilometersPerHourSpeedImpl(magnitude);
case KNOT:
return new KnotSpeedImpl(magnitude);
case METER:
return new KilometersPerHourSpeedImpl(magnitude * 3.6);
default:
throw new IllegalArgumentException("Unit "+unit+" not understood for a speed");
}
}
@Override
public SpeedWithBearing getSpeedWithBearing(double speedMagnitude, Units speedUnit, double bearingInDegrees) {
Bearing bearing = new DegreeBearingImpl(bearingInDegrees);
switch (speedUnit) {
case KMH:
return new KilometersPerHourSpeedWithBearingImpl(speedMagnitude, bearing);
case KNOT:
return new KnotSpeedWithBearingImpl(speedMagnitude, bearing);
case METER:
return new KilometersPerHourSpeedWithBearingImpl(speedMagnitude * 3.6, bearing);
default:
throw new IllegalArgumentException("Unit "+speedUnit+" not understood for a speed");
}
}
@Override
public String replace(String nmeaSentence, String regexToFind, String replaceWith) {
while (nmeaSentence.matches(".*" + regexToFind + ".*")) {
final String resultWithOldChecksum = nmeaSentence.replaceFirst(regexToFind, replaceWith);
if (resultWithOldChecksum.matches(".*\\*[0-9a-fA-F][0-9a-fA-F]$")) {
// found checksum
int checksum = Integer.valueOf(resultWithOldChecksum.substring(resultWithOldChecksum.length() - 2), 16);
for (char oldChar : regexToFind.toCharArray()) {
checksum ^= (byte) oldChar;
}
for (char newChar : replaceWith.toCharArray()) {
checksum ^= (byte) newChar;
}
String newHexChecksum = Integer.toHexString(checksum).toUpperCase();
if (newHexChecksum.length() == 1) {
newHexChecksum = "0" + newHexChecksum;
}
nmeaSentence = resultWithOldChecksum.substring(0, resultWithOldChecksum.length() - 2) + newHexChecksum;
} else {
nmeaSentence = resultWithOldChecksum;
}
}
return nmeaSentence;
}
}
@@ -2,15 +2,32 @@ package com.sap.sailing.server.gateway.deserialization.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.SharedDomainFactory;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.statistics.Statistics;
import com.sap.sailing.domain.statistics.impl.StatisticsImpl;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.serialization.impl.StatisticsJsonSerializer;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class StatisticsJsonDeserializer implements JsonDeserializer<Statistics> {
private final CompetitorJsonDeserializer competitorJsonDeserializer;
public static StatisticsJsonDeserializer create(SharedDomainFactory baseDomainFactory) {
return new StatisticsJsonDeserializer(CompetitorJsonDeserializer.create(baseDomainFactory));
}
private StatisticsJsonDeserializer(CompetitorJsonDeserializer competitorJsonDeserializer) {
this.competitorJsonDeserializer = competitorJsonDeserializer;
}
@Override
public Statistics deserialize(JSONObject object) throws JsonDeserializationException {
@@ -21,7 +38,17 @@ public class StatisticsJsonDeserializer implements JsonDeserializer<Statistics>
long gpsFixes = getLongValue(object, StatisticsJsonSerializer.FIELD_NUMBER_OF_GPS_FIXES);
long windFixes = getLongValue(object, StatisticsJsonSerializer.FIELD_NUMBER_OF_WIND_FIXES);
Distance distance = new MeterDistance(getDoubleValue(object, StatisticsJsonSerializer.FIELD_DISTANCE_TRAVELED_IN_METERS));
return new StatisticsImpl(competitors, regattas, races, trackedRaces, gpsFixes, windFixes, distance);
JSONObject maxSpeedObject = (JSONObject) object.get(StatisticsJsonSerializer.FIELD_MAX_SPEED);
final Triple<Competitor, Speed, TimePoint> maxSpeed;
if (maxSpeedObject != null) {
final Competitor fastestCompetitor = competitorJsonDeserializer.deserialize((JSONObject) maxSpeedObject.get(StatisticsJsonSerializer.FIELD_FASTEST_COMPETITOR));
final Speed fastestCompetitorSpeed = new KnotSpeedImpl(getDoubleValue(maxSpeedObject, StatisticsJsonSerializer.FIELD_FASTEST_COMPETITOR_SPEED_IN_KNOTS));
final TimePoint timePoint = new MillisecondsTimePoint(getLongValue(maxSpeedObject, StatisticsJsonSerializer.FIELD_TIMEPOINT_MILLIS));
maxSpeed = new Triple<Competitor, Speed, TimePoint>(fastestCompetitor, fastestCompetitorSpeed, timePoint);
} else {
maxSpeed = null;
}
return new StatisticsImpl(competitors, regattas, races, trackedRaces, gpsFixes, windFixes, distance, maxSpeed);
}
private int getIntValue(JSONObject object, String field) {
@@ -2,8 +2,12 @@ package com.sap.sailing.server.gateway.serialization.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.statistics.Statistics;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Triple;
public class StatisticsJsonSerializer implements JsonSerializer<Statistics> {
@@ -14,6 +18,12 @@ public class StatisticsJsonSerializer implements JsonSerializer<Statistics> {
public static final String FIELD_NUMBER_OF_GPS_FIXES = "numberOfGPSFixes";
public static final String FIELD_NUMBER_OF_WIND_FIXES = "numberOfWindFixes";
public static final String FIELD_DISTANCE_TRAVELED_IN_METERS = "distanceTraveledInMeters";
public static final String FIELD_MAX_SPEED = "maxSpeed";
public static final String FIELD_FASTEST_COMPETITOR = "fastestCompetitor";
public static final String FIELD_FASTEST_COMPETITOR_SPEED_IN_KNOTS = "fastestCompetitorSpeedInKnots";
public static final String FIELD_TIMEPOINT_MILLIS = "timepointMillis";
private final CompetitorJsonSerializer competitorJsonSerializer = CompetitorJsonSerializer.create();
@Override
public JSONObject serialize(Statistics object) {
@@ -25,6 +35,16 @@ public class StatisticsJsonSerializer implements JsonSerializer<Statistics> {
result.put(FIELD_NUMBER_OF_GPS_FIXES, object.getNumberOfGPSFixes());
result.put(FIELD_NUMBER_OF_WIND_FIXES, object.getNumberOfWindFixes());
result.put(FIELD_DISTANCE_TRAVELED_IN_METERS, object.getDistanceTraveled().getMeters());
final Triple<Competitor, Speed, TimePoint> maxSpeed = object.getMaxSpeed();
if (maxSpeed != null) {
JSONObject maxSpeedObject = new JSONObject();
maxSpeedObject.put(FIELD_FASTEST_COMPETITOR, competitorJsonSerializer.serialize(maxSpeed.getA()));
maxSpeedObject.put(FIELD_FASTEST_COMPETITOR_SPEED_IN_KNOTS, maxSpeed.getB().getKnots());
maxSpeedObject.put(FIELD_TIMEPOINT_MILLIS, maxSpeed.getC().asMillis());
result.put(FIELD_MAX_SPEED, maxSpeedObject);
}
return result;
}
}
@@ -59,7 +59,8 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sse.datamining.shared;bundle-version="1.0.0",
com.sap.sailing.datamining,
com.sap.sailing.server,
com.sap.sailing.grib
com.sap.sailing.grib,
com.sap.sailing.nmeaconnector
Bundle-ClassPath: .
Web-ContextPath: /sailingserver
Export-Package: com.sap.sailing.server.gateway,
@@ -131,7 +131,16 @@
<url-pattern>/grib-wind-import</url-pattern>
</servlet-mapping>
<servlet>
<servlet>
<servlet-name>NmeaWindImportServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.windimport.nmea.NmeaWindImportServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>NmeaWindImportServlet</servlet-name>
<url-pattern>/nmea-wind-import</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>ExpeditionWindMeasureStatusGetServlet</servlet-name>
<servlet-class>com.sap.sailing.server.gateway.impl.ExpeditionWindMeasureStatusGetServlet</servlet-class>
</servlet>
@@ -198,7 +198,7 @@ public abstract class AbstractWindImportServlet extends SailingServerHttpServlet
response.getWriter().append(windImportResult.json().toJSONString());
}
protected abstract Iterable<Wind> importWind(Map<InputStream, String> streamsWithFilenames) throws IOException;
protected abstract Iterable<Wind> importWind(Map<InputStream, String> streamsWithFilenames) throws IOException, InterruptedException;
protected abstract WindSource getWindSource(UploadRequest uploadRequest);

Some files were not shown because too many files have changed in this diff Show More