From ff262a62272050cee68d0897212078dddfcdcae9 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 14:57:51 +1200 Subject: [PATCH] Added Knots <-> MMperSec conversions to AC35UnitConverter. Removed redundant/unused conversions. Documented all of the conversions, and renamed them to pack/unpackX, to match the API spec. Updated/added some tests in AC35UnitConverterTest. RaceStatus now contains a Bearing instead of a packed int bearing. RaceStatus now contains wind speed in knots, instead of MMperSec packed. This means that only RaceStatus decoder/encoder need to care about the bits-over-wire packed values. issue #35 #36 #story[1095] --- .../src/main/java/mock/model/RaceServer.java | 7 +- .../MessageDecoders/RaceStatusDecoder.java | 10 ++- .../MessageEncoders/RaceStatusEncoder.java | 9 +- .../java/network/Messages/BoatLocation.java | 16 ++-- .../java/network/Messages/RaceStatus.java | 32 +++---- .../java/network/Utils/AC35UnitConverter.java | 79 +++++++++++++---- .../java/visualiser/model/VisualiserRace.java | 4 +- .../RaceStatusDecoderTest.java | 11 +-- .../network/Utils/AC35UnitConverterTest.java | 84 +++++++++++++++---- 9 files changed, 173 insertions(+), 79 deletions(-) diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index b68ada32..8629056d 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -133,9 +133,6 @@ public class RaceServer { } - //Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class. - int windDirectionInt = AC35UnitConverter.encodeHeading(race.getWindDirection().degrees()); - int windSpeedInt = (int) (race.getWindSpeed() * Constants.KnotsToMMPerSecond); //Create race status object, and send it. RaceStatus raceStatus = new RaceStatus( @@ -144,8 +141,8 @@ public class RaceServer { race.getRaceId(), race.getRaceStatusEnum(), race.getRaceClock().getStartingTimeMilli(), - windDirectionInt, - windSpeedInt, + race.getWindDirection(), + race.getWindSpeed(), race.getRaceType(), boatStatuses); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java index 4b67daa5..307c2815 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java @@ -5,6 +5,8 @@ import network.Messages.BoatStatus; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; import network.Messages.RaceStatus; +import network.Utils.AC35UnitConverter; +import shared.model.Bearing; import java.util.ArrayList; import java.util.Arrays; @@ -65,10 +67,12 @@ public class RaceStatusDecoder { long expectedStart = bytesToLong(expectedStartBytes); byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20); - int windDirection = bytesToInt(windDirectionBytes); + int windDirectionInt = bytesToInt(windDirectionBytes); + Bearing windDirection = Bearing.fromDegrees(AC35UnitConverter.unpackHeading(windDirectionInt)); byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22); - short windSpeed = bytesToShort(windSpeedBytes); + int windSpeedInt = bytesToInt(windSpeedBytes); + double windSpeedKnots = AC35UnitConverter.unpackMMperSecToKnots(windSpeedInt); byte[] numberOfBoatsBytes = Arrays.copyOfRange(encodedMessage, 22, 23); int numberOfBoats = bytesToInt(numberOfBoatsBytes); @@ -99,7 +103,7 @@ public class RaceStatusDecoder { raceStatus, expectedStart, windDirection, - windSpeed, + windSpeedKnots, raceType, boatStatuses ); } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java index 7a0be153..fcb06d82 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java @@ -4,10 +4,13 @@ package network.MessageEncoders; import network.Messages.AC35Data; import network.Messages.BoatStatus; import network.Messages.RaceStatus; +import network.Utils.AC35UnitConverter; +import shared.model.Bearing; import java.nio.ByteBuffer; import java.util.List; +import static network.Utils.ByteConverter.bytesToInt; import static network.Utils.ByteConverter.intToBytes; import static network.Utils.ByteConverter.longToBytes; @@ -52,10 +55,12 @@ public class RaceStatusEncoder implements MessageEncoder { byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6); //North = 0x0000 East = 0x4000 South = 0x8000. - byte[] raceWind = intToBytes(raceStatus.getWindDirection(), 2); + int windDirectionInt = AC35UnitConverter.packHeading(raceStatus.getWindDirection().degrees()); + byte[] raceWind = intToBytes(windDirectionInt, 2); //mm/sec - byte[] windSpeed = intToBytes(raceStatus.getWindSpeed(), 2); + int windSpeedInt = AC35UnitConverter.packKnotsToMMperSec(raceStatus.getWindSpeed()); + byte[] windSpeed = intToBytes(windSpeedInt, 2); byte[] numBoats = intToBytes(boatStatuses.size(), 1); diff --git a/racevisionGame/src/main/java/network/Messages/BoatLocation.java b/racevisionGame/src/main/java/network/Messages/BoatLocation.java index 1f2bfc49..e870a802 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatLocation.java +++ b/racevisionGame/src/main/java/network/Messages/BoatLocation.java @@ -5,8 +5,8 @@ import network.Messages.Enums.MessageType; import network.Utils.AC35UnitConverter; import shared.model.Constants; -import static network.Utils.AC35UnitConverter.convertGPS; -import static network.Utils.AC35UnitConverter.convertGPSToInt; +import static network.Utils.AC35UnitConverter.unpackGPS; +import static network.Utils.AC35UnitConverter.packGPS; /** * Represents the information in a boat location message (AC streaming spec: 4.9). @@ -159,8 +159,8 @@ public class BoatLocation extends AC35Data { this.sourceID = sourceID; this.sequenceNumber = sequenceNumber; this.deviceType = 1; - this.latitude = convertGPSToInt(lat); - this.longitude = convertGPSToInt(lon); + this.latitude = packGPS(lat); + this.longitude = packGPS(lon); this.altitude = 0; this.heading = convertHeadingDoubleToInt(heading); this.pitch = 0; @@ -340,11 +340,11 @@ public class BoatLocation extends AC35Data { } public double getLatitudeDouble(){ - return convertGPS(this.latitude); + return unpackGPS(this.latitude); } public double getLongitudeDouble(){ - return convertGPS(this.longitude); + return unpackGPS(this.longitude); } public void setLongitude(int longitude) { @@ -474,11 +474,11 @@ public class BoatLocation extends AC35Data { } public double getHeadingDegrees(){ - return AC35UnitConverter.convertHeading(getHeading()); + return AC35UnitConverter.unpackHeading(getHeading()); } public double getTrueWindAngleDegrees(){ - return AC35UnitConverter.convertTrueWindAngle(getTrueWindAngle()); + return AC35UnitConverter.unpackTrueWindAngle(getTrueWindAngle()); } @Override diff --git a/racevisionGame/src/main/java/network/Messages/RaceStatus.java b/racevisionGame/src/main/java/network/Messages/RaceStatus.java index 4d98700d..574cfce4 100644 --- a/racevisionGame/src/main/java/network/Messages/RaceStatus.java +++ b/racevisionGame/src/main/java/network/Messages/RaceStatus.java @@ -5,6 +5,7 @@ import network.Messages.Enums.MessageType; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; import network.Utils.AC35UnitConverter; +import shared.model.Bearing; import shared.model.Constants; import java.util.List; @@ -51,12 +52,13 @@ public class RaceStatus extends AC35Data { /** * The wind direction of the course. */ - private int windDirection; + private Bearing windDirection; /** * The wind speed of the course. + * Knots. */ - private int windSpeed; + private double windSpeed; /** * The type of race this is. @@ -78,11 +80,12 @@ public class RaceStatus extends AC35Data { * @param raceStatus The status of the race. * @param expectedStartTime The expected start time of the race. * @param windDirection The current wind direction in the race. - * @param windSpeed The current wind speed in the race. + * @param windSpeed The current wind speed in the race, in knots. * @param raceType The type of race this is. * @param boatStatuses A list of BoatStatuses. One for each boat. */ - public RaceStatus(byte messageVersionNumber, long currentTime, int raceID, RaceStatusEnum raceStatus, long expectedStartTime, int windDirection, int windSpeed, RaceTypeEnum raceType, List boatStatuses) { + public RaceStatus(byte messageVersionNumber, long currentTime, int raceID, RaceStatusEnum raceStatus, long expectedStartTime, Bearing windDirection, double windSpeed, RaceTypeEnum raceType, List boatStatuses) { + super(MessageType.RACESTATUS); this.messageVersionNumber = messageVersionNumber; this.currentTime = currentTime; @@ -96,6 +99,7 @@ public class RaceStatus extends AC35Data { } + /** * Returns the version number of this message. * @return The version number of the message. @@ -144,16 +148,16 @@ public class RaceStatus extends AC35Data { * Returns the current direction of the wind in the race. * @return Current wind direction. */ - public int getWindDirection() + public Bearing getWindDirection() { return windDirection; } /** - * Returns the wind speed for this race status, in millimeters per second. - * @return Wind speed in millimeters per second. + * Returns the wind speed for this race status, in knots. + * @return Wind speed in knots. */ - public int getWindSpeed() + public double getWindSpeed() { return windSpeed; } @@ -221,16 +225,4 @@ public class RaceStatus extends AC35Data { return raceStatus == RaceStatusEnum.PRESTART; } - - public double getScaledWindDirection() { - return AC35UnitConverter.convertHeading(windDirection); - } - - /** - * Returns the wind speed for this race status, in knots. - * @return Wind speed in knots. - */ - public double getWindSpeedKnots() { - return (windSpeed / Constants.KnotsToMMPerSecond); - } } diff --git a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java index 73f6d0e9..4838e42c 100644 --- a/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java +++ b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java @@ -1,43 +1,88 @@ package network.Utils; +import shared.model.Constants; + /** - * Created by fwy13 on 28/04/17. + * Contains various unit conversion for encoding/decoding messages. + * Our program uses the "unpacked" units, and the over-the-wire format uses "packed" units (e.g., degrees stored as ints). */ public class AC35UnitConverter { - public static double convertGPS(int value){ - //converts latitude or longitue to angle + + /** + * Converts a packed GPSCoordinate (latitude or longitude) into the unpacked unit. + * @param value Packed lat/long value. + * @return Unpacked lat/long angle, in degrees. + */ + public static double unpackGPS(int value) { return (double) value * 180.0 / 2147483648.0;//2^31 = 2147483648 } - public static int convertGPSToInt(double value){ - //converts latitude or longitue to angle + /** + * Converts a latitude or longitude angle into a packed unit. + * @param value The lat/long angle, in degrees, to convert. + * @return The packed value. + */ + public static int packGPS(double value) { return (int) (value * 2147483648.0/180.0);//2^31 = 2147483648 } - public static double convertHeading(long value){ - return (double) value * 360.0/65536.0;//2^15 + + /** + * Unpacks a heading from an int to an angle in degrees (this is a bearing). + * @param value The packed value to unpack. + * @return The unpacked value in degrees. + */ + public static double unpackHeading(int value) { + return (value * 360.0 / 65536.0);//2^15 } - public static double convertHeading(int value){ - return (double) value * 360.0/65536.0;//2^15 + /** + * Packs a heading (this is a bearing), in degrees, to a packed int value. + * @param value The heading in degrees. + * @return The packed value. + */ + public static int packHeading(double value) { + return (int) (value / 360.0 * 65536.0);//2^15 } - public static double convertHeading(double value){ - return value * 360.0/65536.0;//2^15 + /** + * Unpacks a true wind angle from a short to an angle in degrees (this is an azimuth). + * @param value The packed value to unpack. + * @return The unpacked value in degrees. + */ + public static double unpackTrueWindAngle(short value) { + return (value * 180.0 / 32768.0);//-2^15 to 2^15 } - public static int encodeHeading(int value){ - return (int) (value / 360.0 * 65536.0);//2^15 + /** + * Packs a true wind angle (this is an azimuth) from an angle in degrees to a packed short value. + * @param value The unpacked value in degrees. + * @return The packed value. + */ + public static short packTrueWindAngle(double value) { + return (short) (value / 180.0 * 32768.0);//-2^15 to 2^15 } - public static int encodeHeading(double value){ - return (int) (value / 360.0 * 65536.0);//2^15 + + /** + * Unpacks a speed, in millimeters per second, to a double, in knots. + * @param millimetersPerSec Speed in millimeters per second. + * @return Speed in knots. + */ + public static double unpackMMperSecToKnots(int millimetersPerSec) { + return (millimetersPerSec / Constants.KnotsToMMPerSecond); } - public static double convertTrueWindAngle(long value){ - return (double) value * 180.0/32768.0;//-2^15 to 2^15 + /** + * Packs a speed, in knots, into an int, in millimeters per second. + * @param speedKnots Speed in knots. + * @return Speed in millimeters per second. + */ + public static int packKnotsToMMperSec(double speedKnots) { + return (int) (speedKnots * Constants.KnotsToMMPerSecond); } + } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index fdc2802d..faa47688 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -313,8 +313,8 @@ public class VisualiserRace extends Race implements Runnable { //Wind. this.setWind( - Bearing.fromDegrees(raceStatus.getScaledWindDirection()), - raceStatus.getWindSpeedKnots() ); + raceStatus.getWindDirection(), + raceStatus.getWindSpeed() ); //Current race time. this.raceClock.setUTCTime(raceStatus.getCurrentTime()); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java index 1adb69b4..4f1e2721 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java @@ -9,6 +9,7 @@ import network.Messages.Enums.RaceTypeEnum; import network.Messages.RaceStatus; import org.junit.Assert; import org.junit.Test; +import shared.model.Bearing; import java.util.ArrayList; import java.util.Iterator; @@ -67,8 +68,8 @@ public class RaceStatusDecoderTest { int raceID = 585; RaceStatusEnum raceStatus = RaceStatusEnum.STARTED; long raceStartTime = time - (1000 * 31); - int windDirection = 2341; - int windSpeed = 10201; + Bearing windDirection = Bearing.fromDegrees(185.34); + double windSpeedKnots = 14.52; RaceTypeEnum raceType = RaceTypeEnum.MATCH_RACE; List boatStatuses = new ArrayList<>(2); boatStatuses.add(boatStatus1); @@ -81,7 +82,7 @@ public class RaceStatusDecoderTest { raceStatus, raceStartTime, windDirection, - windSpeed, + windSpeedKnots, raceType, boatStatuses ); @@ -110,8 +111,8 @@ public class RaceStatusDecoderTest { Assert.assertEquals(original.getRaceID(), decoded.getRaceID()); Assert.assertEquals(original.getRaceStatus(), decoded.getRaceStatus()); Assert.assertEquals(original.getExpectedStartTime(), decoded.getExpectedStartTime()); - Assert.assertEquals(original.getWindDirection(), decoded.getWindDirection()); - Assert.assertEquals(original.getWindSpeed(), decoded.getWindSpeed()); + Assert.assertEquals(original.getWindDirection().degrees(), decoded.getWindDirection().degrees(), 0.01); + Assert.assertEquals(original.getWindSpeed(), decoded.getWindSpeed(), 0.01); //Compare all BoatStatuses Iterator originalIterator = original.getBoatStatuses().iterator(); diff --git a/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java b/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java index 24fc2be2..17369098 100644 --- a/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java +++ b/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java @@ -2,42 +2,92 @@ package network.Utils; import org.junit.Test; -import static network.Utils.AC35UnitConverter.convertGPS; -import static network.Utils.AC35UnitConverter.convertGPSToInt; -import static network.Utils.AC35UnitConverter.convertHeading; -import static network.Utils.AC35UnitConverter.convertTrueWindAngle; +import static network.Utils.AC35UnitConverter.*; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** - * Created by fwy13 on 4/05/17. + * Tests the pack/unpack and conversion functions in {@link AC35UnitConverter}. */ public class AC35UnitConverterTest { + + /** + * Tests if gps coordinates can be unpacked. + */ + @Test + public void testUnpackGPS(){ + assertTrue(unpackGPS(0) == 0); + assertTrue(unpackGPS(Integer.MAX_VALUE) == (double)Integer.MAX_VALUE * 180.0 / Math.pow(2, 31)); + + } + + + /** + * Tests if gps coodinates can be packed. + */ @Test - public void testConvertGPS(){ - assertTrue(convertGPS(0) == 0); - assertTrue(convertGPS(Integer.MAX_VALUE) == (double)Integer.MAX_VALUE * 180.0 / Math.pow(2, 31)); + public void testPackGPS(){ + assertTrue(packGPS(0) == 0); + assertTrue(packGPS(180) == (int)2147483648.0); } + /** + * Tests if headings/bearings can be unpacked. + */ + @Test + public void testUnpackHeading(){ + assertTrue(unpackHeading(0) == 0); + assertTrue(unpackHeading(65536) == 360.0); + } + + /** + * Tests if headings/bearings can be packed. + */ @Test - public void testConvertGPSToInt(){ - assertTrue(convertGPSToInt(0) == 0); - assertTrue(convertGPSToInt(180) == (int)2147483648.0); + public void testPackHeading(){ + assertTrue(packHeading(0) == 0); + assertTrue(packHeading(360) == 65536); + } + + /** + * Tests if true wind angles (azimuths) can be unpacked. + */ + @Test + public void testUnpackTrueWindAngle(){ + assertEquals(unpackTrueWindAngle((short)0), 0, 0.001); + assertEquals(unpackTrueWindAngle((short)32767), 180.0, 0.01); } + /** + * Tests if true wind angles (azimuths) can be packed. + */ @Test - public void testConvertHeading(){ - assertTrue(convertHeading(0) == 0); - assertTrue(convertHeading(65536) == 360.0); + public void testPackTrueWindAngle(){ + assertTrue(packTrueWindAngle(0) == (short)0); + assertTrue(packTrueWindAngle(180.0) == (short)32768); } + /** + * Tests if millimeters per second can be unpacked to knots. + */ @Test - public void testConvertTrueWindAngle(){ - assertTrue(convertTrueWindAngle(0) == 0); - assertTrue(convertTrueWindAngle(32768) == 180.0); + public void testUnpackMMperSecToKnots(){ + assertEquals(unpackMMperSecToKnots(0), 0d, 0.001); + assertEquals(unpackMMperSecToKnots(7331), 14.25, 0.01); } + + /** + * Tests if knots can be packed into millimeters per second. + */ + @Test + public void testPackKnotsToMMperSec(){ + assertEquals(packKnotsToMMperSec(0), 0, 1); + assertEquals(packKnotsToMMperSec(7.44), 3828, 1); + } + }