Began fixing bugs with caused by asynchronous listener calls.

#bug
This commit is contained in:
Calum
2017-07-30 20:12:19 +12:00
parent 7894e31926
commit 6cae338c1e
67 changed files with 1370 additions and 1602 deletions
-2
View File
@@ -1,7 +1,5 @@
package seng302.model;
import org.w3c.dom.Node;
/**
* Stores the data for the cornering of a mark.
*/
+27 -29
View File
@@ -1,22 +1,19 @@
package seng302.model;
import static seng302.utilities.GeoUtility.getGeoCoordinate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.scene.control.ListView;
import javafx.scene.paint.Color;
import seng302.model.mark.Mark;
import static seng302.utilities.GeoUtility.getGeoCoordinate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import javafx.scene.paint.Color;
import seng302.client.ClientPacketParser;
import seng302.controllers.RaceViewController;
import seng302.gameServer.GameState;
import seng302.model.mark.Mark;
import seng302.utilities.GeoPoint;
/**
@@ -32,6 +29,11 @@ public class Yacht {
void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity);
}
@FunctionalInterface
public interface YachtPositionListener {
void notifyPosition(int position);
}
//BOTH AFAIK
private String boatType;
private Integer sourceId;
@@ -137,7 +139,7 @@ public class Yacht {
Double windSpeedKnots = GameState.getWindSpeedKnots();
Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading);
Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle);
Double maxBoatSpeed = boatSpeedInKnots / ClientPacketParser.MS_TO_KNOTS * 1000;
Double maxBoatSpeed = boatSpeedInKnots / 1.943844492 * 1000;
if (sailIn && velocity <= maxBoatSpeed && maxBoatSpeed != 0d) {
if (velocity < maxBoatSpeed) {
@@ -159,30 +161,22 @@ public class Yacht {
velocity = 0d;
}
}
if (sailIn) {
Double secondsElapsed = timeInterval / 1000000.0;
Double windSpeedKnots = GameState.getWindSpeedKnots();
Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading);
Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle);
velocity = boatSpeedInKnots / 1.943844492 * 1000; // TODO: 26/07/17 cir27 - Remove magic number
Double metersCovered = velocity * secondsElapsed;
location = getGeoCoordinate(location, heading, metersCovered);
} else {
velocity = 0d;
}
}
}
// if (sailIn) {
// Double secondsElapsed = timeInterval / 1000000.0;
// Double windSpeedKnots = GameState.getWindSpeedKnots();
// Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading);
// Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle);
// velocity = boatSpeedInKnots / 1.943844492 * 1000; // TODO: 26/07/17 cir27 - Remove magic number
// Double metersCovered = velocity * secondsElapsed;
// location = getGeoCoordinate(location, heading, metersCovered);
// } else {
// velocity = 0d;
// }
Double metersCovered = velocity * secondsElapsed;
location = getGeoCoordinate(location, heading, metersCovered);
}
public Double getHeading() {
return heading;
}
public void adjustHeading(Double amount) {
Double newVal = heading + amount;
lastHeading = heading;
@@ -501,4 +495,8 @@ public class Yacht {
public void addLocationListener (YachtLocationListener listener) {
locationListeners.add(listener);
}
public void addPositionListener (YachtPositionListener listener) {
}
}
@@ -1,44 +0,0 @@
package seng302.model.map;
/**
* The Boundary class represents a rectangle territorial boundary on a map. It
* contains four extremity double values(N, E, S, W). N and S are represented as
* latitudes in radians. E and W are represented as longitudes in radians.
*
* Created by Haoming on 10/5/17
*/
public class Boundary {
private double northLat, eastLng, southLat, westLng;
public Boundary(double northLat, double eastLng, double southLat, double westLng) {
this.northLat = northLat;
this.eastLng = eastLng;
this.southLat = southLat;
this.westLng = westLng;
}
double getCentreLat() {
return (northLat + southLat) / 2;
}
double getCentreLng() {
return (eastLng + westLng) / 2;
}
double getNorthLat() {
return northLat;
}
double getEastLng() {
return eastLng;
}
double getSouthLat() {
return southLat;
}
double getWestLng() {
return westLng;
}
}
@@ -1,106 +0,0 @@
package seng302.model.map;
import javafx.geometry.Point2D;
import javafx.scene.image.Image;
import seng302.utilities.GeoPoint;
import javax.net.ssl.HttpsURLConnection;
import java.net.URL;
import java.lang.Math;
/**
* CanvasMap retrieves a map image with given geo boundary from Google Map server.
* By passing a rectangle like geo boundary, it returns a map image with the
* highest resolution. However, due to free quote account usage limit, the maximum
* resolution is only 1280 * 1280.
*
* Created by Haoming on 15/5/2017
*/
public class CanvasMap {
private Boundary boundary;
private long width, height; // desired image size
private int zoom;
private String KEY = "AIzaSyC-5oOShMCY5Oy_9L7guYMPUPFHDMr37wE";
public CanvasMap(Boundary boundary) {
this.boundary = boundary;
calculateOptimalMapSize();
}
public Image getMapImage() {
try {
URL url = new URL(getRequest());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
return new Image(connection.getInputStream());
} catch (Exception e) {
System.out.println("[CanvasMap] Exception");
return null;
}
}
private String getRequest() {
StringBuilder sb = new StringBuilder();
sb.append("https://maps.googleapis.com/maps/api/staticmap?");
sb.append(String.format("center=%f,%f", boundary.getCentreLat(), boundary.getCentreLng()));
sb.append(String.format("&zoom=%d", zoom));
sb.append(String.format("&size=%dx%d&scale=2", width, height));
sb.append("&style=feature:all|element:labels|visibility:off"); // hide all labels on map
// sb.append(String.format("&markers=%f,%f", boundary.getSouthLat(), boundary.getWestLng()));
// sb.append(String.format("&key=%s", KEY));
return sb.toString();
}
private void calculateOptimalMapSize() {
for (int z = 20; z > 0; z--) {
MapSize mapSize = getMapSize(z, boundary);
zoom = z;
width = mapSize.width;
height = mapSize.height;
// if map size is valid, exit the loop as we have the highest resolution
if (mapSize.isValid()) break;
}
}
private MapSize getMapSize(int zoom, Boundary boundary) {
double scale = Math.pow(2, zoom);
GeoPoint geoSW = new GeoPoint(boundary.getSouthLat(), boundary.getWestLng());
GeoPoint geoNE = new GeoPoint(boundary.getNorthLat(), boundary.getEastLng());
Point2D pointSW = MercatorProjection.toMapPoint(geoSW);
Point2D pointNE = MercatorProjection.toMapPoint(geoNE);
return new MapSize(Math.abs(pointNE.getX() - pointSW.getX()) * scale,
Math.abs(pointNE.getY() - pointSW.getY()) * scale);
}
class MapSize {
long width, height;
MapSize(double width, double height) {
this.width = Math.round(width);
this.height = Math.round(height);
}
/**
* Map size is valid when width and height are both less than 640 pixels
* @return true if both dimensions are less than 640px
*/
boolean isValid() {
return Math.max(width, height) <= 640;
}
}
public long getWidth() {
return width;
}
public long getHeight() {
return height;
}
public int getZoom() {
return zoom;
}
}
@@ -1,55 +0,0 @@
package seng302.model.map;
import javafx.geometry.Point2D;
import seng302.utilities.GeoPoint;
/**
* An utility class useful to convert between Geo locations and Mercator projection
* planar coordinates.
* Created by Haoming on 15/5/2017
*/
public class MercatorProjection {
private static final double MERCATOR_RANGE = 256;
private static final double pixelsPerLngDegree = MERCATOR_RANGE / 360.0;
private static final double pixelsPerLngRadian = MERCATOR_RANGE / (2 * Math.PI);
/**
* A help function keeps the value in bound between -0.9999 and 0.9999.
* @param value in bound value
* @return the value in bound
*/
private static double bound(double value) {
return Math.min(Math.max(value, -0.9999), 0.9999);
}
/**
* Projects a Geo Location (lat, lng) on a planar
* @param geo GeoPoint (lat, lng) location to be projected
* @return the projection Point2D (x, y) on planar
*/
public static Point2D toMapPoint(GeoPoint geo) {
double x, y;
Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
x = (origin.getX() + geo.getLng() * pixelsPerLngDegree);
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
// 89.189. This is about a third of a tile past the edge of the world tile.
double sinY = bound(Math.sin(Math.toRadians(geo.getLat())));
y = origin.getY() + 0.5 * Math.log((1 + sinY) / (1 - sinY)) * (-pixelsPerLngRadian);
return new Point2D(x, y);
}
/**
* Converts the planar projection (x, y) back to Geo Location (lat, lng)
* @param point Point2D (x, y) to be converted back
* @return the original Geo location converted from the given projection point
*/
public static GeoPoint toMapGeo(Point2D point) {
Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
double lng = (point.getX() - origin.getX()) / pixelsPerLngDegree;
double latRadians = (point.getY() - origin.getY()) / (-pixelsPerLngRadian);
double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2.0);
return new GeoPoint(lat, lng);
}
}
@@ -1,23 +0,0 @@
package seng302.model.map;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import java.net.URL;
import java.util.ResourceBundle;
public class TestMapController implements Initializable{
@FXML
private Canvas mapCanvas;
@Override
public void initialize(URL location, ResourceBundle resources) {
GraphicsContext gc = mapCanvas.getGraphicsContext2D();
Boundary bound = new Boundary(57.662943, 11.848501, 57.673945, 11.824966);
CanvasMap canvasMap = new CanvasMap(bound);
gc.drawImage(canvasMap.getMapImage(), 0, 0, canvasMap.getWidth(), canvasMap.getHeight());
}
}
@@ -1,7 +0,0 @@
package seng302.model.stream;
import seng302.model.stream.packets.StreamPacket;
public interface PacketBufferDelegate {
boolean addToBuffer(StreamPacket streamPacket);
}
@@ -1,130 +0,0 @@
package seng302.model.stream;
import seng302.model.stream.packets.StreamPacket;
import java.io.*;
import java.net.Socket;
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
public class StreamReceiver extends Thread {
private InputStream inputStream;
private OutputStream outputStream;
private Socket host;
private ByteArrayOutputStream crcBuffer;
private Thread t;
private String threadName;
public static PriorityBlockingQueue<StreamPacket> packetBuffer;
private static boolean moreBytes;
public StreamReceiver(String hostAddress, int hostPort, String threadName) {
this.threadName = threadName;
this.setDaemon(true);
try {
host = new Socket(hostAddress, hostPort);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void run(){
PriorityBlockingQueue<StreamPacket> pq = new PriorityBlockingQueue<>(256, new Comparator<StreamPacket>() {
@Override
public int compare(StreamPacket s1, StreamPacket s2) {
return (int) (s1.getTimeStamp() - s2.getTimeStamp());
}
});
packetBuffer = pq;
connect();
}
public void start () {
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
public StreamReceiver(Socket host, PriorityBlockingQueue packetBuffer){
this.host=host;
this.packetBuffer = packetBuffer;
}
public void connect(){
// int sync1;
// int sync2;
// moreBytes = true;
// while(moreBytes) {
// try {
// crcBuffer = new ByteArrayOutputStream();
// sync1 = readByte();
// sync2 = readByte();
// //checking if it is the start of the packet
// if(sync1 == 0x47 && sync2 == 0x83) {
// int type = readByte();
// //No. of milliseconds since Jan 1st 1970
// long timeStamp = bytesToLong(getBytes(6));
// skipBytes(4);
// long payloadLength = bytesToLong(getBytes(2));
// byte[] payload = getBytes((int) payloadLength);
// Checksum checksum = new CRC32();
// checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
// long computedCrc = checksum.getValue();
// long packetCrc = bytesToLong(getBytes(4));
// if (computedCrc == packetCrc) {
// packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload));
// } else {
// System.err.println("Packet has been dropped");
// }
// }
// } catch (Exception e) {
// moreBytes = false;
// }
// }
}
private int readByte() throws Exception {
int currentByte = -1;
try {
currentByte = inputStream.read();
crcBuffer.write(currentByte);
} catch (IOException e) {
e.printStackTrace();
}
if (currentByte == -1){
throw new Exception();
}
return currentByte;
}
private byte[] getBytes(int n) throws Exception{
byte[] bytes = new byte[n];
for (int i = 0; i < n; i++){
bytes[i] = (byte) readByte();
}
return bytes;
}
private void skipBytes(long n) throws Exception{
for (int i=0; i < n; i++){
readByte();
}
}
public static void main(String[] args) {
StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1");
//StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread2");
sr.start();
}
public static void noMoreBytes(){
moreBytes = false;
}
}
@@ -11,7 +11,7 @@ public class MarkRoundingData {
private int roundingSide;
private long timeStamp;
MarkRoundingData(int boatId, int markId, int roundingSide, long timeStamp) {
public MarkRoundingData(int boatId, int markId, int roundingSide, long timeStamp) {
this.boatId = boatId;
this.markId = markId;
this.roundingSide = roundingSide;
@@ -14,7 +14,7 @@ public class PositionUpdateData {
private double heading;
private double groundSpeed;
PositionUpdateData(int deviceId, DeviceType type, double lat, double lon,
public PositionUpdateData(int deviceId, DeviceType type, double lat, double lon,
double heading, double groundSpeed) {
this.deviceId = deviceId;
this.type = type;
@@ -10,7 +10,7 @@ public class RaceStartData {
private int notificationType;
private long timeStamp;
RaceStartData (long raceId, long raceStartTime, int notificationType, long timeStamp) {
public RaceStartData (long raceId, long raceStartTime, int notificationType, long timeStamp) {
this.raceId = raceId;
this.raceStartTime = raceStartTime;
this.notificationType = notificationType;
@@ -19,7 +19,7 @@ public class RaceStatusData {
private long expectedStartTime;
private List<long[]> boatData = new ArrayList<>();
RaceStatusData(
public RaceStatusData(
long windDir, long rawWindSpeed, int raceStatus, long currentTime, long expectedStartTime) {
windDirection = windDir / WIND_DIR_FACTOR;
@@ -29,8 +29,8 @@ public class RaceStatusData {
this.expectedStartTime = expectedStartTime;
}
void addBoatData (long boatID, long estTimeToNextMark, long estTimeToFinish, int leg) {
boatData.add(new long[] {boatID, estTimeToNextMark, estTimeToFinish, leg});
public void addBoatData (long boatID, long estTimeToNextMark, long estTimeToFinish, int leg, int boatStatus) {
boatData.add(new long[] {boatID, estTimeToNextMark, estTimeToFinish, leg, boatStatus});
}
public double getWindDirection() {
@@ -1,425 +0,0 @@
package seng302.model.stream.parser;
import seng302.model.stream.parser.PositionUpdateData.DeviceType;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
/**
* StreamParser is a utilities class for taking byte data, formatted according to the AC35
* streaming protocol, and parsing it into basic data types or collections.
*
* Created by kre39 on 23/04/17.
*/
public class StreamParser {
/**
* Extracts and returns the seq num used in the heartbeat packet.
*
* @param packet Packet parsed in to use the payload
* @return the packet sequence number if the packet is of type HEARTBEAT, null otherwise.
*/
public static Long extractHeartBeat(StreamPacket packet) {
if (packet.getType() != PacketType.HEARTBEAT)
return null;
long heartbeat = bytesToLong(packet.getPayload());
System.out.println("heartbeat = " + heartbeat);
return heartbeat;
}
/**
* Extracts the useful race status data from race status type packets. This method will also
* print to the console the current state of the race (if it has started/finished or is about to
* start), along side this it'll also display the amount of time since the race has started or
* time till it starts
*
* @param packet Packet parsed in to use the payload
* @return null if the packet type is not RACE_STATUS, otherwise an instance of RaceStatusData
* containing the parsed packet data.
*/
public static RaceStatusData extractRaceStatus(StreamPacket packet) {
if (packet.getType() != PacketType.RACE_STATUS)
return null;
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long currentTime = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11));
int raceStatus = payload[11];
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18));
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
// currentTime = format.format((new Date(currentTime)))
RaceStatusData data = new RaceStatusData(
windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime
);
// long timeTillStart =
// ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000;
//
// if (timeTillStart > 0) {
// timeSinceStart = timeTillStart;
// } else {
// if (raceStatus == 4 || raceStatus == 8) {
// raceFinished = true;
// raceStarted = false;
// } else if (!raceStarted) {
// raceStarted = true;
// raceFinished = false;
// }
// timeSinceStart = timeTillStart;
// }
//
//
int noBoats = payload[22];
int raceType = payload[23];
for (int i = 0; i < noBoats; i++) {
long boatID = bytesToLong(
Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20)));
// boat.setBoatStatus((int) payload[28 + (i * 20)]);
// setBoatLegPosition(boat, (int) payload[29 + (i * 20)]);
// boat.setPenaltiesAwarded((int) payload[30 + (i * 20)]);
// boat.setPenaltiesServed((int) payload[31 + (i * 20)]);
Long estTimeAtNextMark = bytesToLong(
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20)));
// boat.setEstimateTimeTillNextMark(estTimeAtNextMark);
Long estTimeAtFinish = bytesToLong(
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
int leg = (int) payload[29 + (i * 20)];
// boat.setEstimateTimeAtFinish(estTimeAtFinish);
data.addBoatData(boatID, estTimeAtNextMark, estTimeAtFinish, leg);
}
return data;
}
// private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
// Integer placing = 1;
// if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) {
// for (Yacht boat : boats.values()) {
// if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){
// placing += 1;
// }
// }
// updatingBoat.setPosition(placing.toString());
// updatingBoat.setLegNumber(leg);
// boatsPos.putIfAbsent(placing, updatingBoat);
// boatsPos.replace(placing, updatingBoat);
// } else if(updatingBoat.getLegNumber() == null){
// updatingBoat.setPosition("1");
// updatingBoat.setLegNumber(leg);
// }
// }
/**
* Parses and returns the text from a StreamPacket containing text data for display.
*
* @param packet Packet parsed in to use the payload
* @return A list containing all display message text. Is null if the packet is not of type
* DISPLAY_TEXT_MESSAGE.
*/
public static List<String> extractDisplayMessage(StreamPacket packet) {
if (packet.getType() != PacketType.DISPLAY_TEXT_MESSAGE)
return null;
List<String> message = new ArrayList<>();
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int numOfLines = payload[3];
int totalLen = 0;
for (int i = 0; i < numOfLines; i++) {
int lineNum = payload[4 + totalLen];
int textLength = payload[5 + totalLen];
byte[] messageTextBytes = Arrays
.copyOfRange(payload, 6 + totalLen, 6 + textLength + totalLen);
message.add(new String(messageTextBytes));
totalLen += 2 + textLength;
}
return message;
}
/**
* Parses and returns an XMLParser containing XML data sent in the given StreamPacket. XML data
* can be for races, boats or the regatta.
*
* @param packet Packet parsed in to use the payload
* @return XMLParse containing xmldata. Returns null if the StreamPacket is not of type
* XML_MESSAGE.
*/
public static Document extractXmlMessage(StreamPacket packet) {
if ( packet.getType() != PacketType.RACE_XML &&
packet.getType() != PacketType.REGATTA_XML &&
packet.getType() != PacketType.BOAT_XML )
return null;
byte[] payload = packet.getPayload();
int messageType = payload[9];
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
String xmlMessage = new String(
(Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim();
//Create XML document Object
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xmlMessage)));
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
return doc;
}
/**
* Extracts the race start status from the packet and returns it as a long array.
*
* @param packet Packet parsed in to use the payload
* @return An array of form [raceID, raceStartTime, notificationType, timeStamp] or null if
* the packet type is not of RACE_START_STATUS.
*/
public static RaceStartData extractRaceStartStatus(StreamPacket packet) {
if (packet.getType() != PacketType.RACE_START_STATUS) {
return null;
}
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceStartTime = bytesToLong(Arrays.copyOfRange(payload, 9, 15));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 15, 19));
int notificationType = payload[19];
return new RaceStartData(raceId, raceStartTime, notificationType, timeStamp);
}
/**
* Parses the the byte array in a StreamPacket for yacht events to retrieve the necessary info
* and returns it a an array of longs.
*
* @param packet Packet parsed in to use the payload
* @return the event data in the form [boatID, incidentID, eventID, timeStamp]. Returns null if
* the packet is not of type YACHT_EVENT_CODE.
*/
public static long[] extractYachtEventCode(StreamPacket packet) {
if (packet.getType() != PacketType.YACHT_EVENT_CODE)
return null;
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 17, 21));
int eventId = payload[21];
return new long[] {subjectId, incidentId, eventId, timeStamp};
}
/**
* Parses data from a StreamPacket for yacht actions and returns it in a long array.
*
* @param packet Packet parsed in to use the payload
* @return long array of packet data in the form [subjectID, incidentID, eventID, timeStamp].
* Returns null if the packet is not of type YACHT_ACTION_CODE.
*/
public static long[] extractYachtActionCode(StreamPacket packet) {
if (packet.getType() != PacketType.YACHT_ACTION_CODE)
return null;
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
int eventId = payload[17];
return new long[] {subjectId, incidentId, eventId, timeStamp};
}
/**
* Strips the message from the chatter text type packets.
*
* @param packet Packet parsed in to use the payload
* @return Chatter text message as a string. Returns null if the packet is not of type
* CHATTER_TEXT.
*/
public static String extractChatterText(StreamPacket packet) {
if (packet.getType() != PacketType.CHATTER_TEXT)
return null;
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int messageType = payload[1];
int length = payload[2];
return new String(Arrays.copyOfRange(payload, 3, 3 + length));
}
/**
* Takes the data from a bot location stream packet and parses the id, timeValid, lat, lon,
* heading and groundspeed into a BoatPositionPacket which is returned.
*
* @param packet Packet parsed in to use the payload
* @return BoatPositionPacket containing important boat information. Returns null if the packet
* is not of type BOAT_LOCATION.
*/
public static PositionUpdateData extractBoatLocation(StreamPacket packet) {
if (packet.getType() != PacketType.BOAT_LOCATION)
return null;
byte[] payload = packet.getPayload();
int deviceType = (int) payload[15];
long timeValid = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long seq = bytesToLong(Arrays.copyOfRange(payload, 11, 15));
long boatId = bytesToLong(Arrays.copyOfRange(payload, 7, 11));
long rawLat = bytesToLong(Arrays.copyOfRange(payload, 16, 20));
long rawLon = bytesToLong(Arrays.copyOfRange(payload, 20, 24));
//Converts the double to a usable lat/lon
double lat = ((180d * (double) rawLat) / Math.pow(2, 31));
double lon = ((180d * (double) rawLon) / Math.pow(2, 31));
double heading = bytesToLong(Arrays.copyOfRange(payload, 28, 30));
heading = 360.0 / 0xffff * heading; //Convert to degrees.
double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0;
DeviceType type;
if (deviceType == 1)
type = DeviceType.YACHT_TYPE;
else
type = DeviceType.MARK_TYPE;
return new PositionUpdateData((int) boatId, type, lat, lon, heading, groundSpeed);
}
/**
* Processes a stream packet for a mark rounding and returns the boatID, markID and timestamp.
*
* @param packet The packet containing the payload
* @return an array containing longs. The values are [boatID, markID, timeStamp]. Returns null
* if packet is not of type MARK_ROUNDING.
*/
public static MarkRoundingData extractMarkRounding(StreamPacket packet) {
if (packet.getType() != PacketType.MARK_ROUNDING)
return null;
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
int boatStatus = payload[17];
int roundingSide = payload[18];
int markType = payload[19];
int markId = payload[20];
return new MarkRoundingData((int) subjectId, markId, roundingSide, timeStamp);
}
/**
* Returns a list containing the string value of data within the given stream packet for
* course wind.
*
* @param packet The packet containing the payload
* @return the string values of the wind packet. Returns null if the packet is not of type
* COURSE_WIND.
*/
public static List<String> extractCourseWind(StreamPacket packet) {
if (packet.getType() != PacketType.COURSE_WIND)
return null;
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int selectedWindId = payload[1];
int loopCount = payload[2];
List<String> windInfo = new ArrayList<>();
for (int i = 0; i < loopCount; i++) {
String wind = "WindId: " + payload[3 + (20 * i)];
wind +=
"\nTime: " + bytesToLong(Arrays.copyOfRange(payload, 4 + (20 * i), 10 + (20 * i)));
wind += "\nRaceId: " + bytesToLong(
Arrays.copyOfRange(payload, 10 + (20 * i), 14 + (20 * i)));
wind += "\nWindDirection: " + bytesToLong(
Arrays.copyOfRange(payload, 14 + (20 * i), 16 + (20 * i)));
wind += "\nWindSpeed: " + bytesToLong(
Arrays.copyOfRange(payload, 16 + (20 * i), 18 + (20 * i)));
wind += "\nBestUpWindAngle: " + bytesToLong(
Arrays.copyOfRange(payload, 18 + (20 * i), 20 + (20 * i)));
wind += "\nBestDownWindAngle: " + bytesToLong(
Arrays.copyOfRange(payload, 20 + (20 * i), 22 + (20 * i)));
wind += "\nFlags: " + String
.format("%8s", Integer.toBinaryString(payload[22 + (20 * i)] & 0xFF))
.replace(' ', '0');
windInfo.add(wind);
}
return windInfo;
}
/**
* Returns the parsed data from a StreamPacket for average wind data.
*
* @param packet The packet containing the payload
* @return The wind data in the form
* [rawPeriod, rawSamplePeriod, period2, speed2, period3, speed3, period4, speed4, timestamp]
* or null if the packet is not of type AVG_WIND.
*/
public static long[] extractAvgWind(StreamPacket packet) {
if (packet.getType() != PacketType.AVG_WIND)
return null;
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long rawPeriod = bytesToLong(Arrays.copyOfRange(payload, 7, 9));
long rawSamplePeriod = bytesToLong(Arrays.copyOfRange(payload, 9, 11));
long period2 = bytesToLong(Arrays.copyOfRange(payload, 11, 13));
long speed2 = bytesToLong(Arrays.copyOfRange(payload, 13, 15));
long period3 = bytesToLong(Arrays.copyOfRange(payload, 15, 17));
long speed3 = bytesToLong(Arrays.copyOfRange(payload, 17, 19));
long period4 = bytesToLong(Arrays.copyOfRange(payload, 19, 21));
long speed4 = bytesToLong(Arrays.copyOfRange(payload, 21, 23));
return new long[] {
rawPeriod, rawSamplePeriod, period2, speed2, period3, speed3, period4, speed4, timeStamp
};
}
public static void extractBoatAction(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long actionType = bytesToLong(Arrays.copyOfRange(payload, 0, 1));
if (actionType == 1) {
System.out.println("VMG");
} else if (actionType == 2) {
System.out.println("SAILS IN");
} else if (actionType == 3) {
System.out.println("SAILS OUT");
} else if (actionType == 4) {
System.out.println("TACK/GYBE");
} else if (actionType == 5) {
System.out.println("UPWIND");
} else if (actionType == 6) {
System.out.println("DOWNWIND");
}
}
/**
* takes an array of up to 7 bytes and returns a positive
* long constructed from the input bytes
*
* @return a positive long if there is less than 7 bytes -1 otherwise
*/
public static long bytesToLong(byte[] bytes) {
long partialLong = 0;
int index = 0;
for (byte b : bytes) {
if (index > 6) {
return -1;
}
partialLong = partialLong | (b & 0xFFL) << (index * 8);
index++;
}
return partialLong;
}
}
@@ -1,159 +0,0 @@
package seng302.model.stream.xml.generator;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import seng302.server.messages.XMLMessageSubType;
import java.io.*;
/**
* An XML generator to generate the Race, Boat, and Regatta XML dynamically
*/
public class XMLGenerator {
private static final String XML_TEMPLATE_DIR = "/server_config/xml_templates";
private static final String REGATTA_TEMPLATE_NAME = "regatta.ftlh";
private static final String BOATS_TEMPLATE_NAME = "boats.ftlh";
private static final String RACE_TEMPLATE_NAME = "race.ftlh";
private Configuration configuration;
private Regatta regatta;
private Race race;
/**
* Set up a configuration instance for Apache Freemake
*/
private void setupConfiguration() {
configuration = new Configuration(Configuration.VERSION_2_3_26);
try {
configuration.setClassForTemplateLoading(getClass(), XML_TEMPLATE_DIR);
} catch (NullPointerException e){
System.out.println("[FATAL] Server could not load XML Template directory, ensure this directory isn't empty");
}
}
/**
* Create an instance of the XML Generator
*/
public XMLGenerator(){
setupConfiguration();
}
/**
* Set the race regatta to send to players
* Note: This must be set before a regatta message can be generated
* @param regatta The race regatta
*/
public void setRegatta(Regatta regatta){
this.regatta = regatta;
}
/**
* Set the race to send to players
* Note: This must be set before a boat or race message can be generated
* @param race The race
*/
public void setRace(Race race){
this.race = race;
}
/**
* Parse an XML template and generate the output as a string
* @param templateName The templates file name
* @param type The XML message sub type
*/
private String parseToXmlString(String templateName, XMLMessageSubType type) throws IOException, TemplateException {
Template template;
ByteArrayOutputStream os = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os);
template = configuration.getTemplate(templateName);
switch (type) {
case REGATTA:
template.process(regatta, writer);
break;
case BOAT:
template.process(race, writer);
break;
case RACE:
template.process(race, writer);
break;
default:
throw new UnsupportedOperationException();
}
try {
return os.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
System.out.println("[FATAL] UTF-8 Not supported");
return null;
}
}
/**
* Get the race regatta as a string
* Note: Regatta must be set before calling this
* @return String containing the regatta XML, null if there was an error
*/
public String getRegattaAsXml(){
String result = null;
if (regatta == null) return null;
try {
result = parseToXmlString(REGATTA_TEMPLATE_NAME, XMLMessageSubType.REGATTA);
} catch (TemplateException e) {
System.out.println("[FATAL] Error parsing regatta");
} catch (IOException e) {
System.out.println("[FATAL] Error reading regatta");
}
return result;
}
/**
* Get the boats XML as a string
* Note: Race must be set before calling this
* @return String containing the boats XML, null if there was an error
*/
public String getBoatsAsXml() {
String result = null;
if (race == null) return null;
try {
result = parseToXmlString(BOATS_TEMPLATE_NAME, XMLMessageSubType.BOAT);
} catch (TemplateException e) {
System.out.println("[FATAL] Error parsing boats");
} catch (IOException e) {
System.out.println("[FATAL] Error reading boats");
}
return result;
}
/**
* Get the race XML as a string
* Note: Race must be set before calling this
* @return String containing the race XML, null if there was an error
*/
public String getRaceAsXml() {
String result = null;
if (race == null) return null;
try {
result = parseToXmlString(RACE_TEMPLATE_NAME, XMLMessageSubType.RACE);
} catch (TemplateException e) {
System.out.println("[FATAL] Error parsing race");
} catch (IOException e) {
System.out.println("[FATAL] Error reading race");
}
return result;
}
}
@@ -18,7 +18,7 @@ public class RaceXMLData {
private List<Limit> courseLimit;
private Map<Integer, Mark> individualMarks;
RaceXMLData(List<Integer> participants, List<Mark> compoundMarks, List<Corner> markSequence,
public RaceXMLData(List<Integer> participants, List<Mark> compoundMarks, List<Corner> markSequence,
List<Limit> courseLimit) {
this.participants = participants;
this.markSequence = markSequence;
@@ -12,7 +12,7 @@ public class RegattaXMLData {
private Double centralLng;
private Integer utcOffset;
RegattaXMLData (Integer regattaID, String regattaName, String courseName,
public RegattaXMLData (Integer regattaID, String regattaName, String courseName,
Double centralLat, Double centralLng, Integer utcOffset) {
this.regattaID = regattaID;
this.regattaName = regattaName;
@@ -1,290 +0,0 @@
package seng302.model.stream.xml.parser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import seng302.model.Yacht;
import seng302.model.Corner;
import seng302.model.Limit;
import seng302.model.mark.GateMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkType;
import seng302.model.mark.SingleMark;
/**
* Utilities for parsing XML documents
*/
public class XMLParser {
/**
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
*
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static Integer getElementInt(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Integer.parseInt(tagList.item(0).getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of a given child element tag, assuming it exists, as an String.
*
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static String getElementString(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return tagList.item(0).getTextContent();
} else {
return null;
}
}
/**
* Returns the text content of a given child element tag, assuming it exists, as a Double.
*
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static Double getElementDouble(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Double.parseDouble(tagList.item(0).getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as a String.
*
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The String representation of the text content of an attribute in the given node, else
* returns null.
*/
private static String getNodeAttributeString(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return attrItem.getTextContent();
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as an Integer.
*
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The Integer representation of the text content of an attribute in the given node,
* else returns null.
*/
private static Integer getNodeAttributeInt(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return Integer.parseInt(attrItem.getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as a Double.
*
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The Double representation of the text content of an attribute in the given node, else
* returns null.
*/
private static Double getNodeAttributeDouble(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return Double.parseDouble(attrItem.getTextContent());
} else {
return null;
}
}
/**
* Produces a mapping of boat sourceIDS to boat objects created from the given xml document.
* @param doc XML Document Object
* @return Mapping of sourceIds to Boats.
*/
public static Map<Integer, Yacht> parseBoats(Document doc){
Map<Integer, Yacht> competingBoats = new HashMap<>();
Element docEle = doc.getDocumentElement();
NodeList boatsList = docEle.getElementsByTagName("Boats").item(0).getChildNodes();
for (int i = 0; i < boatsList.getLength(); i++) {
Node currentBoat = boatsList.item(i);
if (currentBoat.getNodeName().equals("Boat")) {
// Boat boat = new Boat(currentBoat);
Yacht yacht = new Yacht(XMLParser.getNodeAttributeString(currentBoat, "Type"),
XMLParser.getNodeAttributeInt(currentBoat, "SourceID"),
XMLParser.getNodeAttributeString(currentBoat, "HullNum"),
XMLParser.getNodeAttributeString(currentBoat, "ShortName"),
XMLParser.getNodeAttributeString(currentBoat, "BoatName"),
XMLParser.getNodeAttributeString(currentBoat, "Country"));
if (yacht.getBoatType().equals("Yacht")) {
competingBoats.put(yacht.getSourceId(), yacht);
}
}
}
return competingBoats;
}
/**
* Returns an object containing the data extracted from the given xml formatted document
*
* @param doc XML Document Object
* @return Object containing regatta data
*/
public static RegattaXMLData parseRegatta(Document doc) {
Element docEle = doc.getDocumentElement();
Integer regattaID = XMLParser.getElementInt(docEle, "RegattaID");
String regattaName = XMLParser.getElementString(docEle, "RegattaName");
String courseName = XMLParser.getElementString(docEle, "CourseName");
Double centralLat = XMLParser.getElementDouble(docEle, "CentralLatitude");
Double centralLng = XMLParser.getElementDouble(docEle, "CentralLongitude");
Integer utcOffset = XMLParser.getElementInt(docEle, "UtcOffset");
return new RegattaXMLData(
regattaID, regattaName, courseName, centralLat, centralLng, utcOffset
);
}
/**
* Returns an object containing the data extracted from the given xml formatted document
*
* @param doc XML document
* @return object containing race data
*/
public static RaceXMLData parseRace(Document doc) {
Element docEle = doc.getDocumentElement();
return new RaceXMLData(
extractParticpantIDs(docEle),
extractCompoundMarks(docEle),
extractMarkOrder(docEle),
extractCourseLimit(docEle)
);
}
/**
* Extracts course limit data
*/
private static List<Limit> extractCourseLimit(Element docEle) {
List<Limit> courseLimit = new ArrayList<>();
NodeList limitList = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes();
for (int i = 0; i < limitList.getLength(); i++) {
Node limitNode = limitList.item(i);
if (limitNode.getNodeName().equals("Limit")) {
courseLimit.add(
new Limit(
XMLParser.getNodeAttributeInt(limitNode, "SeqID"),
XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
XMLParser.getNodeAttributeDouble(limitNode, "lon")
)
);
}
}
return courseLimit;
}
/**
* Extracts course order data
*/
private static List<Corner> extractMarkOrder (Element docEle) {
List<Corner> compoundMarkSequence = new ArrayList<>();
NodeList cornerList = docEle.getElementsByTagName("CompoundMarkSequence").item(0)
.getChildNodes();
for (int i = 0; i < cornerList.getLength(); i++) {
Node cornerNode = cornerList.item(i);
if (cornerNode.getNodeName().equals("Corner")) {
compoundMarkSequence.add(
new Corner(
XMLParser.getNodeAttributeInt(cornerNode, "SeqID"),
XMLParser.getNodeAttributeInt(cornerNode, "CompoundMarkID"),
XMLParser.getNodeAttributeString(cornerNode, "Rounding"),
XMLParser.getNodeAttributeInt(cornerNode, "ZoneSize")
)
);
}
}
return compoundMarkSequence;
}
/**
* Extracts course participants data
*/
private static List<Integer> extractParticpantIDs (Element docEle) {
List<Integer> boatIDs = new ArrayList<>();
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
for (int i = 0; i < pList.getLength(); i++) {
Node pNode = pList.item(i);
if (pNode.getNodeName().equals("Yacht")) {
boatIDs.add(XMLParser.getNodeAttributeInt(pNode, "SourceID"));
}
}
return boatIDs;
}
/**
* Extracts course mark data
*/
private static List<Mark> extractCompoundMarks(Element docEle) {
List<Mark> allMarks = new ArrayList<>();
NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) {
allMarks.add(createMark(cMarkNode));
}
}
return allMarks;
}
/**
* Creates marks objects from the given node
*/
private static Mark createMark(Node compoundMark) {
List<SingleMark> subMarks = new ArrayList<>();
Integer compoundMarkID = XMLParser.getNodeAttributeInt(compoundMark, "CompoundMarkID");
String cMarkName = XMLParser.getNodeAttributeString(compoundMark, "Name");
NodeList childMarks = compoundMark.getChildNodes();
for (int i = 0; i < childMarks.getLength(); i++) {
Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) {
Integer sourceID = XMLParser.getNodeAttributeInt(markNode, "SourceID");
String markName = XMLParser.getNodeAttributeString(markNode, "Name");
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "TargetLat");
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "TargetLng");
SingleMark mark = new SingleMark(markName, targetLat, targetLng, sourceID,
compoundMarkID);
subMarks.add(mark);
}
}
if (subMarks.size() == 1) {
return subMarks.get(0);
} else {
return new GateMark( cMarkName, MarkType.OPEN_GATE, subMarks.get(0), subMarks.get(1),
subMarks.get(0).getLatitude(), subMarks.get(0).getLongitude(), compoundMarkID
);
}
}
}