added a few NMEA utilities

This commit is contained in:
Axel Uhl
2013-01-03 17:25:40 +01:00
parent 8164f03170
commit 8e758deea5
6 changed files with 190 additions and 11 deletions
@@ -15,13 +15,16 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import net.sf.marineapi.nmea.event.SentenceEvent;
import net.sf.marineapi.nmea.event.SentenceListener;
import net.sf.marineapi.nmea.io.SentenceReader;
import net.sf.marineapi.nmea.sentence.MWVSentence;
import net.sf.marineapi.nmea.sentence.Sentence;
import net.sf.marineapi.nmea.sentence.SentenceId;
import org.junit.Test;
@@ -35,12 +38,18 @@ import slash.navigation.gpx.GpxRoute;
import com.sap.sailing.domain.base.impl.KilometersPerHourSpeedWithBearingImpl;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.tracking.impl.WindTrackImpl;
import com.sap.sailing.nmeaconnector.NmeaFactory;
import com.sap.sailing.nmeaconnector.NmeaUtil;
public class ReadWindVaneDataTest {
@Test
@@ -52,10 +61,17 @@ public class ReadWindVaneDataTest {
@Test
public void readWindVaneOutputWithInterlacedTimeStamps() throws IOException, InterruptedException {
DateFormat df = new SimpleDateFormat("EE, dd. MMM yyyy HH:mm:ss", Locale.GERMANY);
TimePoint lastTimestampFromFile = null;
final List<Sentence> sentences = new ArrayList<>();
final List<TimePoint> timePointsForSentences = new ArrayList<>();
readWindVaneNmeaFile(sentences, timePointsForSentences);
assertEquals(610, sentences.size()); // we have 684 sentences altogether; 74 of those are WIBAT which are not understood
}
private void readWindVaneNmeaFile(final List<Sentence> sentences, final List<TimePoint> timePointsForSentences)
throws IOException, InterruptedException {
NmeaUtil nmeaUtil = NmeaFactory.INSTANCE.getUtil();
DateFormat df = new SimpleDateFormat("EE, dd. MMM yyyy HH:mm:ss", Locale.GERMANY);
TimePoint lastTimestampFromFile = null;
InputStream is = getClass().getResourceAsStream("/windvane.nmea");
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream(pis);
@@ -79,6 +95,8 @@ public class ReadWindVaneDataTest {
while ((line=br.readLine()) != null) {
if (line.startsWith("$") || line.startsWith("!")) {
// NMEA0183 sentence
// Special hack for a bug with the WindVane's speed unit output which erroneously uses "K" for "knots":
line = nmeaUtil.replace(line, ",K,", ",N,");
if (lastTimestampFromFile != null) {
lastTimestampFromFile = lastTimestampFromFile.plus(1000);
timePointsForSentences.add(lastTimestampFromFile);
@@ -104,11 +122,48 @@ public class ReadWindVaneDataTest {
br.close();
Thread.sleep(3000);
nmeaReader.stop();
assertEquals(610, sentences.size()); // we have 684 sentences altogether; 74 of those are WIBAT which are not understood
}
@Test
public void readGpxFile() throws IOException {
DynamicGPSFixMovingTrackImpl<Object> track = new DynamicGPSFixMovingTrackImpl<Object>("Wind Vane", /* millisecondsOverWhichToAverage */ 5000);
loadGpxFileIntoTrack(track);
track.lockForRead();
try {
assertEquals(489, track.getRawFixes().size());
} finally {
track.unlockAfterRead();
}
}
@Test
public void readWindVaneNmeaIntoWindTrack() throws IOException, InterruptedException {
NmeaUtil nmeaUtil = NmeaFactory.INSTANCE.getUtil();
final List<Sentence> sentences = new ArrayList<>();
DynamicGPSFixMovingTrackImpl<Object> gpsTrack = new DynamicGPSFixMovingTrackImpl<Object>("Wind Vane", /* millisecondsOverWhichToAverage */ 5000);
loadGpxFileIntoTrack(gpsTrack);
final List<TimePoint> timePointsForSentences = new ArrayList<>();
WindTrack windTrack = new WindTrackImpl(/* millisecondsOverWhichToAverage */ 5000, /* useSpeed */ true,
/* nameForReadWriteLock */ "readWindVaneNmeaIntoWindTrack");
readWindVaneNmeaFile(sentences, timePointsForSentences);
Iterator<TimePoint> timePointIter = timePointsForSentences.iterator();
for (Sentence sentence : sentences) {
TimePoint timePoint = timePointIter.next();
Position position = gpsTrack.getEstimatedPosition(timePoint, /* extrapolate */ true);
if (SentenceId.MWV == SentenceId.valueOf(sentence.getSentenceId())) {
MWVSentence mwvSentence = (MWVSentence) sentence;
windTrack.add(nmeaUtil.getWind(timePoint, position, mwvSentence));
}
}
windTrack.lockForRead();
try {
assertEquals(610, Util.size(windTrack.getRawFixes()));
} finally {
windTrack.unlockAfterRead();
}
}
private void loadGpxFileIntoTrack(DynamicGPSFixMovingTrackImpl<Object> track) throws IOException {
ParserResult read = new NavigationFormatParser().read(getClass().getResource("/20121219114023.gpx"),
Arrays.asList(new NavigationFormat[] { new Gpx10Format() }));
@SuppressWarnings("rawtypes")
@@ -119,18 +174,11 @@ public class ReadWindVaneDataTest {
GpxRoute route = (GpxRoute) routes.get(0);
List<GpxPosition> gpsPositions = route.getPositions();
assertNotNull(gpsPositions);
DynamicGPSFixMovingTrackImpl<Object> track = new DynamicGPSFixMovingTrackImpl<Object>("Wind Vane", /* millisecondsOverWhichToAverage */ 5000);
for (GpxPosition gpxPosition : gpsPositions) {
GPSFixMoving gpsFixMoving = new GPSFixMovingImpl(new DegreePosition(gpxPosition.getLatitude(), gpxPosition.getLongitude()),
new MillisecondsTimePoint(gpxPosition.getTime().getTimeInMillis()), new KilometersPerHourSpeedWithBearingImpl(
gpxPosition.getSpeed(), new DegreeBearingImpl(gpxPosition.getHeading())));
track.addGPSFix(gpsFixMoving);
}
track.lockForRead();
try {
assertEquals(489, track.getRawFixes().size());
} finally {
track.unlockAfterRead();
}
}
}
@@ -7,10 +7,13 @@ Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Bundle-ClassPath: .,
lib/marineapi.jar
Export-Package: net.sf.marineapi.nmea.event,
Export-Package: com.sap.sailing.nmeaconnector,
net.sf.marineapi.nmea.event,
net.sf.marineapi.nmea.io,
net.sf.marineapi.nmea.parser,
net.sf.marineapi.nmea.sentence,
net.sf.marineapi.nmea.util,
net.sf.marineapi.provider,
net.sf.marineapi.provider.event
Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.domain.common
@@ -0,0 +1,9 @@
package com.sap.sailing.nmeaconnector;
import com.sap.sailing.nmeaconnector.impl.NmeaFactoryImpl;
public interface NmeaFactory {
static NmeaFactory INSTANCE = new NmeaFactoryImpl();
NmeaUtil getUtil();
}
@@ -0,0 +1,25 @@
package com.sap.sailing.nmeaconnector;
import net.sf.marineapi.nmea.sentence.MWVSentence;
import net.sf.marineapi.nmea.util.Units;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.tracking.Wind;
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);
}
@@ -0,0 +1,13 @@
package com.sap.sailing.nmeaconnector.impl;
import com.sap.sailing.nmeaconnector.NmeaFactory;
import com.sap.sailing.nmeaconnector.NmeaUtil;
public class NmeaFactoryImpl implements NmeaFactory {
@Override
public NmeaUtil getUtil() {
return new NmeaUtilImpl();
}
}
@@ -0,0 +1,81 @@
package com.sap.sailing.nmeaconnector.impl;
import net.sf.marineapi.nmea.sentence.MWVSentence;
import net.sf.marineapi.nmea.util.Units;
import com.sap.sailing.domain.base.SpeedWithBearing;
import com.sap.sailing.domain.base.impl.KilometersPerHourSpeedWithBearingImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
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.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.nmeaconnector.NmeaUtil;
public class NmeaUtilImpl implements NmeaUtil {
@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 sequenceToFind, String replaceWith) {
final String result;
final String resultWithOldChecksum = nmeaSentence.replace(sequenceToFind, 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 : sequenceToFind.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;
}
result = resultWithOldChecksum.substring(0, resultWithOldChecksum.length()-2)+newHexChecksum;
} else {
result = resultWithOldChecksum;
}
return result;
}
}