Parsing classes now static utilities. Data now moved to model via controller class. Race logic shifted out of grpahics classes. Several improvements to code readability.

#story[986] #refactor
This commit is contained in:
Calum
2017-07-24 12:14:08 +12:00
parent 3ec1242a9a
commit aad93d8913
33 changed files with 1515 additions and 1682 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ import javafx.scene.Scene;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.stage.Stage; import javafx.stage.Stage;
import seng302.model.PolarTable; import seng302.model.PolarTable;
import seng302.model.stream.StreamParser; import seng302.model.stream.parsers.StreamParser;
import seng302.model.stream.StreamReceiver; import seng302.model.stream.StreamReceiver;
public class App extends Application { public class App extends Application {
@@ -2,7 +2,7 @@ package seng302.gameServer;
import seng302.model.Player; import seng302.model.Player;
import seng302.model.stream.PacketBufferDelegate; import seng302.model.stream.PacketBufferDelegate;
import seng302.model.stream.StreamParser; import seng302.model.stream.parsers.StreamParser;
import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.packets.StreamPacket;
import java.io.IOException; import java.io.IOException;
@@ -1,15 +1,9 @@
package seng302.gameServer; package seng302.gameServer;
import seng302.model.Player; import seng302.model.Player;
import seng302.model.stream.StreamParser; import seng302.model.stream.parsers.StreamParser;
import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.packets.StreamPacket;
import seng302.gameServer.GameState;
import seng302.models.Player;
import seng302.models.stream.PacketBufferDelegate;
import seng302.models.stream.StreamParser;
import seng302.models.stream.packets.StreamPacket;
import seng302.server.messages.ChatterMessage; import seng302.server.messages.ChatterMessage;
import seng302.server.messages.Heartbeat;
import seng302.server.messages.Message; import seng302.server.messages.Message;
import java.io.*; import java.io.*;
@@ -2,6 +2,7 @@ package seng302.model;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import seng302.model.mark.Mark; import seng302.model.mark.Mark;
import seng302.model.stream.packets.StreamPacket;
import seng302.visualiser.controllers.RaceViewController; import seng302.visualiser.controllers.RaceViewController;
import java.text.DateFormat; import java.text.DateFormat;
@@ -13,10 +14,10 @@ import java.text.SimpleDateFormat;
* Class created to store more variables (eg. boat statuses) compared to the XMLParser boat class, * Class created to store more variables (eg. boat statuses) compared to the XMLParser boat class,
* also done outside Boat class because some old variables are not used anymore. * also done outside Boat class because some old variables are not used anymore.
*/ */
public class Yacht { public class Boat {
// Used in boat group // Used in boat group
private Color colour; private Color colour = Color.BLACK;
private String boatType; private String boatType;
private Integer sourceID; private Integer sourceID;
@@ -25,19 +26,16 @@ public class Yacht {
private String boatName; private String boatName;
private String country; private String country;
// Situational data
// Boat status // Boat status
private Integer boatStatus; private Integer boatStatus;
private Integer legNumber; private Integer legNumber = 0;
private Integer position = 0;
private Integer penaltiesAwarded; private Integer penaltiesAwarded;
private Integer penaltiesServed; private Integer penaltiesServed;
private Long estimateTimeAtFinish; private Long estimateTimeAtFinish;
private String position;
private Double lat; private Double lat;
private Double lon; private Double lon;
private Float heading; private Double heading;
private double velocity; private double velocity;
private Long timeTillNext; private Long timeTillNext;
private Long markRoundTime; private Long markRoundTime;
@@ -46,31 +44,7 @@ public class Yacht {
private Mark lastMarkRounded; private Mark lastMarkRounded;
private Mark nextMark; private Mark nextMark;
public Boat(String boatType, Integer sourceID, String hullID, String shortName,
/**
* Used in EventTest and RaceTest.
*
* @param boatName Create a yacht object with name.
*/
public Yacht(String boatName) {
this.boatName = boatName;
}
/**
* Used in BoatGroupTest.
*
* @param boatName The name of the team sailing the boat
* @param boatVelocity The speed of the boat in meters/second
* @param shortName A shorter version of the teams name
*/
public Yacht(String boatName, double boatVelocity, String shortName, int id) {
this.boatName = boatName;
this.velocity = boatVelocity;
this.shortName = shortName;
this.sourceID = id;
}
public Yacht(String boatType, Integer sourceID, String hullID, String shortName,
String boatName, String country) { String boatName, String country) {
this.boatType = boatType; this.boatType = boatType;
this.sourceID = sourceID; this.sourceID = sourceID;
@@ -78,7 +52,6 @@ public class Yacht {
this.shortName = shortName; this.shortName = shortName;
this.boatName = boatName; this.boatName = boatName;
this.country = country; this.country = country;
this.position = "-";
} }
public String getBoatType() { public String getBoatType() {
@@ -118,28 +91,9 @@ public class Yacht {
} }
public void setLegNumber(Integer legNumber) { public void setLegNumber(Integer legNumber) {
if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus(sourceID)) {
RaceViewController.updateYachtPositionSparkline(this, legNumber);
}
this.legNumber = legNumber; this.legNumber = legNumber;
} }
public Integer getPenaltiesAwarded() {
return penaltiesAwarded;
}
public void setPenaltiesAwarded(Integer penaltiesAwarded) {
this.penaltiesAwarded = penaltiesAwarded;
}
public Integer getPenaltiesServed() {
return penaltiesServed;
}
public void setPenaltiesServed(Integer penaltiesServed) {
this.penaltiesServed = penaltiesServed;
}
public void setEstimateTimeAtNextMark(Long estimateTimeAtNextMark) { public void setEstimateTimeAtNextMark(Long estimateTimeAtNextMark) {
timeTillNext = estimateTimeAtNextMark; timeTillNext = estimateTimeAtNextMark;
} }
@@ -153,11 +107,11 @@ public class Yacht {
this.estimateTimeAtFinish = estimateTimeAtFinish; this.estimateTimeAtFinish = estimateTimeAtFinish;
} }
public String getPosition() { public Integer getPosition() {
return position; return position;
} }
public void setPosition(String position) { public void setPosition(Integer position) {
this.position = position; this.position = position;
} }
@@ -222,11 +176,11 @@ public class Yacht {
this.lon = lon; this.lon = lon;
} }
public Float getHeading() { public Double getHeading() {
return heading; return heading;
} }
public void setHeading(Float heading) { public void setHeading(Double heading) {
this.heading = heading; this.heading = heading;
} }
+1 -1
View File
@@ -3,7 +3,7 @@ package seng302.model;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
/** /**
* Enum for randomly generating colours. * Enum for generating colours.
*/ */
public enum Colors { public enum Colors {
RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE; RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE;
+37
View File
@@ -0,0 +1,37 @@
package seng302.model;
import org.w3c.dom.Node;
/**
* Stores the data for the cornering of a mark.
*/
public class Corner {
private Integer seqID;
private Integer compoundMarkID;
private String rounding;
private Integer zoneSize;
public Corner(Integer seqID, Integer compoundMarkID, String rounding, Integer zoneSize) {
this.seqID = seqID;
this.compoundMarkID = compoundMarkID;
this.rounding = rounding;
this.zoneSize = zoneSize;
}
public Integer getSeqID() {
return seqID;
}
public Integer getCompoundMarkID() {
return compoundMarkID;
}
public String getRounding() {
return rounding;
}
public Integer getZoneSize() {
return zoneSize;
}
}
+29
View File
@@ -0,0 +1,29 @@
package seng302.model;
/**
* Stores data on the border of a race
*/
public class Limit {
private Integer seqID;
private Double lat;
private Double lng;
public Limit(Integer seqID, Double lat, Double lng) {
this.seqID = seqID;
this.lat = lat;
this.lng = lng;
}
public Integer getSeqID() {
return seqID;
}
public Double getLat() {
return lat;
}
public Double getLng() {
return lng;
}
}
+3 -3
View File
@@ -9,7 +9,7 @@ import java.net.Socket;
public class Player { public class Player {
private Socket socket; private Socket socket;
private Yacht yacht; private Boat boat;
private Integer lastMarkPassed; private Integer lastMarkPassed;
@@ -29,8 +29,8 @@ public class Player {
this.lastMarkPassed = lastMarkPassed; this.lastMarkPassed = lastMarkPassed;
} }
public Yacht getYacht() { public Boat getYacht() {
return yacht; return boat;
} }
@Override @Override
@@ -0,0 +1,16 @@
package seng302.model;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleLongProperty;
/**
* Class for storing race data that does not relate to specific vessels or marks such as time or wind
*/
public class RaceStatus {
double windSpeed;
double windDirection;
long raceTime;
}
+2 -2
View File
@@ -10,7 +10,7 @@ public abstract class Mark {
private MarkType markType; private MarkType markType;
private double latitude; private double latitude;
private double longitude; private double longitude;
private long id; private int id;
private int compoundMarkID; private int compoundMarkID;
/** /**
@@ -134,7 +134,7 @@ public abstract class Mark {
return longitude; return longitude;
} }
public long getId() { public int getId() {
return id; return id;
} }
@@ -1,649 +0,0 @@
package seng302.model.stream;
import java.io.IOException;
import java.io.StringReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.PriorityBlockingQueue;
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.Yacht;
import seng302.model.mark.Mark;
import seng302.model.stream.packets.BoatPositionPacket;
import seng302.model.stream.packets.StreamPacket;
/**
* The purpose of this class is to take in the stream of divided packets so they can be read
* and parsed in by turning the byte arrays into useful data. There are two public static hashmaps
* that are threadsafe so the visualiser can always access the latest speed and position available
* Created by kre39 on 23/04/17.
*/
public class StreamParser{
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markLocations = new ConcurrentHashMap<>();
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatLocations = new ConcurrentHashMap<>();
private static boolean newRaceXmlReceived = false;
private static boolean raceStarted = false;
private static XMLParser xmlObject;
private static boolean raceFinished = false;
private static boolean streamStatus = false;
private static long timeSinceStart = -1;
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
private static Map<Integer, Yacht> boatsPos = new ConcurrentSkipListMap<>();
private static double windDirection = 0;
private static Double windSpeed = 0d;
private static Long currentTimeLong;
private static String currentTimeString;
//CONVERSION CONSTANTS
private static final Double MS_TO_KNOTS = 1.94384;
/**
* Used to initialise the thread name and stream parser object so a thread can be executed
*/
public StreamParser() {
}
/**
* Looks at the type of the packet then sends it to the appropriate parser to extract the
* specific data associated with that packet type
*
* @param packet the packet to be looked at and processed
*/
public static void parsePacket(StreamPacket packet) {
try {
switch (packet.getType()) {
case HEARTBEAT:
extractHeartBeat(packet);
break;
case RACE_STATUS:
extractRaceStatus(packet);
break;
case DISPLAY_TEXT_MESSAGE:
extractDisplayMessage(packet);
break;
case XML_MESSAGE:
newRaceXmlReceived = true;
extractXmlMessage(packet);
break;
case RACE_START_STATUS:
extractRaceStartStatus(packet);
break;
case YACHT_EVENT_CODE:
extractYachtEventCode(packet);
break;
case YACHT_ACTION_CODE:
extractYachtActionCode(packet);
break;
case CHATTER_TEXT:
extractChatterText(packet);
break;
case BOAT_LOCATION:
extractBoatLocation(packet);
break;
case MARK_ROUNDING:
extractMarkRounding(packet);
break;
case COURSE_WIND:
extractCourseWind(packet);
break;
case AVG_WIND:
extractAvgWind(packet);
break;
case BOAT_ACTION:
extractBoatAction(packet);
break;
}
} catch (NullPointerException e) {
System.out.println("Error parsing packet");
e.printStackTrace();
}
}
/**
* Extracts the seq num used in the heartbeat packet
*
* @param packet Packet parsed in to use the payload
*/
private static void extractHeartBeat(StreamPacket packet) {
long heartbeat = bytesToLong(packet.getPayload());
System.out.println("heartbeat = " + heartbeat);
}
private static String getTimeZoneString() {
Integer offset = xmlObject.getRegattaXML().getUtcOffset();
StringBuilder utcOffset = new StringBuilder();
utcOffset.append("GMT");
if (offset > 0) {
utcOffset.append("+");
utcOffset.append(offset);
} else if (offset < 0) {
utcOffset.append("-");
utcOffset.append(offset);
}
return utcOffset.toString();
}
/**
* 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
*/
private static void extractRaceStatus(StreamPacket packet) {
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));
currentTimeLong = currentTime;
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
if (xmlObject.getRegattaXML() != null) {
format.setTimeZone(TimeZone.getTimeZone(getTimeZoneString()));
currentTimeString = format.format((new Date(currentTime)).getTime());
}
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;
}
double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc...
windDirection = windDir / windDirFactor;
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
int noBoats = payload[22];
int raceType = payload[23];
for (int i = 0; i < noBoats; i++) {
long boatStatusSourceID = bytesToLong(
Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20)));
Yacht boat = boats.get((int) boatStatusSourceID);
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.setEstimateTimeAtNextMark(estTimeAtNextMark);
Long estTimeAtFinish = bytesToLong(
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
boat.setEstimateTimeAtFinish(estTimeAtFinish);
// boatsPos.put(estTimeAtFinish, boat);
// String boatStatus = "SourceID: " + boatStatusSourceID;
// boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)];
// boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)];
// boatStatus += "\nPenaltiesAwarded: " + (int)payload[29 + (i * 20)];
// boatStatus += "\nPenaltiesServed: " + (int)payload[30 + (i * 20)];
// boatStatus += "\nEstTimeAtNextMark: " + bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20)));
// boatStatus += "\nEstTimeAtFinish: " + bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20)));
// boatStatuses.add(boatStatus);
}
// if (isRaceStarted()) {
// int pos = 1;
// for (Yacht yacht : boatsPos.values()) {
// yacht.setPosition(String.valueOf(pos));
// pos++;
// }
// } else {
// for (Yacht yacht : boatsPos.values()) {
// yacht.setPosition("-");
// }
// }
}
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);
}
}
/**
* Used to extract the messages passed through with the display message packet
*
* @param packet Packet parsed in to use the payload
*/
private static void extractDisplayMessage(StreamPacket packet) {
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);
String messageText = new String(messageTextBytes);
totalLen += 2 + textLength;
}
}
/**
* Used to read in the xml data. Will call the specific methods to create the course and boats
*
* @param packet Packet parsed in to use the payload
*/
private static void extractXmlMessage(StreamPacket packet) {
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 = null;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xmlMessage)));
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
xmlObject.constructXML(doc, messageType);
if (messageType == 7) { //7 is the boat XML
boats = xmlObject.getBoatXML().getCompetingBoats();
}
if (messageType == 6) { //6 is race info xml
newRaceXmlReceived = true;
}
}
/**
* Extracts the race start status from the packet, currently is unused within the app but
* is here for potential future use
*
* @param packet Packet parsed in to use the payload
*/
private static void extractRaceStartStatus(StreamPacket packet) {
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];
}
/**
* When a yacht event occurs this will parse the byte array to retrieve the necessary info,
* currently unused
*
* @param packet Packet parsed in to use the payload
*/
private static void extractYachtEventCode(StreamPacket packet) {
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];
}
/**
* When a yacht action occurs this will parse the parse the byte array to retrieve the necessary
* info, currently unused
*
* @param packet Packet parsed in to use the payload
*/
private static void extractYachtActionCode(StreamPacket packet) {
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];
}
/**
* Strips the message from the chatter text type packets, currently the message is unused
*
* @param packet Packet parsed in to use the payload
*/
private static void extractChatterText(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int messageType = payload[1];
int length = payload[2];
String message = new String(Arrays.copyOfRange(payload, 3, 3 + length));
System.out.println(message);
}
/**
* Used to breakdown the boatlocation packets so the boat coordinates, id and groundspeed are
* all used All the other extra data is still being read and translated however is unused.
*
* @param packet Packet parsed in to use the payload
*/
private static void extractBoatLocation(StreamPacket packet) {
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));
long heading = bytesToLong(Arrays.copyOfRange(payload, 28, 30));
double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0;
//type 1 is a racing yacht and type 3 is a mark, needed for updating positions of the mark and boat
if (deviceType == 1){
Yacht boat = boats.get((int) boatId);
boat.setVelocity(groundSpeed);
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed);
//add a new priority que to the boatLocations HashMap
if (!boatLocations.containsKey(boatId)) {
boatLocations.put(boatId,
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
@Override
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
return (int) (p1.getTimeValid() - p2.getTimeValid());
}
}));
}
boatLocations.get(boatId).put(boatPacket);
} else if (deviceType == 3) {
BoatPositionPacket markPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
heading, groundSpeed);
//add a new priority que to the boatLocations HashMap
if (!markLocations.containsKey(boatId)) {
markLocations.put(boatId,
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
@Override
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
return (int) (p1.getTimeValid() - p2.getTimeValid());
}
}));
}
markLocations.get(boatId).put(markPacket);
}
}
/**
* This packet type is received when a mark or gate is rounded by a boat
*
* @param packet The packet containing the payload
*/
private static void extractMarkRounding(StreamPacket packet) {
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];
// assign mark rounding time to boat
boats.get((int)subjectId).setMarkRoundingTime(timeStamp);
for (Mark mark : xmlObject.getRaceXML().getAllCompoundMarks()) {
if (mark.getCompoundMarkID() == markId) {
boats.get((int)subjectId).setLastMarkRounded(mark);
}
}
Long[] array = new Long[]{subjectId, timeStamp, (long) markId};
}
/**
* This packet type contains periodic data on the state of the wind
*
* @param packet The packet containing the payload
*/
private static void extractCourseWind(StreamPacket packet) {
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);
}
}
/**
* This packet conatins the average wind to ground speed
*
* @param packet The packet containing the paylaod
*/
private static void extractAvgWind(StreamPacket packet) {
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));
}
private 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
*/
private 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;
}
/**
* returns false if race not started, true otherwise
*
* @return race started status
*/
public static boolean isRaceStarted() {
return raceStarted;
}
/**
* returns false if stream not connected, true otherwise
*
* @return stream started status
*/
public static boolean isStreamStatus() {
return streamStatus;
}
/**
* returns race timer
*
* @return race timer in long
*/
public static long getTimeSinceStart() {
return timeSinceStart;
}
/**
* return false if race not finished, true otherwise
*
* @return race finished status
*/
public static boolean isRaceFinished() {
return raceFinished;
}
/**
* return a map of boats with sourceID and the boat
*
* @return map of boats
*/
public static Map<Integer, Yacht> getBoats() {
return boats;
}
/**
* returns the latest updated object from xml parser
*
* @return the latest xml object
*/
public static XMLParser getXmlObject() {
return xmlObject;
}
/**
* returns the wind direction in degrees
*
* @return a double wind direction value
*/
public static double getWindDirection() {
return windDirection;
}
/**
* Returns the wind speed in knots
* @return A double indicating the wind speed in knots
*/
public static Double getWindSpeed() {
return windSpeed;
}
/**
* returns stream time in formatted string format
*
* @return String of stream time
*/
public static String getCurrentTimeString() {
return currentTimeString;
}
/**
* used in boat position since tree map can sort position efficiently.
*
* @return a map of time to finish and boat.
*/
public static Map<Integer, Yacht> getBoatsPos() {
return boatsPos;
}
/**
* returns current time in stream in long
*
* @return a long value of current time
*/
public static Long getCurrentTimeLong() {
return currentTimeLong;
}
/**
* Used to check if a new un-processed xml has been found, if so will return true before
* toggling off so that the next check will return false.
*
* @return the status of if new xml has been received
*/
public static boolean isNewRaceXmlReceived() {
if (newRaceXmlReceived) {
newRaceXmlReceived = false;
return true;
} else {
return false;
}
}
}
@@ -116,25 +116,6 @@ public class StreamReceiver extends Thread {
} }
} }
/**
* takes an array of up to 7 bytes in little endian format and
* returns a positive long constructed from the input bytes
*
* @return a positive long if there is less than 8 bytes -1 otherwise
*/
private 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;
}
public static void main(String[] args) { public static void main(String[] args) {
StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1"); StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1");
@@ -1,610 +0,0 @@
package seng302.model.stream;
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.mark.GateMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkType;
import seng302.model.mark.SingleMark;
/**
* Class to create an XML object from the XML Packet Messages.
*
* Example usage:
*
* Document doc; // some xml document
* Integer xmlMessageType; // an Integer of value 5, 6, 7
*
* xmlP = new XMLParser(doc, xmlMessageType);
* RegattaXMLObject rXmlObj = xmlP.createRegattaXML(); // creates a regattaXML object.
*/
public class XMLParser {
private Document xmlDoc;
private RaceXMLObject raceXML;
private RegattaXMLObject regattaXML;
private BoatXMLObject boatXML;
public XMLParser() {
}
/**
* Constructor for XMLParser
*
* @param doc Document to create XML object.
* @param messageType Defines if a message is a RegattaXML(5), RaceXML(6), BoatXML(7).
*/
public void constructXML(Document doc, Integer messageType) {
this.xmlDoc = doc;
switch (messageType) {
case 5:
regattaXML = new RegattaXMLObject(this.xmlDoc);
break;
case 6:
raceXML = new RaceXMLObject(this.xmlDoc);
break;
case 7:
boatXML = new BoatXMLObject(this.xmlDoc);
break;
}
}
public RaceXMLObject getRaceXML() {
return raceXML;
}
public RegattaXMLObject getRegattaXML() {
return regattaXML;
}
public BoatXMLObject getBoatXML() {
return boatXML;
}
/**
* 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;
}
}
public class RegattaXMLObject {
//Regatta Info
private Integer regattaID;
private String regattaName;
private String courseName;
private Double centralLat;
private Double centralLng;
private Integer utcOffset;
/**
* Constructor for a RegattaXMLObject.
* Takes the information from a Document object and creates a more usable format.
*
* @param doc XML Document Object
*/
RegattaXMLObject(Document doc) {
Element docEle = doc.getDocumentElement();
this.regattaID = getElementInt(docEle, "RegattaID");
this.regattaName = getElementString(docEle, "RegattaName");
this.courseName = getElementString(docEle, "CourseName");
this.centralLat = getElementDouble(docEle, "CentralLatitude");
this.centralLng = getElementDouble(docEle, "CentralLongitude");
this.utcOffset = getElementInt(docEle, "UtcOffset");
}
public Integer getRegattaID() {
return regattaID;
}
public String getRegattaName() {
return regattaName;
}
public String getCourseName() {
return courseName;
}
public Double getCentralLat() {
return centralLat;
}
public Double getCentralLng() {
return centralLng;
}
public Integer getUtcOffset() {
return utcOffset;
}
}
public class RaceXMLObject {
// Race Info
private Integer raceID;
private String raceType;
private String creationTimeDate; // XML Creation Time
//Race Start Details
private String raceStartTime;
private Boolean postponeStatus;
//Non atomic race attributes
private ArrayList<Participant> participants;
private ArrayList<Mark> allMarks;
private ArrayList<Mark> nonDuplicateMarks;
private ArrayList<Corner> compoundMarkSequence;
private ArrayList<Limit> courseLimit;
// ensures there's no duplicate marks.
private List<Long> seenSourceIDs = new ArrayList<Long>();
/**
* Constructor for a RaceXMLObject.
* Takes the information from a Document object and creates a more usable format.
*
* @param doc XML Document Object
*/
RaceXMLObject(Document doc) {
Element docEle = doc.getDocumentElement();
//Atomic and Semi-Atomic Elements
this.raceID = getElementInt(docEle, "RaceID");
this.raceType = getElementString(docEle, "RaceType");
this.creationTimeDate = getElementString(docEle, "CreationTimeDate");
Node raceStart = docEle.getElementsByTagName("RaceStartTime").item(0);
this.raceStartTime = getNodeAttributeString(raceStart, "Start");
this.postponeStatus = Boolean
.parseBoolean(getNodeAttributeString(raceStart, "Postpone"));
//Participants
participants = new ArrayList<>();
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
for (int i = 0; i < pList.getLength(); i++) {
Node pNode = pList.item(i);
String entry;
if (pNode.getNodeName().equals("Yacht")) {
Integer sourceID = getNodeAttributeInt(pNode, "SourceID");
if (pNode.getAttributes().getLength() == 2) {
entry = getNodeAttributeString(pNode, "Entry");
} else {
entry = null;
}
Participant pa = new Participant(sourceID, entry);
participants.add(pa);
}
}
//Course
allMarks = new ArrayList<>();
nonDuplicateMarks = new ArrayList<>();
createCompoundMarks(docEle);
//Course Mark Sequence
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")) {
Corner corner = new Corner(cornerNode);
compoundMarkSequence.add(corner);
}
}
//Course Limits
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")) {
Limit limit = new Limit(limitNode);
courseLimit.add(limit);
}
}
}
private void createCompoundMarks(Element docEle) {
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")) {
createAndAddMark(cMarkNode);
}
}
}
private void createAndAddMark(Node compoundMark) {
Boolean markSeen = false;
List<SingleMark> marksList = new ArrayList<>();
Integer compoundMarkID = getNodeAttributeInt(compoundMark, "CompoundMarkID");
String cMarkName = 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 = getNodeAttributeInt(markNode, "SourceID");
String markName = getNodeAttributeString(markNode, "Name");
Double targetLat = getNodeAttributeDouble(markNode, "TargetLat");
Double targetLng = getNodeAttributeDouble(markNode, "TargetLng");
SingleMark mark = new SingleMark(markName, targetLat, targetLng, sourceID, compoundMarkID);
marksList.add(mark);
}
}
for (SingleMark mark : marksList) {
if (seenSourceIDs.contains(mark.getId())) {
markSeen = true;
} else {
seenSourceIDs.add(mark.getId());
}
}
if (marksList.size() == 1) {
if (!markSeen) {
nonDuplicateMarks.add(marksList.get(0));
}
allMarks.add(marksList.get(0));
} else if (marksList.size() == 2) {
GateMark thisGateMark = new GateMark(cMarkName, MarkType.OPEN_GATE, marksList.get(0),
marksList.get(1), marksList.get(0).getLatitude(),
marksList.get(0).getLongitude(), compoundMarkID);
if(!markSeen) {
nonDuplicateMarks.add(thisGateMark);
}
allMarks.add(thisGateMark);
}
}
public Integer getRaceID() {
return raceID;
}
public String getRaceType() {
return raceType;
}
public String getCreationTimeDate() {
return creationTimeDate;
}
public String getRaceStartTime() {
return raceStartTime;
}
public Boolean getPostponeStatus() {
return postponeStatus;
}
public ArrayList<Participant> getParticipants() {
return participants;
}
/**
* @return Returns ALL compound marks as stated in the RaceXML (INCLUDING DUPLICATE MARKS)
*/
public List<Mark> getAllCompoundMarks() {
return allMarks;
}
/**
* @return Returns Marks from the race XML without any duplicates
*/
public List<Mark> getNonDupCompoundMarks() {
return nonDuplicateMarks;
}
public ArrayList<Corner> getCompoundMarkSequence() {
return compoundMarkSequence;
}
public ArrayList<Limit> getCourseLimit() {
return courseLimit;
}
public class Participant {
Integer sourceID;
String entry;
Participant(Integer sourceID, String entry) {
this.sourceID = sourceID;
this.entry = entry;
}
public Integer getsourceID() {
return sourceID;
}
public String getEntry() {
return entry;
}
}
public class Corner {
private Integer seqID;
private Integer compoundMarkID;
private String rounding;
private Integer zoneSize;
Corner(Node cornerNode) {
this.seqID = getNodeAttributeInt(cornerNode, "SeqID");
this.compoundMarkID = getNodeAttributeInt(cornerNode, "CompoundMarkID");
this.rounding = getNodeAttributeString(cornerNode, "Rounding");
this.zoneSize = getNodeAttributeInt(cornerNode, "ZoneSize");
}
public Integer getSeqID() {
return seqID;
}
public Integer getCompoundMarkID() {
return compoundMarkID;
}
public String getRounding() {
return rounding;
}
public Integer getZoneSize() {
return zoneSize;
}
}
public class Limit {
private Integer seqID;
private Double lat;
private Double lng;
Limit(Node limitNode) {
this.seqID = getNodeAttributeInt(limitNode, "SeqID");
this.lat = getNodeAttributeDouble(limitNode, "Lat");
this.lng = getNodeAttributeDouble(limitNode, "Lon");
}
public Integer getSeqID() {
return seqID;
}
public Double getLat() {
return lat;
}
public Double getLng() {
return lng;
}
}
}
public class BoatXMLObject {
private String lastModified;
private Integer version;
//Settings for the boat type in the race. This may end up having to be reworked if multiple boat types compete.
private String boatType;
private Double boatLength;
private Double hullLength;
private Double markZoneSize;
private Double courseZoneSize;
private ArrayList<Double> zoneLimits;// will only contain 5 elements. Limits 1-5
//Boats
ArrayList<Yacht> boats;
//Competing boats
Map<Integer, Yacht> competingBoats = new HashMap<>();
/**
* Constructor for a BoatXMLObject.
* Takes the information from a Document object and creates a more usable format.
*
* @param doc XML Document Object
*/
BoatXMLObject(Document doc) {
Element docEle = doc.getDocumentElement();
this.lastModified = getElementString(docEle, "Modified");
this.version = getElementInt(docEle, "Version");
NodeList settingsList = docEle.getElementsByTagName("Settings").item(0).getChildNodes();
this.boatType = getNodeAttributeString(settingsList.item(1), "Type");
this.boatLength = getNodeAttributeDouble(settingsList.item(3), "BoatLength");
this.hullLength = getNodeAttributeDouble(settingsList.item(3), "HullLength");
this.markZoneSize = getNodeAttributeDouble(settingsList.item(5), "MarkZoneSize");
this.courseZoneSize = getNodeAttributeDouble(settingsList.item(5), "CourseZoneSize");
Node zoneLimitsList = settingsList.item(7);
this.zoneLimits = new ArrayList<>();
for (int i = 0; i < zoneLimitsList.getAttributes().getLength(); i++) {
String tag = String.format("Limit%d", i + 1);
this.zoneLimits.add(getNodeAttributeDouble(zoneLimitsList, tag));
}
this.boats = new ArrayList<>();
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 boat = new Yacht(getNodeAttributeString(currentBoat, "Type"),
getNodeAttributeInt(currentBoat, "SourceID"),
getNodeAttributeString(currentBoat, "HullNum"),
getNodeAttributeString(currentBoat, "ShortName"),
getNodeAttributeString(currentBoat, "BoatName"),
getNodeAttributeString(currentBoat, "Country"));
this.boats.add(boat);
if (boat.getBoatType().equals("Yacht")) {
competingBoats.put(boat.getSourceID(), boat);
}
}
}
}
public String getLastModified() {
return lastModified;
}
public Integer getVersion() {
return version;
}
public String getBoatType() {
return boatType;
}
public Double getBoatLength() {
return boatLength;
}
public Double getHullLength() {
return hullLength;
}
public Double getMarkZoneSize() {
return markZoneSize;
}
public Double getCourseZoneSize() {
return courseZoneSize;
}
public ArrayList<Double> getZoneLimits() {
return zoneLimits;
}
public ArrayList<Yacht> getBoats() {
return boats;
}
public Map<Integer, Yacht> getCompetingBoats() {
return competingBoats;
}
}
}
@@ -1,39 +0,0 @@
package seng302.model.stream.packets;
public class BoatPositionPacket {
private long boatId;
private long timeValid;
private double lat;
private double lon;
private double heading;
private double groundSpeed;
public BoatPositionPacket(long boatId, long timeValid, double lat, double lon, double heading, double groundSpeed) {
this.boatId = boatId;
this.timeValid = timeValid;
this.lat = lat;
this.lon = lon;
this.heading = heading;
this.groundSpeed = groundSpeed;
}
public long getTimeValid() {
return timeValid;
}
public double getLat() {
return lat;
}
public double getLon() {
return lon;
}
public double getHeading() {
return heading;
}
public double getGroundSpeed() {
return groundSpeed;
}
}
@@ -1,7 +1,5 @@
package seng302.model.stream.packets; package seng302.model.stream.packets;
import java.lang.reflect.Type;
/** /**
* Created by Kusal on 4/24/2017. * Created by Kusal on 4/24/2017.
*/ */
@@ -9,7 +7,9 @@ public enum PacketType {
HEARTBEAT, HEARTBEAT,
RACE_STATUS, RACE_STATUS,
DISPLAY_TEXT_MESSAGE, DISPLAY_TEXT_MESSAGE,
XML_MESSAGE, RACE_XML,
REGATTA_XML,
BOAT_XML,
RACE_START_STATUS, RACE_START_STATUS,
YACHT_EVENT_CODE, YACHT_EVENT_CODE,
YACHT_ACTION_CODE, YACHT_ACTION_CODE,
@@ -21,7 +21,7 @@ public enum PacketType {
BOAT_ACTION, BOAT_ACTION,
OTHER; OTHER;
public static PacketType assignPacketType(int packetType){ public static PacketType assignPacketType(int packetType, byte[] payload){
switch(packetType){ switch(packetType){
case 1: case 1:
return HEARTBEAT; return HEARTBEAT;
@@ -30,7 +30,14 @@ public enum PacketType {
case 20: case 20:
return DISPLAY_TEXT_MESSAGE; return DISPLAY_TEXT_MESSAGE;
case 26: case 26:
return XML_MESSAGE; switch (payload[9]) { //The type of XML message
case 5:
return REGATTA_XML;
case 6:
return RACE_XML;
case 7:
return BOAT_XML;
}
case 27: case 27:
return RACE_START_STATUS; return RACE_START_STATUS;
case 29: case 29:
@@ -13,7 +13,7 @@ public class StreamPacket {
private byte[] payload; private byte[] payload;
public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) { public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
this.type = PacketType.assignPacketType(type); this.type = PacketType.assignPacketType(type, payload);
this.messageLength = messageLength; this.messageLength = messageLength;
this.timeStamp = timeStamp; this.timeStamp = timeStamp;
this.payload = payload; this.payload = payload;
@@ -0,0 +1,36 @@
package seng302.model.stream.parsers;
/**
* Simple data wrapper for mark rounding data packet.
*/
public class MarkRoundingData {
private int boatId;
private int markId;
private int roundingSide;
private long timeStamp;
public MarkRoundingData(int boatId, int markId, int roundingSide, long timeStamp) {
this.boatId = boatId;
this.markId = markId;
this.roundingSide = roundingSide;
this.timeStamp = timeStamp;
}
public int getBoatId() {
return boatId;
}
public int getMarkId() {
return markId;
}
public int getRoundingSide() {
return roundingSide;
}
public long getTimeStamp() {
return timeStamp;
}
}
@@ -0,0 +1,50 @@
package seng302.model.stream.parsers;
public class PositionUpdateData {
public enum DeviceType {
YACHT_TYPE,
MARK_TYPE
}
private int deviceId;
private DeviceType type;
private double lat;
private double lon;
private double heading;
private double groundSpeed;
public PositionUpdateData(int deviceId, DeviceType type, double lat, double lon,
double heading, double groundSpeed) {
this.deviceId = deviceId;
this.type = type;
this.lat = lat;
this.lon = lon;
this.heading = heading;
this.groundSpeed = groundSpeed;
}
public int getDeviceId() {
return deviceId;
}
public DeviceType getType() {
return type;
}
public double getLat() {
return lat;
}
public double getLon() {
return lon;
}
public double getHeading() {
return heading;
}
public double getGroundSpeed() {
return groundSpeed;
}
}
@@ -0,0 +1,35 @@
package seng302.model.stream.parsers;
/**
* Class for storing data parsed from race start status packet
*/
public class 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;
this.timeStamp = timeStamp;
}
public long getRaceId() {
return raceId;
}
public long getRaceStartTime() {
return raceStartTime;
}
public int getNotificationType() {
return notificationType;
}
public long getTimeStamp() {
return timeStamp;
}
}
@@ -0,0 +1,65 @@
package seng302.model.stream.parsers;
import java.util.ArrayList;
import java.util.List;
/**
* Stores parsed data from race status packets
*/
public class RaceStatusData {
//CONVERSION CONSTANTS
private static final double WIND_DIR_FACTOR = 0x4000 / 90; //0x4000 is 90 degrees
private static final double MS_TO_KNOTS = 1.94384;
private double windDirection;
private double windSpeed;
private boolean raceStarted = false;
private long currentTime;
private long expectedStartTime;
List<long[]> boatData = new ArrayList<>();
public RaceStatusData(
long windDir, long rawWindSpeed, int raceStatus, long currentTime, long expectedStartTime) {
windDirection = windDir / WIND_DIR_FACTOR;
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
raceStarted = raceStatus == 3;
this.currentTime = currentTime;
this.expectedStartTime = expectedStartTime;
}
public void addBoatData (long boatID, long estTimeToNextMark, long estTimeToFinish, int leg) {
boatData.add(new long[] {boatID, estTimeToNextMark, estTimeToFinish, leg});
}
public double getWindDirection() {
return windDirection;
}
public double getWindSpeed() {
return windSpeed;
}
public boolean isRaceStarted() {
return raceStarted;
}
public long getCurrentTime() {
return currentTime;
}
public long getExpectedStartTime() {
return expectedStartTime;
}
/**
* Returns the data for boats collected form race status packets.
*
* @return A list of boat data. Boat data is in the form
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber].
*/
public List<long[]> getBoatData () {
return boatData;
}
}
@@ -0,0 +1,440 @@
package seng302.model.stream.parsers;
import seng302.model.stream.parsers.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;
import seng302.model.stream.parsers.xml.RegattaXMLData;
/**
* 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;
}
private static String getTimeZoneString(RegattaXMLData regattaXML) {
Integer offset = regattaXML.getUtcOffset();
StringBuilder utcOffset = new StringBuilder();
utcOffset.append("GMT");
if (offset > 0) {
utcOffset.append("+");
utcOffset.append(offset);
} else if (offset < 0) {
utcOffset.append("-");
utcOffset.append(offset);
}
return utcOffset.toString();
}
/**
* 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.setEstimateTimeAtNextMark(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;
}
}
@@ -0,0 +1,48 @@
package seng302.model.stream.parsers.xml;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import seng302.model.Corner;
import seng302.model.Limit;
import seng302.model.mark.Mark;
/**
* Process a Document object containing race data in XML format and stores the data.
*/
public class RaceXMLData {
private List<Integer> participants;
private Map<Integer, Mark> compoundMarks;
private List<Corner> markSequence;
private List<Limit> courseLimit;
private Map<Integer, Mark> individualMarks;
RaceXMLData(List<Integer> participants, List<Mark> compoundMarks, List<Corner> markSequence,
List<Limit> courseLimit) {
this.participants = participants;
this.markSequence = markSequence;
this.courseLimit = courseLimit;
this.compoundMarks = new HashMap<>();
for (Mark mark : compoundMarks)
this.compoundMarks.put(mark.getId(), mark);
for (Mark mark : compoundMarks) {
}
}
public List<Integer> getParticipants() {
return participants;
}
public Map<Integer, Mark> getCompoundMarks() {
return compoundMarks;
}
public List<Corner> getMarkSequence() {
return markSequence;
}
public List<Limit> getCourseLimit() {
return courseLimit;
}
}
@@ -0,0 +1,49 @@
package seng302.model.stream.parsers.xml;
/**
* Stores data from regatta xml packet.
*/
public class RegattaXMLData {
//Regatta Info
private Integer regattaID;
private String regattaName;
private String courseName;
private Double centralLat;
private Double centralLng;
private Integer utcOffset;
RegattaXMLData (Integer regattaID, String regattaName, String courseName,
Double centralLat, Double centralLng, Integer utcOffset) {
this.regattaID = regattaID;
this.regattaName = regattaName;
this.courseName = courseName;
this.centralLat = centralLat;
this.centralLng = centralLng;
this.utcOffset = utcOffset;
}
public Integer getRegattaID() {
return regattaID;
}
public String getRegattaName() {
return regattaName;
}
public String getCourseName() {
return courseName;
}
public Double getCentralLat() {
return centralLat;
}
public Double getCentralLng() {
return centralLng;
}
public Integer getUtcOffset() {
return utcOffset;
}
}
@@ -0,0 +1,290 @@
package seng302.model.stream.parsers.xml;
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.Boat;
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, Boat> parseBoats(Document doc){
Map<Integer, Boat> 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);
Boat boat = new Boat(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 (boat.getBoatType().equals("Yacht")) {
competingBoats.put(boat.getSourceID(), boat);
}
}
}
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
);
}
}
}
@@ -0,0 +1,11 @@
package seng302.visualiser;
import seng302.model.stream.packets.StreamPacket;
/**
* Created by cir27 on 21/07/17.
*/
@FunctionalInterface
public interface ClientSocketListener {
void newPacket(StreamPacket packet);
}
@@ -8,18 +8,10 @@ import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import java.util.zip.Checksum; import java.util.zip.Checksum;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import seng302.model.stream.StreamParser;
import seng302.model.stream.XMLParser;
import seng302.model.stream.packets.BoatPositionPacket;
import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.packets.StreamPacket;
import seng302.server.messages.BoatActionMessage; import seng302.server.messages.BoatActionMessage;
import seng302.server.messages.Message; import seng302.server.messages.Message;
@@ -29,7 +21,7 @@ import seng302.server.messages.Message;
*/ */
public class ClientToServerThread extends Thread { public class ClientToServerThread extends Thread {
private Queue<StreamPacket> streamPackets = new ConcurrentLinkedQueue<>(); private Queue<StreamPacket> streamPackets = new ConcurrentLinkedQueue<>();
private List<ListChangeListener<Queue<StreamPacket>>> boatPacketListeners = new ArrayList<>(); private List<ClientSocketListener> listeners = new ArrayList<>();
private Socket socket; private Socket socket;
private InputStream is; private InputStream is;
@@ -78,10 +70,9 @@ public class ClientToServerThread extends Thread {
long computedCrc = checksum.getValue(); long computedCrc = checksum.getValue();
long packetCrc = Message.bytesToLong(getBytes(4)); long packetCrc = Message.bytesToLong(getBytes(4));
if (computedCrc == packetCrc) { if (computedCrc == packetCrc) {
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); // streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
for (ListChangeListener cl : boatPacketListeners) { for (ClientSocketListener csl : listeners)
cl.onChanged(); csl.newPacket(new StreamPacket(type, payloadLength, timeStamp, payload));
}
} else { } else {
System.err.println("Packet has been dropped"); System.err.println("Packet has been dropped");
} }
@@ -114,12 +105,12 @@ public class ClientToServerThread extends Thread {
} }
} }
public void addStreamObserver (ListChangeListener<Queue<StreamPacket>> listChangeListener) { public void addStreamObserver (ClientSocketListener streamListener) {
boatPacketListeners.add(listChangeListener); listeners.add(streamListener);
} }
public void removeStreamObserver (ListChangeListener<Queue<StreamPacket>> listChangeListener) { public void removeStreamObserver (ClientSocketListener streamListener) {
boatPacketListeners.remove(listChangeListener); listeners.remove(streamListener);
} }
private int readByte() throws Exception { private int readByte() throws Exception {
+83 -64
View File
@@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
import javafx.animation.AnimationTimer; import javafx.animation.AnimationTimer;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
@@ -19,22 +20,22 @@ import javafx.scene.layout.Pane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon; import javafx.scene.shape.Polygon;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import seng302.visualiser.controllers.RaceViewController; import seng302.model.Limit;
import seng302.visualiser.fxObjects.BoatGroup; import seng302.visualiser.fxObjects.BoatGroup;
import seng302.visualiser.fxObjects.MarkGroup; import seng302.visualiser.fxObjects.MarkGroup;
import seng302.model.Colors; import seng302.model.Colors;
import seng302.model.Yacht; import seng302.model.Boat;
import seng302.model.map.Boundary; import seng302.model.map.Boundary;
import seng302.model.map.CanvasMap; import seng302.model.map.CanvasMap;
import seng302.model.mark.GateMark; import seng302.model.mark.GateMark;
import seng302.model.mark.Mark; import seng302.model.mark.Mark;
import seng302.model.mark.MarkType; import seng302.model.mark.MarkType;
import seng302.model.mark.SingleMark; import seng302.model.mark.SingleMark;
import seng302.model.stream.StreamParser; import seng302.model.stream.parsers.StreamParser;
import seng302.model.stream.XMLParser; import seng302.model.stream.parsers.xml.XMLParser;
import seng302.model.stream.XMLParser.RaceXMLObject.Limit; import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Limit;
import seng302.model.stream.XMLParser.RaceXMLObject.Participant; import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Participant;
import seng302.model.stream.packets.BoatPositionPacket; import seng302.model.stream.parsers.PositionUpdateData;
import seng302.utilities.GeoPoint; import seng302.utilities.GeoPoint;
import seng302.utilities.GeoUtility; import seng302.utilities.GeoUtility;
@@ -42,7 +43,7 @@ import seng302.utilities.GeoUtility;
* Created by cir27 on 20/07/17. * Created by cir27 on 20/07/17.
*/ */
public class GameView extends Pane { public class GameView extends Pane {
private RaceViewController raceViewController;
private ObservableList<Node> gameObjects; private ObservableList<Node> gameObjects;
private ImageView mapImage; private ImageView mapImage;
@@ -66,7 +67,9 @@ public class GameView extends Pane {
private List<MarkGroup> markGroups = new ArrayList<>(); private List<MarkGroup> markGroups = new ArrayList<>();
private List<BoatGroup> boatGroups = new ArrayList<>(); private List<BoatGroup> boatGroups = new ArrayList<>();
private Text FPSdisplay = new Text();
private Text fpsDisplay = new Text();
private Polygon raceBorder = new Polygon(); private Polygon raceBorder = new Polygon();
//FRAME RATE //FRAME RATE
@@ -75,44 +78,24 @@ public class GameView extends Pane {
private int frameTimeIndex = 0; private int frameTimeIndex = 0;
private boolean arrayFilled = false; private boolean arrayFilled = false;
AnimationTimer timer; private AnimationTimer timer;
private enum ScaleDirection { private enum ScaleDirection {
HORIZONTAL, HORIZONTAL,
VERTICAL VERTICAL
} }
void setup(RaceViewController raceViewController) { public GameView () {
this.raceViewController = raceViewController;
}
public void initialize() {
raceViewController = new RaceViewController();
gameObjects = this.getChildren(); gameObjects = this.getChildren();
// create image view for map, bind panel size to image // create image view for map, bind panel size to image
mapImage = new ImageView(); mapImage = new ImageView();
gameObjects.add(mapImage); gameObjects.add(mapImage);
mapImage.fitWidthProperty().bind(this.widthProperty()); mapImage.fitWidthProperty().bind(this.widthProperty());
mapImage.fitHeightProperty().bind(this.heightProperty()); mapImage.fitHeightProperty().bind(this.heightProperty());
initializeTimer();
} }
void initializeCanvas() { private void initializeTimer () {
fitMarksToCanvas();
drawGoogleMap();
FPSdisplay.setLayoutX(5);
FPSdisplay.setLayoutY(20);
FPSdisplay.setStrokeWidth(2);
gameObjects.add(FPSdisplay);
gameObjects.add(raceBorder);
initializeMarks();
initializeBoats();
this.widthProperty().addListener(resize -> {
canvasWidth = this.getWidth();
canvasHeight = this.getHeight();
fitMarksToCanvas();
});
timer = new AnimationTimer() { timer = new AnimationTimer() {
private long lastTime = 0; private long lastTime = 0;
private int FPSCount = 30; private int FPSCount = 30;
@@ -136,24 +119,35 @@ public class GameView extends Pane {
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame; frameRate = 1_000_000_000.0 / elapsedNanosPerFrame;
if (FPSCount-- == 0) { if (FPSCount-- == 0) {
FPSCount = 30; FPSCount = 30;
// drawFps(frameRate.intValue()); drawFps(frameRate.intValue());
} }
} }
updateGroups(); updateGroups();
if (StreamParser.isRaceFinished()) {
this.stop();
}
lastTime = now; lastTime = now;
} }
} }
if (StreamParser.isRaceFinished()) {
this.stop();
switchToFinishScreen();
}
} }
}; };
} }
void initializeCanvas() {
fitMarksToCanvas();
drawGoogleMap();
fpsDisplay.setLayoutX(5);
fpsDisplay.setLayoutY(20);
fpsDisplay.setStrokeWidth(2);
gameObjects.add(fpsDisplay);
gameObjects.add(raceBorder);
initializeMarks();
initializeBoats();
this.widthProperty().addListener(resize -> {
canvasWidth = this.getWidth();
canvasHeight = this.getHeight();
fitMarksToCanvas();
});
}
private void switchToFinishScreen() { private void switchToFinishScreen() {
try { try {
// canvas view -> anchor pane -> grid pane -> main view // canvas view -> anchor pane -> grid pane -> main view
@@ -255,28 +249,28 @@ public class GameView extends Pane {
} }
private void updateBoatGroup(BoatGroup boatGroup) { private void updateBoatGroup(BoatGroup boatGroup) {
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId()); // PriorityBlockingQueue<PositionUpdateData> movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId());
// giving the movementQueue a 5 packet buffer to account for slightly out of order packets // // giving the movementQueue a 5 packet buffer to account for slightly out of order packets
if (movementQueue.size() > 0) { // if (movementQueue.size() > 0) {
try { // try {
BoatPositionPacket positionPacket = movementQueue.take(); // PositionUpdateData positionPacket = movementQueue.take();
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon()); Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
double heading = 360.0 / 0xffff * positionPacket.getHeading(); // double heading = 360.0 / 0xffff * positionPacket.getHeading();
boatGroup.setDestination( boatGroup.setDestination(
p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(),
positionPacket.getTimeValid(), frameRate); positionPacket.getTimeValid(), frameRate);
} catch (InterruptedException e){ // } catch (InterruptedException e){
e.printStackTrace(); // e.printStackTrace();
}
// } // }
} //// }
// }
} }
private void updateMarkGroup (long raceId, MarkGroup markGroup) { private void updateMarkGroup (long raceId, MarkGroup markGroup) {
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.markLocations.get(raceId); PriorityBlockingQueue<PositionUpdateData> movementQueue = StreamParser.markLocations.get(raceId);
if (movementQueue.size() > 0){ if (movementQueue.size() > 0){
try { try {
BoatPositionPacket positionPacket = movementQueue.take(); PositionUpdateData positionPacket = movementQueue.take();
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon()); Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId); markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -289,7 +283,7 @@ public class GameView extends Pane {
* Draws all the boats. * Draws all the boats.
*/ */
private void initializeBoats() { private void initializeBoats() {
Map<Integer, Yacht> boats = StreamParser.getBoats(); Map<Integer, Boat> boats = StreamParser.getBoats();
Group wakes = new Group(); Group wakes = new Group();
Group trails = new Group(); Group trails = new Group();
Group annotations = new Group(); Group annotations = new Group();
@@ -301,7 +295,7 @@ public class GameView extends Pane {
participantIDs.add(p.getsourceID()); participantIDs.add(p.getsourceID());
} }
for (Yacht boat : boats.values()) { for (Boat boat : boats.values()) {
if (participantIDs.contains(boat.getSourceID())) { if (participantIDs.contains(boat.getSourceID())) {
boat.setColour(Colors.getColor()); boat.setColour(Colors.getColor());
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour()); BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
@@ -336,14 +330,9 @@ public class GameView extends Pane {
gameObjects.addAll(markGroups); gameObjects.addAll(markGroups);
} }
// private void drawFps(int fps){ private void drawFps(int fps){
// if (raceViewController.isDisplayFps()){ fpsDisplay.setText(String.format("%d FPS", fps));
// FPSdisplay.setVisible(true); }
// FPSdisplay.setText(String.format("%d FPS", fps));
// } else {
// FPSdisplay.setVisible(false);
// }
// }
/** /**
* Calculates x and y location for every marker that fits it to the canvas the race will be * Calculates x and y location for every marker that fits it to the canvas the race will be
@@ -367,7 +356,7 @@ public class GameView extends Pane {
*/ */
private void findMinMaxPoint() { private void findMinMaxPoint() {
List<Limit> sortedPoints = new ArrayList<>(); List<Limit> sortedPoints = new ArrayList<>();
for (Limit limit : StreamParser.getXmlObject().getRaceXML().getCourseLimit()) { for (Limit limit : raceData) {
sortedPoints.add(limit); sortedPoints.add(limit);
} }
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat)); sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
@@ -512,4 +501,34 @@ public class GameView extends Pane {
metersPerPixelY = dVertical / dy; metersPerPixelY = dVertical / dy;
} }
public void setAnnotationVisibilities (boolean teamName, boolean velocity, boolean estTime,
boolean legTime, boolean trail, boolean wake) {
for (BoatGroup boatGroup : boatGroups) {
boatGroup.setVisibility(teamName, velocity, estTime, legTime, trail, wake);
}
}
public void setFPSVisibility (boolean visibility) {
fpsDisplay.setVisible(visibility);
}
public ReadOnlyBooleanProperty getFPSVisibilityProperty () {
return fpsDisplay.visibleProperty();
}
public void selectBoat (int boatId) {
}
public void pauseRace () {
timer.stop();
}
public void startRace () {
timer.start();
}
public void updateBorder (List<Limit> courseLimit) {
}
} }
@@ -15,24 +15,24 @@ import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import seng302.model.Yacht; import seng302.model.Boat;
import seng302.model.stream.StreamParser; import seng302.model.stream.parsers.StreamParser;
import seng302.model.stream.XMLParser.RaceXMLObject.Participant; import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Participant;
public class FinishScreenViewController implements Initializable { public class FinishScreenViewController implements Initializable {
@FXML @FXML
private GridPane finishScreenGridPane; private GridPane finishScreenGridPane;
@FXML @FXML
private TableView<Yacht> finishOrderTable; private TableView<Boat> finishOrderTable;
@FXML @FXML
private TableColumn<Yacht, String> posCol; private TableColumn<Boat, String> posCol;
@FXML @FXML
private TableColumn<Yacht, String> boatNameCol; private TableColumn<Boat, String> boatNameCol;
@FXML @FXML
private TableColumn<Yacht, String> shortNameCol; private TableColumn<Boat, String> shortNameCol;
@FXML @FXML
private TableColumn<Yacht, String> countryCol; private TableColumn<Boat, String> countryCol;
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
@@ -41,7 +41,7 @@ public class FinishScreenViewController implements Initializable {
finishOrderTable.getStylesheets().add(getClass().getResource("/css/master.css").toString()); finishOrderTable.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// set up data for table // set up data for table
ObservableList<Yacht> data = FXCollections.observableArrayList(); ObservableList<Boat> data = FXCollections.observableArrayList();
finishOrderTable.setItems(data); finishOrderTable.setItems(data);
// setting table col data // setting table col data
@@ -67,7 +67,7 @@ public class FinishScreenViewController implements Initializable {
} }
// add data to table // add data to table
for (Yacht boat : StreamParser.getBoatsPos().values()) { for (Boat boat : StreamParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) { if (participantIDs.contains(boat.getSourceID())) {
data.add(boat); data.add(boat);
} }
@@ -27,7 +27,10 @@ import javafx.stage.Stage;
import javafx.stage.StageStyle; import javafx.stage.StageStyle;
import javafx.util.Duration; import javafx.util.Duration;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import seng302.model.Corner;
import seng302.model.stream.parsers.xml.RaceXMLData;
import seng302.utilities.GeoUtility; import seng302.utilities.GeoUtility;
import seng302.visualiser.GameView;
import seng302.visualiser.controllers.annotations.Annotation; import seng302.visualiser.controllers.annotations.Annotation;
import seng302.visualiser.controllers.annotations.ImportantAnnotationController; import seng302.visualiser.controllers.annotations.ImportantAnnotationController;
import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate; import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate;
@@ -38,13 +41,11 @@ import seng302.model.*;
import seng302.model.mark.GateMark; import seng302.model.mark.GateMark;
import seng302.model.mark.Mark; import seng302.model.mark.Mark;
import seng302.model.mark.SingleMark; import seng302.model.mark.SingleMark;
import seng302.model.stream.StreamParser; import seng302.model.stream.parsers.StreamParser;
import seng302.model.stream.XMLParser;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import seng302.model.stream.XMLParser.RaceXMLObject.Participant; import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Participant;
import java.util.stream.Collectors;
/** /**
* Created by ptg19 on 29/03/17. * Created by ptg19 on 29/03/17.
@@ -70,21 +71,21 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
@FXML @FXML
private Button selectAnnotationBtn; private Button selectAnnotationBtn;
@FXML @FXML
private ComboBox boatSelectionComboBox; private ComboBox<Boat> boatSelectionComboBox;
@FXML
private GameViewController gameViewController; //Race Data
private Map<Integer, Boat> participants;
private Map<Integer, Mark> course;
private RaceXMLData raceData;
private GameView gameView;
private static ArrayList<Yacht> startingBoats = new ArrayList<>();
private boolean displayFps;
private Timeline timerTimeline; private Timeline timerTimeline;
private Stage stage; private Stage stage;
private static HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>(); private HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>();
private static ArrayList<Yacht> racingBoats = new ArrayList<>();
private ImportantAnnotationsState importantAnnotations; private ImportantAnnotationsState importantAnnotations;
private Yacht selectedBoat; private Boat selectedBoat;
public void initialize() { public void initialize() {
// Load a default important annotation state // Load a default important annotation state
importantAnnotations = new ImportantAnnotationsState(); importantAnnotations = new ImportantAnnotationsState();
@@ -94,19 +95,22 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
raceSparkLine.getYAxis().setTranslateX(-5); raceSparkLine.getYAxis().setTranslateX(-5);
raceSparkLine.getYAxis().setAutoRanging(false); raceSparkLine.getYAxis().setAutoRanging(false);
sparklineYAxis.setTickMarkVisible(false); sparklineYAxis.setTickMarkVisible(false);
startingBoats = new ArrayList<>(StreamParser.getBoats().values());
gameViewController.setup(this);
gameViewController.initializeCanvas();
initializeUpdateTimer(); initializeUpdateTimer();
initialiseFPSCheckBox(); initialiseFPSCheckBox();
initialiseAnnotationSlider(); initialiseAnnotationSlider();
initialiseBoatSelectionComboBox(); initialiseBoatSelectionComboBox();
gameViewController.timer.start();
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
} }
public void loadRace (Map<Integer, Boat> participants, RaceXMLData raceData) {
this.participants = participants;
this.raceData = raceData;
this.course = raceData.getCompoundMarks();
gameView = new GameView();
gameView.startRace();
}
/** /**
* The important annotations have been changed, update this view * The important annotations have been changed, update this view
@@ -126,33 +130,30 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
try { try {
FXMLLoader fxmlLoader = new FXMLLoader(); FXMLLoader fxmlLoader = new FXMLLoader();
Stage stage = new Stage(); Stage stage = new Stage();
// Set controller // Set controller
ImportantAnnotationController controller = new ImportantAnnotationController(this, ImportantAnnotationController controller = new ImportantAnnotationController(
stage); this, stage
);
fxmlLoader.setController(controller); fxmlLoader.setController(controller);
// Load FXML and set CSS // Load FXML and set CSS
fxmlLoader fxmlLoader.setLocation(
.setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml")); getClass().getResource("/views/importantAnnotationSelectView.fxml")
);
Scene scene = new Scene(fxmlLoader.load(), 469, 298); Scene scene = new Scene(fxmlLoader.load(), 469, 298);
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString()); scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
stage.initStyle(StageStyle.UNDECORATED); stage.initStyle(StageStyle.UNDECORATED);
stage.setScene(scene); stage.setScene(scene);
stage.show(); stage.show();
controller.loadState(importantAnnotations); controller.loadState(importantAnnotations);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
private void initialiseFPSCheckBox() { private void initialiseFPSCheckBox() {
displayFps = true; toggleFps.selectedProperty().addListener((obs, oldVal, newVal) ->
toggleFps.selectedProperty().addListener( gameView.setFPSVisibility(toggleFps.isSelected())
(observable, oldValue, newValue) -> displayFps = !displayFps); );
} }
private void initialiseAnnotationSlider() { private void initialiseAnnotationSlider() {
@@ -189,8 +190,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}); });
annotationSlider.valueProperty().addListener((obs, oldval, newVal) -> annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
setAnnotations((int) annotationSlider.getValue())); setAnnotations((int) annotationSlider.getValue())
);
annotationSlider.setValue(2); annotationSlider.setValue(2);
} }
@@ -198,21 +199,21 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
/** /**
* Used to add any new boats into the race that may have started late or not have had data received yet * Used to add any new boats into the race that may have started late or not have had data received yet
*/ */
void updateSparkLine(){ private void updateSparkLine(){
// Collect the racing boats that aren't already in the chart // Collect the racing boats that aren't already in the chart
ArrayList<Yacht> sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceID()) List<Boat> sparkLineCandidates = new ArrayList<>();
&& yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new)); participants.forEach((id, boat) ->{
if (!sparkLineData.containsKey(id) && boat.getPosition() != null && !boat.getPosition().equals("-"))
sparkLineCandidates.add(boat);
});
// Obtain the qualifying boats to set the max on the Y axis sparklineYAxis.setUpperBound(participants.size() + 1);
racingBoats = startingBoats.stream().filter(yacht ->
yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
sparklineYAxis.setUpperBound(racingBoats.size() + 1);
// Create a new data series for new boats // Create a new data series for new boats
sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> { sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> {
Series<String, Double> yachtData = new Series<>(); Series<String, Double> yachtData = new Series<>();
yachtData.setName(yacht.getBoatName()); yachtData.setName(yacht.getSourceID().toString());
yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition()))); yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + participants.size() - Double.parseDouble(yacht.getPosition())));
sparkLineData.put(yacht.getSourceID(), yachtData); sparkLineData.put(yacht.getSourceID(), yachtData);
}); });
@@ -230,48 +231,52 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
// Adds the new data series to the sparkline (and set the colour of the series) // Adds the new data series to the sparkline (and set the colour of the series)
raceSparkLine.setCreateSymbols(false); raceSparkLine.setCreateSymbols(false);
positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> { positions
raceSparkLine.getData().add(spark); .stream()
spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName())); .filter(spark -> !raceSparkLine.getData().contains(spark))
}); .forEach(spark -> {
raceSparkLine.getData().add(spark);
spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
});
} }
/** /**
* Updates the yachts sparkline of the desired boat and using the new leg number * Updates the yachts sparkline of the desired boat and using the new leg number
* @param yacht The yacht to be updated on the sparkline * @param boat The yacht to be updated on the sparkline
* @param legNumber the leg number that the position will be assigned to * @param legNumber the leg number that the position will be assigned to
*/ */
public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){ public void updateYachtPositionSparkline(Boat boat, Integer legNumber){
XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID()); XYChart.Series<String, Double> positionData = sparkLineData.get(boat.getSourceID());
positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition()))); positionData.getData().add(
new XYChart.Data<>(
Integer.toString(legNumber),
1 + participants.size() - Double.parseDouble(boat.getPosition())
)
);
} }
/** /**
* gets the rgb string of the boats colour to use for the chart via css * gets the rgb string of the boats colour to use for the chart via css
* @param boatName boat passed in to get the boats colour * @param boatId id of boat passed in to get the boats colour
* @return the colour as an rgb string * @return the colour as an rgb string
*/ */
private String getBoatColorAsRGB(String boatName){ private String getBoatColorAsRGB(String boatId){
Color color = Color.WHITE; Color color = participants.get(Integer.valueOf(boatId)).getColour();
for (Yacht yacht: startingBoats){
if (Objects.equals(yacht.getBoatName(), boatName)){
color = yacht.getColour();
}
}
if (color == null){ if (color == null){
return String.format( "#%02X%02X%02X",255,255,255); return String.format("#%02X%02X%02X",255,255,255);
} }
return String.format( "#%02X%02X%02X", return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ), (int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ), (int)( color.getGreen() * 255 ),
(int)( color.getBlue() * 255 ) ); (int)( color.getBlue() * 255 )
);
} }
/** /**
* Initalises a timer which updates elements of the RaceView such as wind direction, boat * Initialises a timer which updates elements of the RaceView such as wind direction, boat
* orderings etc.. which are dependent on the info from the stream parser constantly. * orderings etc.. which are dependent on the info from the stream parser constantly.
* Updates of each of these attributes are called ONCE EACH SECOND * Updates of each of these attributes are called ONCE EACH SECOND
*/ */
@@ -286,9 +291,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
updateWindDirection(); updateWindDirection();
updateOrder(); updateOrder();
updateBoatSelectionComboBox(); updateBoatSelectionComboBox();
updateSparkLine();
}) })
); );
// Start the timer // Start the timer
timerTimeline.playFromStart(); timerTimeline.playFromStart();
} }
@@ -302,9 +307,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* @return The next Mark or null if none found * @return The next Mark or null if none found
*/ */
private Mark getNextMark(BoatGroup bg) { private Mark getNextMark(BoatGroup bg) {
Integer legNumber = bg.getBoat().getLegNumber();
List<XMLParser.RaceXMLObject.Corner> markSequence = StreamParser.getXmlObject().getRaceXML().getCompoundMarkSequence(); Integer legNumber = bg.getBoat().getLegNumber();
List<Corner> markSequence = raceData.getMarkSequence();
if (legNumber == 0) { if (legNumber == 0) {
return null; return null;
@@ -312,18 +317,11 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
return null; return null;
} }
for (XMLParser.RaceXMLObject.Corner corner : markSequence) { for (Corner corner : markSequence) {
if (legNumber + 2 == corner.getSeqID()) { if (legNumber + 2 == corner.getSeqID()) {
Integer thisCompoundMarkID = corner.getCompoundMarkID(); return raceData.getCompoundMarks().get(corner.getCompoundMarkID());
for (Mark mark : StreamParser.getXmlObject().getRaceXML().getAllCompoundMarks()) {
if (mark.getCompoundMarkID() == thisCompoundMarkID) {
return mark;
}
}
} }
} }
return null; return null;
} }
@@ -355,8 +353,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* in the boat selection combo box * in the boat selection combo box
*/ */
private void updateBoatSelectionComboBox() { private void updateBoatSelectionComboBox() {
ObservableList<Yacht> observableBoats = FXCollections ObservableList<Boat> observableBoats = FXCollections.observableArrayList();
.observableArrayList(StreamParser.getBoatsPos().values()); observableBoats.addAll(participants.values());
boatSelectionComboBox.setItems(observableBoats); boatSelectionComboBox.setItems(observableBoats);
} }
@@ -371,15 +369,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// list of racing boat id // list of racing boat id
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
.getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) {
participantIDs.add(p.getsourceID());
}
if (StreamParser.isRaceStarted()) { if (StreamParser.isRaceStarted()) {
for (Yacht boat : StreamParser.getBoatsPos().values()) { for (Boat boat : StreamParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
if (boat.getBoatStatus() == 3) { // 3 is finish status if (boat.getBoatStatus() == 3) { // 3 is finish status
Text textToAdd = new Text(boat.getPosition() + ". " + Text textToAdd = new Text(boat.getPosition() + ". " +
@@ -397,15 +389,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
} }
} }
} else { } else {
for (Yacht boat : StreamParser.getBoats().values()) { participants.forEach((id, boat) ->{
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing Text textToAdd = new Text(boat.getPosition() + ". " +
Text textToAdd = new Text(boat.getPosition() + ". " + boat.getShortName() + " ");
boat.getShortName() + " "); textToAdd.setFill(Paint.valueOf("#d3d3d3"));
textToAdd.setFill(Paint.valueOf("#d3d3d3")); textToAdd.setStyle("");
textToAdd.setStyle(""); positionVbox.getChildren().add(textToAdd);
positionVbox.getChildren().add(textToAdd); });
}
}
} }
} }
@@ -510,8 +500,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
//This listener is fired whenever the combo box changes. This means when the values are updated //This listener is fired whenever the combo box changes. This means when the values are updated
//We dont want to set the selected value if the values are updated but nothing clicked (null) //We dont want to set the selected value if the values are updated but nothing clicked (null)
if (newValue != null && newValue != selectedBoat) { if (newValue != null && newValue != selectedBoat) {
Yacht thisYacht = (Yacht) newValue; Boat thisBoat = (Boat) newValue;
setSelectedBoat(thisYacht); setSelectedBoat(thisBoat);
} }
}); });
} }
@@ -529,9 +519,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
contentAnchorPane.getChildren().addAll((Pane) loader.load()); contentAnchorPane.getChildren().addAll((Pane) loader.load());
} catch (javafx.fxml.LoadException e) { } catch (javafx.fxml.LoadException e) {
System.err.println(e.getCause()); System.err.println(e.getCause().toString());
} catch (IOException e) { } catch (IOException e) {
System.err.println(e); System.err.println(e.toString());
} }
} }
@@ -569,37 +559,30 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
return timerString; return timerString;
} }
boolean isDisplayFps() {
return displayFps;
}
private void setAnnotations(Integer annotationLevel) { private void setAnnotations(Integer annotationLevel) {
switch (annotationLevel) { switch (annotationLevel) {
// No Annotations // No Annotations
case 0: case 0:
for (BoatGroup bg : gameViewController.getBoatGroups()) { gameView.setAnnotationVisibilities(
bg.setVisibility(false, false, false, false, false, false); false, false, false, false, false, false
} );
break; break;
// Important Annotations // Important Annotations
case 1: case 1:
for (BoatGroup bg : gameViewController.getBoatGroups()) { gameView.setAnnotationVisibilities(
bg.setVisibility( importantAnnotations.getAnnotationState(Annotation.NAME),
importantAnnotations.getAnnotationState(Annotation.NAME), importantAnnotations.getAnnotationState(Annotation.SPEED),
importantAnnotations.getAnnotationState(Annotation.SPEED), importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK),
importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK), importantAnnotations.getAnnotationState(Annotation.LEGTIME),
importantAnnotations.getAnnotationState(Annotation.LEGTIME), importantAnnotations.getAnnotationState(Annotation.TRACK),
importantAnnotations.getAnnotationState(Annotation.TRACK), importantAnnotations.getAnnotationState(Annotation.WAKE)
importantAnnotations.getAnnotationState(Annotation.WAKE) );
);
}
break; break;
// All Annotations // All Annotations
case 2: case 2:
for (BoatGroup bg : gameViewController.getBoatGroups()) { gameView.setAnnotationVisibilities(
bg.setVisibility(true, true, true, true, true, true); true, true, true, true, true, true
} );
break; break;
} }
} }
@@ -608,16 +591,16 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
/** /**
* Sets all the annotations of the selected boat to be visible and all others to be hidden * Sets all the annotations of the selected boat to be visible and all others to be hidden
* *
* @param yacht The yacht for which we want to view all annotations * @param boat The yacht for which we want to view all annotations
*/ */
private void setSelectedBoat(Yacht yacht) { private void setSelectedBoat(Boat boat) {
for (BoatGroup bg : gameViewController.getBoatGroups()) { for (BoatGroup bg : gameViewController.getBoatGroups()) {
//We need to iterate over all race groups to get the matching boat group belonging to this boat if we //We need to iterate over all race groups to get the matching boat group belonging to this boat if we
//are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup. //are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
if (bg.getBoat().getHullID().equals(yacht.getHullID())) { if (bg.getBoat().getHullID().equals(boat.getHullID())) {
updateLaylines(bg); updateLaylines(bg);
bg.setIsSelected(true); bg.setIsSelected(true);
selectedBoat = yacht; selectedBoat = boat;
} else { } else {
bg.setIsSelected(false); bg.setIsSelected(false);
} }
@@ -641,4 +624,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
return sparkLineData.containsKey(yachtId); return sparkLineData.containsKey(yachtId);
} }
public void updateRaceData (RaceXMLData raceData) {
this.raceData = raceData;
gameView.updateBorder(raceData.getCourseLimit());
}
} }
@@ -59,7 +59,7 @@ public class StartScreenController {
new GameState(ipAddress); new GameState(ipAddress);
new MainServerThread().start(); new MainServerThread().start();
ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950); ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950);
controller.setClientToServerThread(clientToServerThread); // controller.setClientToServerThread(clientToServerThread);
clientToServerThread.start(); clientToServerThread.start();
// new GameServerThread("Fuck you"); // new GameServerThread("Fuck you");
// get the lobby controller so that we can pass the game server thread to it // get the lobby controller so that we can pass the game server thread to it
@@ -1,78 +1,174 @@
package seng302.visualiser.controllers.client; package seng302.visualiser.controllers.client;
import javafx.beans.value.ChangeListener; import java.text.DateFormat;
import javafx.scene.Node; import java.text.SimpleDateFormat;
import javafx.scene.input.KeyEvent; import java.time.ZoneId;
import javafx.scene.layout.AnchorPane; import java.time.ZoneOffset;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import seng302.model.stream.XMLParser; import seng302.model.Boat;
import seng302.model.mark.Mark;
import seng302.model.stream.parsers.PositionUpdateData.DeviceType;
import seng302.model.stream.parsers.MarkRoundingData;
import seng302.model.stream.parsers.RaceStartData;
import seng302.model.stream.parsers.RaceStatusData;
import seng302.model.stream.parsers.xml.RaceXMLData;
import seng302.model.stream.parsers.StreamParser;
import seng302.model.stream.parsers.xml.RegattaXMLData;
import seng302.model.stream.parsers.xml.XMLParser;
import seng302.model.stream.parsers.PositionUpdateData;
import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.packets.StreamPacket;
import seng302.server.messages.BoatActionMessage; import seng302.visualiser.ClientSocketListener;
import seng302.server.messages.BoatActionType;
import seng302.visualiser.ClientToServerThread; import seng302.visualiser.ClientToServerThread;
import seng302.visualiser.controllers.RaceViewController;
/** /**
* Created by cir27 on 20/07/17. * Created by cir27 on 20/07/17.
*/ */
public class ClientController { public class ClientController {
Pane holderPane; private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
ClientToServerThread socketThread;
private Pane holderPane;
private ClientToServerThread socketThread;
private ClientSocketListener socketListener;
private RaceViewController raceView;
private Map<Integer, Boat> allBoatsMap;
private Map<Integer, Boat> racingBoats = new HashMap<>();
private RegattaXMLData regattaData;
private RaceXMLData raceData;
public ClientController (String ipAddress, Pane holder) { public ClientController (String ipAddress, Pane holder) {
this.holderPane = holder; this.holderPane = holder;
socketThread = new ClientToServerThread(ipAddress, 4950); socketThread = new ClientToServerThread(ipAddress, 4950);
socketThread.start(); socketThread.start();
socketThread.waitForXML(event -> storeXMLData()); socketListener = this::parsePacket;
socketThread.addStreamObserver(socketListener);
}
private void loadRaceView () {
allBoatsMap.forEach((id, boat) -> {
if (raceData.getParticipants().contains(id))
racingBoats.put(id, boat);
});
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("RaceView.fxml"));
raceView = fxmlLoader.getController();
raceView.loadRace(racingBoats, raceData);
} }
private void parsePacket(StreamPacket packet) { private void parsePacket(StreamPacket packet) {
try { switch (packet.getType()) {
switch (packet.getType()) { case RACE_STATUS:
case HEARTBEAT: processRaceStatusUpdate(StreamParser.extractRaceStatus(packet));
extractHeartBeat(packet); break;
break;
case RACE_STATUS: case REGATTA_XML:
extractRaceStatus(packet); regattaData = XMLParser.parseRegatta(
break; StreamParser.extractXmlMessage(packet)
case DISPLAY_TEXT_MESSAGE: );
extractDisplayMessage(packet); DATE_TIME_FORMAT.setTimeZone(
break; TimeZone.getTimeZone(
case XML_MESSAGE: ZoneId.ofOffset("UTC", ZoneOffset.ofHours(regattaData.getUtcOffset()))
newRaceXmlReceived = true; )
extractXmlMessage(packet); );
break; startRaceIfAllDataRecieved();
case RACE_START_STATUS: break;
extractRaceStartStatus(packet);
break; case RACE_XML:
case YACHT_EVENT_CODE: raceData = XMLParser.parseRace(
extractYachtEventCode(packet); StreamParser.extractXmlMessage(packet)
break; );
case YACHT_ACTION_CODE: if (raceView != null) {
extractYachtActionCode(packet); raceView.updateRaceData(raceData);
break; }
case CHATTER_TEXT: startRaceIfAllDataRecieved();
extractChatterText(packet); break;
break;
case BOAT_LOCATION: case BOAT_XML:
extractBoatLocation(packet); allBoatsMap = XMLParser.parseBoats(
break; StreamParser.extractXmlMessage(packet)
case MARK_ROUNDING: );
extractMarkRounding(packet); startRaceIfAllDataRecieved();
break; break;
case COURSE_WIND:
extractCourseWind(packet); case RACE_START_STATUS:
break; RaceStartData raceStartData = StreamParser.extractRaceStartStatus(packet);
case AVG_WIND: break;
extractAvgWind(packet);
break; case BOAT_LOCATION:
case BOAT_ACTION: PositionUpdateData positionData = StreamParser.extractBoatLocation(packet);
extractBoatAction(packet); updatePosition(positionData);
break; break;
case MARK_ROUNDING:
MarkRoundingData roundingData = StreamParser.extractMarkRounding(packet);
updateMarkRounding(roundingData);
break;
}
}
private void startRaceIfAllDataRecieved () {
if (raceData != null && allBoatsMap != null && regattaData != null)
loadRaceView();
}
/**
* Updates the position of a boat. Boat and position are given in the provided data.
* @param positionData
*/
private void updatePosition(PositionUpdateData positionData) {
if (positionData.getType() == DeviceType.YACHT_TYPE) {
Boat boat = racingBoats.get(positionData.getDeviceId());
boat.setVelocity(positionData.getGroundSpeed());
boat.setLat(positionData.getLat());
boat.setLon(positionData.getLon());
boat.setHeading(positionData.getHeading());
} else {
Mark mark = raceData.getCompoundMarks().get(positionData.getDeviceId());
}
}
/**
* Updates the boat as having passed the mark. Boat and mark are given by the ids in the
* provided data.
* @param roundingData Contains data for the rounding of a mark.
*/
private void updateMarkRounding(MarkRoundingData roundingData) {
Boat boat = racingBoats.get(roundingData.getBoatId());
boat.setMarkRoundingTime(roundingData.getTimeStamp());
boat.setLastMarkRounded(
raceData.getCompoundMarks().get(
roundingData.getMarkId()
)
);
}
private void processRaceStatusUpdate (RaceStatusData data) {
String raceTimeStr = DATE_TIME_FORMAT.format(data.getCurrentTime());
Date date = new Date();
date.getTime();
long timeTillStart = (data.getExpectedStartTime() - data.getCurrentTime()) / 1000;
for (long[] boatData : data.getBoatData()) {
Boat boat = allBoatsMap.get((int) boatData[0]);
boat.setEstimateTimeAtNextMark(boatData[1]);
boat.setEstimateTimeAtFinish(boatData[2]);
int legNumber = (int) boatData[3];
boat.setLegNumber(legNumber);
if (legNumber != boat.getLegNumber()) {
int placing = 1;
for (Boat otherBoat : allBoatsMap.values()) {
if (otherBoat.getSourceID() != boatData[0] &&
boat.getLegNumber() <= otherBoat.getLegNumber())
placing++;
}
boat.setPosition(placing);
} }
} catch (NullPointerException e) {
System.out.println("Error parsing packet");
e.printStackTrace();
} }
} }
@@ -5,8 +5,8 @@ import javafx.scene.Group;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import seng302.model.Yacht; import seng302.model.Boat;
import seng302.model.stream.StreamParser; import seng302.model.stream.parsers.StreamParser;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@@ -34,9 +34,9 @@ public class BoatAnnotations extends Group{
private Text estTimeToNextMarkObject; private Text estTimeToNextMarkObject;
private Text legTimeObject; private Text legTimeObject;
private Yacht boat; private Boat boat;
BoatAnnotations (Yacht boat, Color theme) { BoatAnnotations (Boat boat, Color theme) {
super.setCache(true); super.setCache(true);
this.boat = boat; this.boat = boat;
background.setX(BACKGROUND_X); background.setX(BACKGROUND_X);
@@ -83,10 +83,10 @@ public class BoatAnnotations extends Group{
} }
void update () { void update () {
velocityObject.setText(String.format(String.format("%.2f m/s", boat.getVelocity()))); velocityObject.setText(String.format("%.2f m/s", boat.getVelocity()));
DateFormat format = new SimpleDateFormat("mm:ss");
if (boat.getTimeTillNext() != null) { if (boat.getTimeTillNext() != null) {
DateFormat format = new SimpleDateFormat("mm:ss");
String timeToNextMark = format String timeToNextMark = format
.format(boat.getTimeTillNext() - StreamParser.getCurrentTimeLong()); .format(boat.getTimeTillNext() - StreamParser.getCurrentTimeLong());
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark); estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
@@ -95,7 +95,6 @@ public class BoatAnnotations extends Group{
} }
if (boat.getMarkRoundTime() != null) { if (boat.getMarkRoundTime() != null) {
DateFormat format = new SimpleDateFormat("mm:ss");
String elapsedTime = format String elapsedTime = format
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundTime()); .format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundTime());
legTimeObject.setText("Last mark: " + elapsedTime); legTimeObject.setText("Last mark: " + elapsedTime);
@@ -10,12 +10,12 @@ import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon; import javafx.scene.shape.Polygon;
import javafx.scene.transform.Rotate; import javafx.scene.transform.Rotate;
import seng302.visualiser.controllers.GameViewController; import seng302.visualiser.controllers.GameViewController;
import seng302.model.Yacht; import seng302.model.Boat;
import seng302.utilities.GeoUtility; import seng302.utilities.GeoUtility;
import seng302.model.mark.GateMark; import seng302.model.mark.GateMark;
import seng302.model.mark.Mark; import seng302.model.mark.Mark;
import seng302.model.mark.SingleMark; import seng302.model.mark.SingleMark;
import seng302.model.stream.StreamParser; import seng302.model.stream.parsers.StreamParser;
/** /**
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 * BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
@@ -38,7 +38,7 @@ public class BoatGroup extends Group {
private Double lastRotation = 0.0; private Double lastRotation = 0.0;
private long framesToMove; private long framesToMove;
//Graphical objects //Graphical objects
private Yacht boat; private Boat boat;
private Group lineGroup = new Group(); private Group lineGroup = new Group();
private Polygon boatPoly; private Polygon boatPoly;
private Wake wake; private Wake wake;
@@ -58,7 +58,7 @@ public class BoatGroup extends Group {
* to tell which BoatGroup to update. * to tell which BoatGroup to update.
* @param color The colour of the boat polygon and the trailing line. * @param color The colour of the boat polygon and the trailing line.
*/ */
public BoatGroup(Yacht boat, Color color) { public BoatGroup(Boat boat, Color color) {
destinationSet = false; destinationSet = false;
this.boat = boat; this.boat = boat;
initChildren(color); initChildren(color);
@@ -74,7 +74,7 @@ public class BoatGroup extends Group {
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon. * polygon.
*/ */
public BoatGroup(Yacht boat, Color color, double... points) { public BoatGroup(Boat boat, Color color, double... points) {
destinationSet = false; destinationSet = false;
this.boat = boat; this.boat = boat;
initChildren(color, points); initChildren(color, points);
@@ -258,15 +258,9 @@ public class BoatGroup extends Group {
boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going
with the wind. with the wind.
*/ */
if (boatLineFuncResult == windLineFuncResult) { return boatLineFuncResult.equals(windLineFuncResult);
return true;
} else {
return false;
}
} }
public void setIsSelected(Boolean isSelected) { public void setIsSelected(Boolean isSelected) {
this.isSelected = isSelected; this.isSelected = isSelected;
setLineGroupVisible(isSelected); setLineGroupVisible(isSelected);
@@ -275,7 +269,9 @@ public class BoatGroup extends Group {
setLayLinesVisible(isSelected); setLayLinesVisible(isSelected);
} }
public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime, boolean trail, boolean wake) { public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime,
boolean trail, boolean wake) {
boatAnnotations.setVisibile(teamName, velocity, estTime, legTime); boatAnnotations.setVisibile(teamName, velocity, estTime, legTime);
this.wake.setVisible(wake); this.wake.setVisible(wake);
this.lineGroup.setVisible(trail); this.lineGroup.setVisible(trail);
@@ -306,7 +302,7 @@ public class BoatGroup extends Group {
return laylines; return laylines;
} }
public Yacht getBoat() { public Boat getBoat() {
return boat; return boat;
} }