diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 96e7c052..e1b8b70b 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -7,7 +7,7 @@ import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Stage; import seng302.model.PolarTable; -import seng302.model.stream.StreamParser; +import seng302.model.stream.parsers.StreamParser; import seng302.model.stream.StreamReceiver; public class App extends Application { diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index 02d53f78..ebcdc51c 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -2,7 +2,7 @@ package seng302.gameServer; import seng302.model.Player; import seng302.model.stream.PacketBufferDelegate; -import seng302.model.stream.StreamParser; +import seng302.model.stream.parsers.StreamParser; import seng302.model.stream.packets.StreamPacket; import java.io.IOException; diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 6871a9f4..68306595 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -1,15 +1,9 @@ package seng302.gameServer; import seng302.model.Player; -import seng302.model.stream.StreamParser; +import seng302.model.stream.parsers.StreamParser; 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.Heartbeat; import seng302.server.messages.Message; import java.io.*; diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Boat.java similarity index 69% rename from src/main/java/seng302/model/Yacht.java rename to src/main/java/seng302/model/Boat.java index cfe82a84..d372f695 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Boat.java @@ -2,6 +2,7 @@ package seng302.model; import javafx.scene.paint.Color; import seng302.model.mark.Mark; +import seng302.model.stream.packets.StreamPacket; import seng302.visualiser.controllers.RaceViewController; 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, * also done outside Boat class because some old variables are not used anymore. */ -public class Yacht { +public class Boat { // Used in boat group - private Color colour; + private Color colour = Color.BLACK; private String boatType; private Integer sourceID; @@ -25,19 +26,16 @@ public class Yacht { private String boatName; private String country; - // Situational data - - // Boat status private Integer boatStatus; - private Integer legNumber; + private Integer legNumber = 0; + private Integer position = 0; private Integer penaltiesAwarded; private Integer penaltiesServed; private Long estimateTimeAtFinish; - private String position; private Double lat; private Double lon; - private Float heading; + private Double heading; private double velocity; private Long timeTillNext; private Long markRoundTime; @@ -46,31 +44,7 @@ public class Yacht { private Mark lastMarkRounded; private Mark nextMark; - - /** - * 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, + public Boat(String boatType, Integer sourceID, String hullID, String shortName, String boatName, String country) { this.boatType = boatType; this.sourceID = sourceID; @@ -78,7 +52,6 @@ public class Yacht { this.shortName = shortName; this.boatName = boatName; this.country = country; - this.position = "-"; } public String getBoatType() { @@ -118,28 +91,9 @@ public class Yacht { } public void setLegNumber(Integer legNumber) { - if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus(sourceID)) { - RaceViewController.updateYachtPositionSparkline(this, 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) { timeTillNext = estimateTimeAtNextMark; } @@ -153,11 +107,11 @@ public class Yacht { this.estimateTimeAtFinish = estimateTimeAtFinish; } - public String getPosition() { + public Integer getPosition() { return position; } - public void setPosition(String position) { + public void setPosition(Integer position) { this.position = position; } @@ -222,11 +176,11 @@ public class Yacht { this.lon = lon; } - public Float getHeading() { + public Double getHeading() { return heading; } - public void setHeading(Float heading) { + public void setHeading(Double heading) { this.heading = heading; } diff --git a/src/main/java/seng302/model/Colors.java b/src/main/java/seng302/model/Colors.java index cde98a39..72ff3ba5 100644 --- a/src/main/java/seng302/model/Colors.java +++ b/src/main/java/seng302/model/Colors.java @@ -3,7 +3,7 @@ package seng302.model; import javafx.scene.paint.Color; /** - * Enum for randomly generating colours. + * Enum for generating colours. */ public enum Colors { RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE; diff --git a/src/main/java/seng302/model/Corner.java b/src/main/java/seng302/model/Corner.java new file mode 100644 index 00000000..8f2f147b --- /dev/null +++ b/src/main/java/seng302/model/Corner.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/seng302/model/Limit.java b/src/main/java/seng302/model/Limit.java new file mode 100644 index 00000000..4c873c91 --- /dev/null +++ b/src/main/java/seng302/model/Limit.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/seng302/model/Player.java b/src/main/java/seng302/model/Player.java index ed8145bb..9f18e9e9 100644 --- a/src/main/java/seng302/model/Player.java +++ b/src/main/java/seng302/model/Player.java @@ -9,7 +9,7 @@ import java.net.Socket; public class Player { private Socket socket; - private Yacht yacht; + private Boat boat; private Integer lastMarkPassed; @@ -29,8 +29,8 @@ public class Player { this.lastMarkPassed = lastMarkPassed; } - public Yacht getYacht() { - return yacht; + public Boat getYacht() { + return boat; } @Override diff --git a/src/main/java/seng302/model/RaceStatus.java b/src/main/java/seng302/model/RaceStatus.java new file mode 100644 index 00000000..62a336b0 --- /dev/null +++ b/src/main/java/seng302/model/RaceStatus.java @@ -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; + +} diff --git a/src/main/java/seng302/model/mark/Mark.java b/src/main/java/seng302/model/mark/Mark.java index 78be9257..65c60195 100644 --- a/src/main/java/seng302/model/mark/Mark.java +++ b/src/main/java/seng302/model/mark/Mark.java @@ -10,7 +10,7 @@ public abstract class Mark { private MarkType markType; private double latitude; private double longitude; - private long id; + private int id; private int compoundMarkID; /** @@ -134,7 +134,7 @@ public abstract class Mark { return longitude; } - public long getId() { + public int getId() { return id; } diff --git a/src/main/java/seng302/model/stream/StreamParser.java b/src/main/java/seng302/model/stream/StreamParser.java deleted file mode 100644 index 13927934..00000000 --- a/src/main/java/seng302/model/stream/StreamParser.java +++ /dev/null @@ -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> markLocations = new ConcurrentHashMap<>(); - public static ConcurrentHashMap> 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 boats = new ConcurrentHashMap<>(); - private static Map 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() { - @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() { - @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 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 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 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; - } - } -} - diff --git a/src/main/java/seng302/model/stream/StreamReceiver.java b/src/main/java/seng302/model/stream/StreamReceiver.java index b455cdf0..c0b84f4f 100644 --- a/src/main/java/seng302/model/stream/StreamReceiver.java +++ b/src/main/java/seng302/model/stream/StreamReceiver.java @@ -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) { StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1"); diff --git a/src/main/java/seng302/model/stream/XMLParser.java b/src/main/java/seng302/model/stream/XMLParser.java deleted file mode 100644 index 8c023f06..00000000 --- a/src/main/java/seng302/model/stream/XMLParser.java +++ /dev/null @@ -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 participants; - private ArrayList allMarks; - private ArrayList nonDuplicateMarks; - private ArrayList compoundMarkSequence; - private ArrayList courseLimit; - - // ensures there's no duplicate marks. - private List seenSourceIDs = new ArrayList(); - - /** - * 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 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 getParticipants() { - return participants; - } - - /** - * @return Returns ALL compound marks as stated in the RaceXML (INCLUDING DUPLICATE MARKS) - */ - public List getAllCompoundMarks() { - return allMarks; - } - - /** - * @return Returns Marks from the race XML without any duplicates - */ - public List getNonDupCompoundMarks() { - return nonDuplicateMarks; - } - - public ArrayList getCompoundMarkSequence() { - return compoundMarkSequence; - } - - public ArrayList 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 zoneLimits;// will only contain 5 elements. Limits 1-5 - - //Boats - ArrayList boats; - //Competing boats - Map 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 getZoneLimits() { - return zoneLimits; - } - - public ArrayList getBoats() { - return boats; - } - - public Map getCompetingBoats() { - return competingBoats; - } - - } - -} \ No newline at end of file diff --git a/src/main/java/seng302/model/stream/packets/BoatPositionPacket.java b/src/main/java/seng302/model/stream/packets/BoatPositionPacket.java deleted file mode 100644 index 1320c394..00000000 --- a/src/main/java/seng302/model/stream/packets/BoatPositionPacket.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/seng302/model/stream/packets/PacketType.java b/src/main/java/seng302/model/stream/packets/PacketType.java index 693aa286..e7f55b49 100644 --- a/src/main/java/seng302/model/stream/packets/PacketType.java +++ b/src/main/java/seng302/model/stream/packets/PacketType.java @@ -1,7 +1,5 @@ package seng302.model.stream.packets; -import java.lang.reflect.Type; - /** * Created by Kusal on 4/24/2017. */ @@ -9,7 +7,9 @@ public enum PacketType { HEARTBEAT, RACE_STATUS, DISPLAY_TEXT_MESSAGE, - XML_MESSAGE, + RACE_XML, + REGATTA_XML, + BOAT_XML, RACE_START_STATUS, YACHT_EVENT_CODE, YACHT_ACTION_CODE, @@ -21,7 +21,7 @@ public enum PacketType { BOAT_ACTION, OTHER; - public static PacketType assignPacketType(int packetType){ + public static PacketType assignPacketType(int packetType, byte[] payload){ switch(packetType){ case 1: return HEARTBEAT; @@ -30,7 +30,14 @@ public enum PacketType { case 20: return DISPLAY_TEXT_MESSAGE; 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: return RACE_START_STATUS; case 29: diff --git a/src/main/java/seng302/model/stream/packets/StreamPacket.java b/src/main/java/seng302/model/stream/packets/StreamPacket.java index c6ee769a..41442e84 100644 --- a/src/main/java/seng302/model/stream/packets/StreamPacket.java +++ b/src/main/java/seng302/model/stream/packets/StreamPacket.java @@ -13,7 +13,7 @@ public class StreamPacket { private 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.timeStamp = timeStamp; this.payload = payload; diff --git a/src/main/java/seng302/model/stream/parsers/MarkRoundingData.java b/src/main/java/seng302/model/stream/parsers/MarkRoundingData.java new file mode 100644 index 00000000..a2a624a8 --- /dev/null +++ b/src/main/java/seng302/model/stream/parsers/MarkRoundingData.java @@ -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; + } +} diff --git a/src/main/java/seng302/model/stream/parsers/PositionUpdateData.java b/src/main/java/seng302/model/stream/parsers/PositionUpdateData.java new file mode 100644 index 00000000..5399c135 --- /dev/null +++ b/src/main/java/seng302/model/stream/parsers/PositionUpdateData.java @@ -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; + } +} diff --git a/src/main/java/seng302/model/stream/parsers/RaceStartData.java b/src/main/java/seng302/model/stream/parsers/RaceStartData.java new file mode 100644 index 00000000..848b9e85 --- /dev/null +++ b/src/main/java/seng302/model/stream/parsers/RaceStartData.java @@ -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; + } +} diff --git a/src/main/java/seng302/model/stream/parsers/RaceStatusData.java b/src/main/java/seng302/model/stream/parsers/RaceStatusData.java new file mode 100644 index 00000000..94556b08 --- /dev/null +++ b/src/main/java/seng302/model/stream/parsers/RaceStatusData.java @@ -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 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 getBoatData () { + return boatData; + } +} diff --git a/src/main/java/seng302/model/stream/parsers/StreamParser.java b/src/main/java/seng302/model/stream/parsers/StreamParser.java new file mode 100644 index 00000000..e46ceadf --- /dev/null +++ b/src/main/java/seng302/model/stream/parsers/StreamParser.java @@ -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 extractDisplayMessage(StreamPacket packet) { + if (packet.getType() != PacketType.DISPLAY_TEXT_MESSAGE) + return null; + List 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 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 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; + } +} + diff --git a/src/main/java/seng302/model/stream/parsers/xml/RaceXMLData.java b/src/main/java/seng302/model/stream/parsers/xml/RaceXMLData.java new file mode 100644 index 00000000..3b7e5ef7 --- /dev/null +++ b/src/main/java/seng302/model/stream/parsers/xml/RaceXMLData.java @@ -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 participants; + private Map compoundMarks; + private List markSequence; + private List courseLimit; + private Map individualMarks; + + RaceXMLData(List participants, List compoundMarks, List markSequence, + List 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 getParticipants() { + return participants; + } + + public Map getCompoundMarks() { + return compoundMarks; + } + + public List getMarkSequence() { + return markSequence; + } + + public List getCourseLimit() { + return courseLimit; + } +} diff --git a/src/main/java/seng302/model/stream/parsers/xml/RegattaXMLData.java b/src/main/java/seng302/model/stream/parsers/xml/RegattaXMLData.java new file mode 100644 index 00000000..73a4aaa6 --- /dev/null +++ b/src/main/java/seng302/model/stream/parsers/xml/RegattaXMLData.java @@ -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; + } + +} diff --git a/src/main/java/seng302/model/stream/parsers/xml/XMLParser.java b/src/main/java/seng302/model/stream/parsers/xml/XMLParser.java new file mode 100644 index 00000000..35892dfc --- /dev/null +++ b/src/main/java/seng302/model/stream/parsers/xml/XMLParser.java @@ -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 parseBoats(Document doc){ + Map 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 extractCourseLimit(Element docEle) { + List 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 extractMarkOrder (Element docEle) { + List 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 extractParticpantIDs (Element docEle) { + List 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 extractCompoundMarks(Element docEle) { + List 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 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 + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/seng302/visualiser/ClientSocketListener.java b/src/main/java/seng302/visualiser/ClientSocketListener.java new file mode 100644 index 00000000..3646db32 --- /dev/null +++ b/src/main/java/seng302/visualiser/ClientSocketListener.java @@ -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); +} diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index 0f73d5e0..d67ac833 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -8,18 +8,10 @@ import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.Queue; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.zip.CRC32; 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.server.messages.BoatActionMessage; import seng302.server.messages.Message; @@ -29,7 +21,7 @@ import seng302.server.messages.Message; */ public class ClientToServerThread extends Thread { private Queue streamPackets = new ConcurrentLinkedQueue<>(); - private List>> boatPacketListeners = new ArrayList<>(); + private List listeners = new ArrayList<>(); private Socket socket; private InputStream is; @@ -78,10 +70,9 @@ public class ClientToServerThread extends Thread { long computedCrc = checksum.getValue(); long packetCrc = Message.bytesToLong(getBytes(4)); if (computedCrc == packetCrc) { - streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); - for (ListChangeListener cl : boatPacketListeners) { - cl.onChanged(); - } +// streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload)); + for (ClientSocketListener csl : listeners) + csl.newPacket(new StreamPacket(type, payloadLength, timeStamp, payload)); } else { System.err.println("Packet has been dropped"); } @@ -114,12 +105,12 @@ public class ClientToServerThread extends Thread { } } - public void addStreamObserver (ListChangeListener> listChangeListener) { - boatPacketListeners.add(listChangeListener); + public void addStreamObserver (ClientSocketListener streamListener) { + listeners.add(streamListener); } - public void removeStreamObserver (ListChangeListener> listChangeListener) { - boatPacketListeners.remove(listChangeListener); + public void removeStreamObserver (ClientSocketListener streamListener) { + listeners.remove(streamListener); } private int readByte() throws Exception { diff --git a/src/main/java/seng302/visualiser/GameView.java b/src/main/java/seng302/visualiser/GameView.java index 89fbe68f..3a2f4822 100644 --- a/src/main/java/seng302/visualiser/GameView.java +++ b/src/main/java/seng302/visualiser/GameView.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.PriorityBlockingQueue; import javafx.animation.AnimationTimer; +import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.collections.ObservableList; import javafx.fxml.FXMLLoader; import javafx.geometry.Point2D; @@ -19,22 +20,22 @@ import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; -import seng302.visualiser.controllers.RaceViewController; +import seng302.model.Limit; import seng302.visualiser.fxObjects.BoatGroup; import seng302.visualiser.fxObjects.MarkGroup; import seng302.model.Colors; -import seng302.model.Yacht; +import seng302.model.Boat; import seng302.model.map.Boundary; import seng302.model.map.CanvasMap; import seng302.model.mark.GateMark; import seng302.model.mark.Mark; import seng302.model.mark.MarkType; import seng302.model.mark.SingleMark; -import seng302.model.stream.StreamParser; -import seng302.model.stream.XMLParser; -import seng302.model.stream.XMLParser.RaceXMLObject.Limit; -import seng302.model.stream.XMLParser.RaceXMLObject.Participant; -import seng302.model.stream.packets.BoatPositionPacket; +import seng302.model.stream.parsers.StreamParser; +import seng302.model.stream.parsers.xml.XMLParser; +import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Limit; +import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Participant; +import seng302.model.stream.parsers.PositionUpdateData; import seng302.utilities.GeoPoint; import seng302.utilities.GeoUtility; @@ -42,7 +43,7 @@ import seng302.utilities.GeoUtility; * Created by cir27 on 20/07/17. */ public class GameView extends Pane { - private RaceViewController raceViewController; + private ObservableList gameObjects; private ImageView mapImage; @@ -66,7 +67,9 @@ public class GameView extends Pane { private List markGroups = new ArrayList<>(); private List boatGroups = new ArrayList<>(); - private Text FPSdisplay = new Text(); + + private Text fpsDisplay = new Text(); + private Polygon raceBorder = new Polygon(); //FRAME RATE @@ -75,44 +78,24 @@ public class GameView extends Pane { private int frameTimeIndex = 0; private boolean arrayFilled = false; - AnimationTimer timer; + private AnimationTimer timer; private enum ScaleDirection { HORIZONTAL, VERTICAL } - void setup(RaceViewController raceViewController) { - this.raceViewController = raceViewController; - } - - public void initialize() { - raceViewController = new RaceViewController(); + public GameView () { gameObjects = this.getChildren(); // create image view for map, bind panel size to image mapImage = new ImageView(); gameObjects.add(mapImage); mapImage.fitWidthProperty().bind(this.widthProperty()); mapImage.fitHeightProperty().bind(this.heightProperty()); + initializeTimer(); } - 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 initializeTimer () { timer = new AnimationTimer() { private long lastTime = 0; private int FPSCount = 30; @@ -136,24 +119,35 @@ public class GameView extends Pane { frameRate = 1_000_000_000.0 / elapsedNanosPerFrame; if (FPSCount-- == 0) { FPSCount = 30; -// drawFps(frameRate.intValue()); + drawFps(frameRate.intValue()); } } updateGroups(); - if (StreamParser.isRaceFinished()) { - this.stop(); - } 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() { try { // canvas view -> anchor pane -> grid pane -> main view @@ -255,28 +249,28 @@ public class GameView extends Pane { } private void updateBoatGroup(BoatGroup boatGroup) { - PriorityBlockingQueue movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId()); - // giving the movementQueue a 5 packet buffer to account for slightly out of order packets - if (movementQueue.size() > 0) { - try { - BoatPositionPacket positionPacket = movementQueue.take(); +// PriorityBlockingQueue movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId()); +// // giving the movementQueue a 5 packet buffer to account for slightly out of order packets +// if (movementQueue.size() > 0) { +// try { +// PositionUpdateData positionPacket = movementQueue.take(); Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon()); - double heading = 360.0 / 0xffff * positionPacket.getHeading(); +// double heading = 360.0 / 0xffff * positionPacket.getHeading(); boatGroup.setDestination( p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), positionPacket.getTimeValid(), frameRate); - } catch (InterruptedException e){ - e.printStackTrace(); - } +// } catch (InterruptedException e){ +// e.printStackTrace(); // } - } +//// } +// } } private void updateMarkGroup (long raceId, MarkGroup markGroup) { - PriorityBlockingQueue movementQueue = StreamParser.markLocations.get(raceId); + PriorityBlockingQueue movementQueue = StreamParser.markLocations.get(raceId); if (movementQueue.size() > 0){ try { - BoatPositionPacket positionPacket = movementQueue.take(); + PositionUpdateData positionPacket = movementQueue.take(); Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon()); markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId); } catch (InterruptedException e) { @@ -289,7 +283,7 @@ public class GameView extends Pane { * Draws all the boats. */ private void initializeBoats() { - Map boats = StreamParser.getBoats(); + Map boats = StreamParser.getBoats(); Group wakes = new Group(); Group trails = new Group(); Group annotations = new Group(); @@ -301,7 +295,7 @@ public class GameView extends Pane { participantIDs.add(p.getsourceID()); } - for (Yacht boat : boats.values()) { + for (Boat boat : boats.values()) { if (participantIDs.contains(boat.getSourceID())) { boat.setColour(Colors.getColor()); BoatGroup boatGroup = new BoatGroup(boat, boat.getColour()); @@ -336,14 +330,9 @@ public class GameView extends Pane { gameObjects.addAll(markGroups); } -// private void drawFps(int fps){ -// if (raceViewController.isDisplayFps()){ -// FPSdisplay.setVisible(true); -// FPSdisplay.setText(String.format("%d FPS", fps)); -// } else { -// FPSdisplay.setVisible(false); -// } -// } + private void drawFps(int fps){ + fpsDisplay.setText(String.format("%d FPS", fps)); + } /** * 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() { List sortedPoints = new ArrayList<>(); - for (Limit limit : StreamParser.getXmlObject().getRaceXML().getCourseLimit()) { + for (Limit limit : raceData) { sortedPoints.add(limit); } sortedPoints.sort(Comparator.comparingDouble(Limit::getLat)); @@ -512,4 +501,34 @@ public class GameView extends Pane { 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 courseLimit) { + + } } diff --git a/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java b/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java index b9010e8e..e08521d4 100644 --- a/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java +++ b/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java @@ -15,24 +15,24 @@ import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; -import seng302.model.Yacht; -import seng302.model.stream.StreamParser; -import seng302.model.stream.XMLParser.RaceXMLObject.Participant; +import seng302.model.Boat; +import seng302.model.stream.parsers.StreamParser; +import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Participant; public class FinishScreenViewController implements Initializable { @FXML private GridPane finishScreenGridPane; @FXML - private TableView finishOrderTable; + private TableView finishOrderTable; @FXML - private TableColumn posCol; + private TableColumn posCol; @FXML - private TableColumn boatNameCol; + private TableColumn boatNameCol; @FXML - private TableColumn shortNameCol; + private TableColumn shortNameCol; @FXML - private TableColumn countryCol; + private TableColumn countryCol; @Override 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()); // set up data for table - ObservableList data = FXCollections.observableArrayList(); + ObservableList data = FXCollections.observableArrayList(); finishOrderTable.setItems(data); // setting table col data @@ -67,7 +67,7 @@ public class FinishScreenViewController implements Initializable { } // add data to table - for (Yacht boat : StreamParser.getBoatsPos().values()) { + for (Boat boat : StreamParser.getBoatsPos().values()) { if (participantIDs.contains(boat.getSourceID())) { data.add(boat); } diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index 70f348fe..ba0557bb 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -27,7 +27,10 @@ import javafx.stage.Stage; import javafx.stage.StageStyle; import javafx.util.Duration; import javafx.util.StringConverter; +import seng302.model.Corner; +import seng302.model.stream.parsers.xml.RaceXMLData; import seng302.utilities.GeoUtility; +import seng302.visualiser.GameView; import seng302.visualiser.controllers.annotations.Annotation; import seng302.visualiser.controllers.annotations.ImportantAnnotationController; import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate; @@ -38,13 +41,11 @@ import seng302.model.*; import seng302.model.mark.GateMark; import seng302.model.mark.Mark; import seng302.model.mark.SingleMark; -import seng302.model.stream.StreamParser; -import seng302.model.stream.XMLParser; +import seng302.model.stream.parsers.StreamParser; import java.io.IOException; import java.util.*; -import seng302.model.stream.XMLParser.RaceXMLObject.Participant; -import java.util.stream.Collectors; +import seng302.model.stream.parsers.xml.XMLParser.RaceXMLObject.Participant; /** * Created by ptg19 on 29/03/17. @@ -70,21 +71,21 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel @FXML private Button selectAnnotationBtn; @FXML - private ComboBox boatSelectionComboBox; - @FXML - private GameViewController gameViewController; + private ComboBox boatSelectionComboBox; + + //Race Data + private Map participants; + private Map course; + private RaceXMLData raceData; + private GameView gameView; - private static ArrayList startingBoats = new ArrayList<>(); - private boolean displayFps; private Timeline timerTimeline; private Stage stage; - private static HashMap> sparkLineData = new HashMap<>(); - private static ArrayList racingBoats = new ArrayList<>(); + private HashMap> sparkLineData = new HashMap<>(); private ImportantAnnotationsState importantAnnotations; - private Yacht selectedBoat; + private Boat selectedBoat; public void initialize() { - // Load a default important annotation state importantAnnotations = new ImportantAnnotationsState(); @@ -94,19 +95,22 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel raceSparkLine.getYAxis().setTranslateX(-5); raceSparkLine.getYAxis().setAutoRanging(false); sparklineYAxis.setTickMarkVisible(false); - startingBoats = new ArrayList<>(StreamParser.getBoats().values()); - gameViewController.setup(this); - gameViewController.initializeCanvas(); initializeUpdateTimer(); initialiseFPSCheckBox(); initialiseAnnotationSlider(); initialiseBoatSelectionComboBox(); - gameViewController.timer.start(); - selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); + selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); } + public void loadRace (Map 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 @@ -126,33 +130,30 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel try { FXMLLoader fxmlLoader = new FXMLLoader(); Stage stage = new Stage(); - // Set controller - ImportantAnnotationController controller = new ImportantAnnotationController(this, - stage); + ImportantAnnotationController controller = new ImportantAnnotationController( + this, stage + ); fxmlLoader.setController(controller); - // Load FXML and set CSS - fxmlLoader - .setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml")); + fxmlLoader.setLocation( + getClass().getResource("/views/importantAnnotationSelectView.fxml") + ); Scene scene = new Scene(fxmlLoader.load(), 469, 298); scene.getStylesheets().add(getClass().getResource("/css/master.css").toString()); stage.initStyle(StageStyle.UNDECORATED); stage.setScene(scene); stage.show(); - controller.loadState(importantAnnotations); - } catch (IOException e) { e.printStackTrace(); } } - private void initialiseFPSCheckBox() { - displayFps = true; - toggleFps.selectedProperty().addListener( - (observable, oldValue, newValue) -> displayFps = !displayFps); + toggleFps.selectedProperty().addListener((obs, oldVal, newVal) -> + gameView.setFPSVisibility(toggleFps.isSelected()) + ); } private void initialiseAnnotationSlider() { @@ -189,8 +190,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel }); annotationSlider.valueProperty().addListener((obs, oldval, newVal) -> - setAnnotations((int) annotationSlider.getValue())); - + setAnnotations((int) annotationSlider.getValue()) + ); 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 */ - void updateSparkLine(){ + private void updateSparkLine(){ // Collect the racing boats that aren't already in the chart - ArrayList sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceID()) - && yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new)); + List sparkLineCandidates = new ArrayList<>(); + 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 - racingBoats = startingBoats.stream().filter(yacht -> - yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new)); - sparklineYAxis.setUpperBound(racingBoats.size() + 1); + sparklineYAxis.setUpperBound(participants.size() + 1); // Create a new data series for new boats sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> { Series yachtData = new Series<>(); - yachtData.setName(yacht.getBoatName()); - yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition()))); + yachtData.setName(yacht.getSourceID().toString()); + yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + participants.size() - Double.parseDouble(yacht.getPosition()))); 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) raceSparkLine.setCreateSymbols(false); - positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> { - raceSparkLine.getData().add(spark); - spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName())); - }); + positions + .stream() + .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 - * @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 */ - public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){ - XYChart.Series positionData = sparkLineData.get(yacht.getSourceID()); - positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition()))); + public void updateYachtPositionSparkline(Boat boat, Integer legNumber){ + XYChart.Series positionData = sparkLineData.get(boat.getSourceID()); + 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 - * @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 */ - private String getBoatColorAsRGB(String boatName){ - Color color = Color.WHITE; - for (Yacht yacht: startingBoats){ - if (Objects.equals(yacht.getBoatName(), boatName)){ - color = yacht.getColour(); - } - } + private String getBoatColorAsRGB(String boatId){ + Color color = participants.get(Integer.valueOf(boatId)).getColour(); 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", (int)( color.getRed() * 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. * Updates of each of these attributes are called ONCE EACH SECOND */ @@ -286,9 +291,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel updateWindDirection(); updateOrder(); updateBoatSelectionComboBox(); + updateSparkLine(); }) ); - // Start the timer timerTimeline.playFromStart(); } @@ -302,9 +307,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel * @return The next Mark or null if none found */ private Mark getNextMark(BoatGroup bg) { - Integer legNumber = bg.getBoat().getLegNumber(); - List markSequence = StreamParser.getXmlObject().getRaceXML().getCompoundMarkSequence(); + Integer legNumber = bg.getBoat().getLegNumber(); + List markSequence = raceData.getMarkSequence(); if (legNumber == 0) { return null; @@ -312,18 +317,11 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel return null; } - for (XMLParser.RaceXMLObject.Corner corner : markSequence) { + for (Corner corner : markSequence) { if (legNumber + 2 == corner.getSeqID()) { - Integer thisCompoundMarkID = corner.getCompoundMarkID(); - - for (Mark mark : StreamParser.getXmlObject().getRaceXML().getAllCompoundMarks()) { - if (mark.getCompoundMarkID() == thisCompoundMarkID) { - return mark; - } - } + return raceData.getCompoundMarks().get(corner.getCompoundMarkID()); } } - return null; } @@ -355,8 +353,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel * in the boat selection combo box */ private void updateBoatSelectionComboBox() { - ObservableList observableBoats = FXCollections - .observableArrayList(StreamParser.getBoatsPos().values()); + ObservableList observableBoats = FXCollections.observableArrayList(); + observableBoats.addAll(participants.values()); boatSelectionComboBox.setItems(observableBoats); } @@ -371,15 +369,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); // list of racing boat id - ArrayList participants = StreamParser.getXmlObject().getRaceXML() - .getParticipants(); - ArrayList participantIDs = new ArrayList<>(); - for (Participant p : participants) { - participantIDs.add(p.getsourceID()); - } 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 (boat.getBoatStatus() == 3) { // 3 is finish status Text textToAdd = new Text(boat.getPosition() + ". " + @@ -397,15 +389,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel } } } else { - for (Yacht boat : StreamParser.getBoats().values()) { - if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing - Text textToAdd = new Text(boat.getPosition() + ". " + - boat.getShortName() + " "); - textToAdd.setFill(Paint.valueOf("#d3d3d3")); - textToAdd.setStyle(""); - positionVbox.getChildren().add(textToAdd); - } - } + participants.forEach((id, boat) ->{ + Text textToAdd = new Text(boat.getPosition() + ". " + + boat.getShortName() + " "); + textToAdd.setFill(Paint.valueOf("#d3d3d3")); + textToAdd.setStyle(""); + 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 //We dont want to set the selected value if the values are updated but nothing clicked (null) if (newValue != null && newValue != selectedBoat) { - Yacht thisYacht = (Yacht) newValue; - setSelectedBoat(thisYacht); + Boat thisBoat = (Boat) newValue; + setSelectedBoat(thisBoat); } }); } @@ -529,9 +519,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel contentAnchorPane.getChildren().addAll((Pane) loader.load()); } catch (javafx.fxml.LoadException e) { - System.err.println(e.getCause()); + System.err.println(e.getCause().toString()); } 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; } - - boolean isDisplayFps() { - return displayFps; - } - private void setAnnotations(Integer annotationLevel) { switch (annotationLevel) { // No Annotations case 0: - for (BoatGroup bg : gameViewController.getBoatGroups()) { - bg.setVisibility(false, false, false, false, false, false); - } + gameView.setAnnotationVisibilities( + false, false, false, false, false, false + ); break; // Important Annotations case 1: - for (BoatGroup bg : gameViewController.getBoatGroups()) { - bg.setVisibility( - importantAnnotations.getAnnotationState(Annotation.NAME), - importantAnnotations.getAnnotationState(Annotation.SPEED), - importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK), - importantAnnotations.getAnnotationState(Annotation.LEGTIME), - importantAnnotations.getAnnotationState(Annotation.TRACK), - importantAnnotations.getAnnotationState(Annotation.WAKE) - ); - } + gameView.setAnnotationVisibilities( + importantAnnotations.getAnnotationState(Annotation.NAME), + importantAnnotations.getAnnotationState(Annotation.SPEED), + importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK), + importantAnnotations.getAnnotationState(Annotation.LEGTIME), + importantAnnotations.getAnnotationState(Annotation.TRACK), + importantAnnotations.getAnnotationState(Annotation.WAKE) + ); break; // All Annotations case 2: - for (BoatGroup bg : gameViewController.getBoatGroups()) { - bg.setVisibility(true, true, true, true, true, true); - } + gameView.setAnnotationVisibilities( + true, true, true, true, true, true + ); 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 * - * @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()) { //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. - if (bg.getBoat().getHullID().equals(yacht.getHullID())) { + if (bg.getBoat().getHullID().equals(boat.getHullID())) { updateLaylines(bg); bg.setIsSelected(true); - selectedBoat = yacht; + selectedBoat = boat; } else { bg.setIsSelected(false); } @@ -641,4 +624,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel return sparkLineData.containsKey(yachtId); } + public void updateRaceData (RaceXMLData raceData) { + this.raceData = raceData; + gameView.updateBorder(raceData.getCourseLimit()); + } + } \ No newline at end of file diff --git a/src/main/java/seng302/visualiser/controllers/StartScreenController.java b/src/main/java/seng302/visualiser/controllers/StartScreenController.java index fc836888..3ce16e52 100644 --- a/src/main/java/seng302/visualiser/controllers/StartScreenController.java +++ b/src/main/java/seng302/visualiser/controllers/StartScreenController.java @@ -59,7 +59,7 @@ public class StartScreenController { new GameState(ipAddress); new MainServerThread().start(); ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950); - controller.setClientToServerThread(clientToServerThread); +// controller.setClientToServerThread(clientToServerThread); clientToServerThread.start(); // new GameServerThread("Fuck you"); // get the lobby controller so that we can pass the game server thread to it diff --git a/src/main/java/seng302/visualiser/controllers/client/ClientController.java b/src/main/java/seng302/visualiser/controllers/client/ClientController.java index 047994b5..a9011680 100644 --- a/src/main/java/seng302/visualiser/controllers/client/ClientController.java +++ b/src/main/java/seng302/visualiser/controllers/client/ClientController.java @@ -1,78 +1,174 @@ package seng302.visualiser.controllers.client; -import javafx.beans.value.ChangeListener; -import javafx.scene.Node; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.AnchorPane; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.ZoneId; +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 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.server.messages.BoatActionMessage; -import seng302.server.messages.BoatActionType; +import seng302.visualiser.ClientSocketListener; import seng302.visualiser.ClientToServerThread; +import seng302.visualiser.controllers.RaceViewController; /** * Created by cir27 on 20/07/17. */ public class ClientController { - Pane holderPane; - ClientToServerThread socketThread; + private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); + + private Pane holderPane; + private ClientToServerThread socketThread; + private ClientSocketListener socketListener; + + private RaceViewController raceView; + + private Map allBoatsMap; + private Map racingBoats = new HashMap<>(); + private RegattaXMLData regattaData; + private RaceXMLData raceData; public ClientController (String ipAddress, Pane holder) { this.holderPane = holder; socketThread = new ClientToServerThread(ipAddress, 4950); 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) { - 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; + switch (packet.getType()) { + case RACE_STATUS: + processRaceStatusUpdate(StreamParser.extractRaceStatus(packet)); + break; + + case REGATTA_XML: + regattaData = XMLParser.parseRegatta( + StreamParser.extractXmlMessage(packet) + ); + DATE_TIME_FORMAT.setTimeZone( + TimeZone.getTimeZone( + ZoneId.ofOffset("UTC", ZoneOffset.ofHours(regattaData.getUtcOffset())) + ) + ); + startRaceIfAllDataRecieved(); + break; + + case RACE_XML: + raceData = XMLParser.parseRace( + StreamParser.extractXmlMessage(packet) + ); + if (raceView != null) { + raceView.updateRaceData(raceData); + } + startRaceIfAllDataRecieved(); + break; + + case BOAT_XML: + allBoatsMap = XMLParser.parseBoats( + StreamParser.extractXmlMessage(packet) + ); + startRaceIfAllDataRecieved(); + break; + + case RACE_START_STATUS: + RaceStartData raceStartData = StreamParser.extractRaceStartStatus(packet); + break; + + case BOAT_LOCATION: + PositionUpdateData positionData = StreamParser.extractBoatLocation(packet); + updatePosition(positionData); + 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(); } } diff --git a/src/main/java/seng302/visualiser/fxObjects/BoatAnnotations.java b/src/main/java/seng302/visualiser/fxObjects/BoatAnnotations.java index 815f44e6..e0e3a040 100644 --- a/src/main/java/seng302/visualiser/fxObjects/BoatAnnotations.java +++ b/src/main/java/seng302/visualiser/fxObjects/BoatAnnotations.java @@ -5,8 +5,8 @@ import javafx.scene.Group; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; -import seng302.model.Yacht; -import seng302.model.stream.StreamParser; +import seng302.model.Boat; +import seng302.model.stream.parsers.StreamParser; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -34,9 +34,9 @@ public class BoatAnnotations extends Group{ private Text estTimeToNextMarkObject; private Text legTimeObject; - private Yacht boat; + private Boat boat; - BoatAnnotations (Yacht boat, Color theme) { + BoatAnnotations (Boat boat, Color theme) { super.setCache(true); this.boat = boat; background.setX(BACKGROUND_X); @@ -83,10 +83,10 @@ public class BoatAnnotations extends Group{ } 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) { - DateFormat format = new SimpleDateFormat("mm:ss"); String timeToNextMark = format .format(boat.getTimeTillNext() - StreamParser.getCurrentTimeLong()); estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark); @@ -95,7 +95,6 @@ public class BoatAnnotations extends Group{ } if (boat.getMarkRoundTime() != null) { - DateFormat format = new SimpleDateFormat("mm:ss"); String elapsedTime = format .format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundTime()); legTimeObject.setText("Last mark: " + elapsedTime); diff --git a/src/main/java/seng302/visualiser/fxObjects/BoatGroup.java b/src/main/java/seng302/visualiser/fxObjects/BoatGroup.java index 2542fe81..cbf51e9f 100644 --- a/src/main/java/seng302/visualiser/fxObjects/BoatGroup.java +++ b/src/main/java/seng302/visualiser/fxObjects/BoatGroup.java @@ -10,12 +10,12 @@ import javafx.scene.shape.Line; import javafx.scene.shape.Polygon; import javafx.scene.transform.Rotate; import seng302.visualiser.controllers.GameViewController; -import seng302.model.Yacht; +import seng302.model.Boat; import seng302.utilities.GeoUtility; import seng302.model.mark.GateMark; import seng302.model.mark.Mark; 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 @@ -38,7 +38,7 @@ public class BoatGroup extends Group { private Double lastRotation = 0.0; private long framesToMove; //Graphical objects - private Yacht boat; + private Boat boat; private Group lineGroup = new Group(); private Polygon boatPoly; private Wake wake; @@ -58,7 +58,7 @@ public class BoatGroup extends Group { * to tell which BoatGroup to update. * @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; this.boat = boat; 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 * polygon. */ - public BoatGroup(Yacht boat, Color color, double... points) { + public BoatGroup(Boat boat, Color color, double... points) { destinationSet = false; this.boat = boat; 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 with the wind. */ - if (boatLineFuncResult == windLineFuncResult) { - return true; - } else { - return false; - } - + return boatLineFuncResult.equals(windLineFuncResult); } - public void setIsSelected(Boolean isSelected) { this.isSelected = isSelected; setLineGroupVisible(isSelected); @@ -275,7 +269,9 @@ public class BoatGroup extends Group { 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); this.wake.setVisible(wake); this.lineGroup.setVisible(trail); @@ -306,7 +302,7 @@ public class BoatGroup extends Group { return laylines; } - public Yacht getBoat() { + public Boat getBoat() { return boat; }