Compare commits

..

272 Commits

Author SHA1 Message Date
Peter Galloway b529d621e9 changed course size to 720x720 2017-05-04 16:42:23 +12:00
William Muir 24667991f1 Merge on to master
Final Push
2017-05-04 14:39:29 +12:00
William Muir f2c4929656 Merge remote-tracking branch 'origin/master' 2017-05-04 14:22:49 +12:00
William Muir 4c038a823a Merge branch 'master' into develop
# Conflicts:
#	.mailmap
2017-05-04 14:22:20 +12:00
Alistair McIntyre 3080c1bf27 Merge remote-tracking branch 'origin/Story28_GetCorrectTimeZone' into develop
# Conflicts:
#	src/main/java/seng302/controllers/Controller.java
2017-05-04 14:15:05 +12:00
Alistair McIntyre 59809c39ea Began figuring out how to implement XML data in place of mock data.
#story[820] #pair[ajm412, wmu16]
2017-05-04 14:13:03 +12:00
Calum 9b063190ce Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop
# Conflicts:
#	src/test/java/seng302/models/MarkGroupTest.java
2017-05-04 14:04:52 +12:00
Calum 49f2398669 Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop 2017-05-04 14:01:32 +12:00
Calum d7a4d20ceb Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop
# Conflicts:
#	src/test/java/seng302/models/MarkGroupTest.java
2017-05-04 14:01:19 +12:00
Michael Rausch 0855e268c8 Removed another test that was causing failing due CI not having disply 2017-05-04 14:01:06 +12:00
Calum 49b8d75aea Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop
# Conflicts:
#	src/test/java/seng302/models/BoatGroupTest.java
2017-05-04 14:00:44 +12:00
Calum b1a9a7845b Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop
# Conflicts:
#	src/test/java/seng302/models/BoatGroupTest.java
2017-05-04 14:00:24 +12:00
Michael Rausch b1e749bafb Removed test that was failing due to CI not having a display 2017-05-04 13:59:44 +12:00
Michael Rausch f6fc6c0693 Merge remote-tracking branch 'origin/develop' into develop 2017-05-04 13:53:49 +12:00
Calum ad1371bce0 Commented out tests that were causing build failures.
Issue is related to tests failing to get the source code from our project needed to build. They run
fine when run from the IDE.
2017-05-04 13:52:47 +12:00
Michael Rausch e77df0a5dd Changed port number and added standalone flag
- can use -standalone to just run the server
- Changed port number as to not conflict with some web servers
2017-05-04 13:52:36 +12:00
Zhi You Tan 6935bd514e Reimplemented race position in the race view. Removed Boat class and Yacht class is replaced completely. Race position "-" shows properly on start screen. Removed BoatTest and TeamsParserTest. Fixed estimated time till finish on server.
#story[818]
2017-05-04 13:50:06 +12:00
Kusal Ekanayake a4cc5f222c Refactored course boundary to be a shade rather than a line and made the stream parser and stream receiver exit gracefully before the app closes.
#story[820]
2017-05-04 13:29:53 +12:00
Calum a0bb7b85b4 potential fix for build failures 2017-05-04 13:26:29 +12:00
Calum fd8ed92f88 Fix for wakes on internal data
#bug
2017-05-04 13:20:50 +12:00
Calum c7b6261602 Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop 2017-05-04 12:46:07 +12:00
Calum 3aefb14faf Added testing. 2017-05-04 12:46:01 +12:00
Michael Rausch 8521b68855 Server thread stops when window has been closed 2017-05-04 12:35:36 +12:00
Michael Rausch e37b244f45 Fixed a bug where boat statuses were being sent incorrectly
- Boats now have an estimated time until finish
- Boat packets are being sent correctly

Tags: #story[829]
2017-05-04 12:27:59 +12:00
Calum 7f40fb6283 Fixed bug left in last commit 2017-05-04 11:00:57 +12:00
Calum 83316f7a17 Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/Controller.java
2017-05-04 10:48:06 +12:00
Calum a56e55ae70 Fix for some movement on racestart issues
#bug
2017-05-04 10:38:04 +12:00
Zhi You Tan 07234ee33a Updated start screen team list to show position in race. Created Yacht class to replace Boat class. Removed Boat class from XMLParser. Removed unused BoatParser.java.
#story[572]
2017-05-04 04:16:16 +12:00
Michael Rausch 145d59df45 Removed unused imports 2017-05-04 01:18:41 +12:00
Michael Rausch 7e8c3af9ce Removed extra 'minutes' labels 2017-05-04 01:08:18 +12:00
Michael Rausch aa6ef72670 Merge remote-tracking branch 'origin/develop' into develop 2017-05-03 23:27:35 +12:00
Haoming Yin ed8781b382 Added method to check if a boat is finished the race or not.
- updated server if all boats finish the race, then start sending race finished message.
- race simulator will terminate if all boats finish the race, and prints log.

#story[715]
2017-05-03 22:44:25 +12:00
Michael Rausch 7a5f4e8f8c Removed minutes label from timestamp
Tags: #story[820]
2017-05-03 22:16:03 +12:00
Michael Rausch d992422efd Various bug fixes
- Made canvas fill entire screen
- Made window scale to screens that aren't 1920x1080
- Changed boat speeds in mock so they aren't too fast
- Added command line options to switch server

Tags: #story[829]
2017-05-03 21:56:51 +12:00
Haoming Yin f0d6312fa5 Fix bugs that server doesn't send boat location before and after the race.
- server will sends boat location not only during the race, but also before the race and after all boats have finished the race.
- refactored simulator so that it runs at the begining to send boat location, and if its "isStarted" set to true, then it starts moving the boats.

#story[715]
2017-05-02 22:51:35 +12:00
Zhi You Tan 772ece25a0 Implemented real time race clock on the start screen using data from stream.
#story[594]
2017-05-02 22:39:33 +12:00
Zhi You Tan d063a41ad4 Merge remote-tracking branch 'origin/wake_remake' into develop
# Conflicts:
#	src/main/java/seng302/App.java
2017-05-02 22:09:03 +12:00
William Muir 6d02f05f05 Quick change to stop adding course markers as they are dynamic and add them in the previous way 2017-05-02 21:51:04 +12:00
William Muir b3a89279d6 Quick change to stop adding course markers as they are dynamic and add them in the previous way 2017-05-02 21:31:38 +12:00
William Muir 4432ba26e5 Merge remote-tracking branch 'origin/develop' into develop 2017-05-02 21:12:22 +12:00
William Muir fe824a8f71 Finished implementing drawing of border from XML
Border is drawn onto canvas, it is static.

#story[469]
2017-05-02 21:05:53 +12:00
Zhi You Tan 8233b75e05 Fixed the start screen team list after merging. Team list shows boats competing in event again.
#story[572]
2017-05-02 18:52:31 +12:00
Zhi You Tan 3af15b2b95 Updated wind direction on race view controller so it responds to the stream.
#story[818]
2017-05-02 18:02:44 +12:00
Haoming Yin a4dfcca302 Fixed a boat bearing bug.
- server should send LastPastCorner's heading bearing, instead of headingCorner's bearing

#story[715]
2017-05-02 14:51:44 +12:00
Kusal Ekanayake 04ce6f6103 Removed white behind fps counter 2017-05-02 14:18:40 +12:00
Kusal Ekanayake 178af141f0 Refactoring and documentation
#story[820]
2017-05-02 14:08:25 +12:00
William Muir c5c2d4375d Changed the canvas controller to read marks from the XML as received from the server (PROTOTYPE)
Gates appear to draw in the correct places although a bit jittery for some reason

Boundary marks are a bit sporadic and still need lines drawn in between

Large refactor of canvas controller and XMLParser still required if these two are going to work together well. Currently canvas controller is a bit of a mess.

#story[469]
2017-05-01 23:10:15 +12:00
William Muir 3aa183042f Merge remote-tracking branch 'origin/wake_remake' into wake_remake 2017-05-01 22:00:15 +12:00
William Muir 944755fde1 Merge branch 'wake_remake' of /home/cosc/student/wmu16/Documents/300/SENG302/team-13 with conflicts. 2017-05-01 22:00:12 +12:00
Peter cd78c35bf6 Merge remote-tracking branch 'origin/wake_remake' into wake_remake 2017-05-01 20:37:58 +12:00
Peter Galloway f8d003002b fixing error from merge #story[820] 2017-05-01 20:37:46 +12:00
Peter Galloway 56fab768f3 fixing error from merge #story[820] 2017-05-01 20:37:26 +12:00
Peter Galloway c92744f21c Merge branch 'wake_remake' into develop 2017-05-01 20:35:15 +12:00
Peter Galloway 1f71fd1967 Merge branch 'wake_remake' into Story30b_correcting_boat_movement
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/RaceObject.java
#	src/main/java/seng302/models/mark/MarkGroup.java
#	src/main/java/seng302/models/parsers/StreamParser.java
2017-05-01 20:33:09 +12:00
Peter Galloway 33ae7beeb4 merging with wake remake #story[820] 2017-05-01 20:09:51 +12:00
Peter Galloway 130efa3a51 Merge branch 'wake_remake' into Story30b_correcting_boat_movement
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/parsers/StreamParser.java
2017-05-01 19:09:09 +12:00
Peter Galloway 7df55fc1a3 problems appear to be fixed and the boats are updating properly from the timeValid field of the boat location. #story[820] 2017-05-01 18:55:21 +12:00
Zhi You Tan 3adadcc1e1 Updated the timer to show two decimals for the seconds.
#story[572]
2017-05-01 18:49:58 +12:00
Kusal Ekanayake 5b027a29d8 Merge remote-tracking branch 'origin/wake_remake' into wake_remake 2017-05-01 18:35:56 +12:00
Kusal Ekanayake 2a9d0fb82c Removed 2 bad import statements that halted the build form building. 2017-05-01 18:35:48 +12:00
Zhi You Tan ee6a543f8d Fixed timer separator instead of dot to semicolon, and fixed the timer position in race canvas, and updated the start screen so it does not grow vertically
#story[572]
2017-05-01 18:35:17 +12:00
Kusal Ekanayake d927531354 Removed broken time extracting method and replaced it with currently existing long extractor. Added speed to the setDestination method for the raceObject abstract class.
#story[820]
2017-05-01 18:22:08 +12:00
Haoming Yin 1d47df09eb Merge branch 'merge_branch_front' into wake_remake
# Conflicts:
#	src/main/java/seng302/App.java
2017-05-01 17:46:16 +12:00
Haoming Yin a9709c4f84 Merged the mock server to visualisation.
#story[715] #story[716]
2017-05-01 17:42:07 +12:00
Alistair McIntyre a06806c42d Merge remote-tracking branch 'origin/wake_remake' into wake_remake 2017-05-01 17:35:51 +12:00
Alistair McIntyre 57de058582 Began figuring out how to implement XML data in place of mock data.
#story[820] #pair[ajm412, wmu16]
2017-05-01 17:35:39 +12:00
Haoming Yin 978493853d Merge branch 'Story29' into merge_branch_front
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/Boat.java
#	src/main/java/seng302/models/Colors.java
#	src/main/java/seng302/models/Event.java
#	src/main/java/seng302/models/Race.java
#	src/main/java/seng302/models/mark/GateMark.java
#	src/main/java/seng302/models/mark/Mark.java
#	src/main/java/seng302/models/mark/MarkType.java
#	src/main/java/seng302/models/mark/SingleMark.java
#	src/main/java/seng302/models/parsers/CourseParser.java
#	src/main/java/seng302/models/parsers/TeamsParser.java
#	src/main/resources/views/MainView.fxml
#	src/main/resources/views/RaceView.fxml
#	src/test/java/seng302/BoatTest.java
#	src/test/java/seng302/ColorsTest.java
#	src/test/java/seng302/EventTest.java
#	src/test/java/seng302/models/mark/MarkTest.java
#	src/test/java/seng302/models/parsers/CourseParserTest.java
2017-05-01 17:06:17 +12:00
Peter Galloway a5ca9218da Discovered the time valid timestamp in the boat location packet is quite inconsistent and either the stream or my implementation is making the display really buggy. Because the way it was before I changed things is more reliable at the moment, I have decided to wait until our mock stream is merged before continuing this development. #story[820] 2017-05-01 16:56:53 +12:00
Kusal Ekanayake 5fe330bfbb Boat trials and wakes now work with both fast and slow data sets.
Instead of fixed, hard coded thresholds and scale factors dynamically changing values
that scale with the onscreen movement are used to determine how graphical objects
are drawn.

#implement #story[816]
2017-05-01 16:41:58 +12:00
Zhi You Tan c80cff87f7 Updated import statements after merge 2017-05-01 16:02:09 +12:00
Zhi You Tan 9a864cc2bd Merge remote-tracking branch 'origin/Mark_to_MarkGroup' into wake_remake
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/Wake.java
#	src/main/java/seng302/models/parsers/StreamParser.java
#	src/main/resources/views/MainView.fxml
2017-05-01 15:59:14 +12:00
Alistair McIntyre c07f13180f Merged Story 30b branch into 30c branch
#story[820]
2017-05-01 15:32:23 +12:00
Alistair McIntyre f672eafd6d Merge remote-tracking branch 'origin/Story30A_InputStreamCheckingHead' into wake_remake
# Conflicts:
#	src/main/java/seng302/models/parsers/StreamPacket.java
#	src/main/java/seng302/models/parsers/StreamParser.java
#	src/main/java/seng302/models/parsers/StreamReceiver.java
2017-05-01 15:27:51 +12:00
Calum 6a361c0d4b Documented some RaceObject classes.
#documentation
2017-05-01 14:30:41 +12:00
alistairjmcintyre b597f010dc Added some missing fields. Added missing getters.
#story[820]
2017-05-01 00:36:10 +12:00
Michael Rausch a670f677e9 Added other email to .mailmap 2017-05-01 00:10:07 +12:00
Michael Rausch a77423b937 Added other email to .mailmap 2017-05-01 00:07:56 +12:00
Michael Rausch 50083a9297 Added tests for reverse and intToBytes
#story[829]
2017-04-30 23:54:32 +12:00
Michael Rausch d5aa430d4a Fixed a method that was converting the timestamp bytes incorrectly
- If an argument is passed to the application, it will use the internal mock server
Tags: #story[820]
2017-04-30 23:41:21 +12:00
Michael Rausch e7f9954970 Removed unneeded files, also fixed heading calculation
Tags #story[829]
2017-04-30 23:29:15 +12:00
William Muir ec57851de2 Created annotation slider to display different levels of annotations
Removed toggleAnnotations method as abstract from race object and made only for boat group as it didnt make sense for markgroup, at least not currently as they have no annotations to show

#story[558]
2017-04-30 23:24:24 +12:00
Calum 0eb767b615 Finished tweaking wakes. Made marker movement smooth.
#implement #story[818]
2017-04-30 22:34:49 +12:00
Calum 45b77c05d4 Improvements to wake 2017-04-30 19:32:29 +12:00
Calum b9900925b8 Fixing wakes, bug caused by attempting to fix a issue with jittery boats actually caused by parser.
#bug
2017-04-30 19:00:07 +12:00
cir27 d94290c58d Merge branch 'Mark_to_MarkGroup' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into Mark_to_MarkGroup 2017-04-30 18:15:24 +12:00
cir27 f50aabff7b Documentation added to graphics classes.
#document
2017-04-30 18:15:16 +12:00
Michael Rausch d07c660eb9 Merge remote-tracking branch 'origin/Story29' into Story29 2017-04-30 17:47:05 +12:00
Michael Rausch 6491efec4c Fixed race status sent in race status messages
#story[829]
2017-04-30 17:46:56 +12:00
Zhi You Tan 25038da2a1 Created BoatsParser.java to parse boats from server boat.xml and created a table on the start screen to display all the teams from server
#story[572]
2017-04-30 17:17:47 +12:00
William Muir 85f461c88c Fixed bug so XML messages are located and sent properly on the server
Imported appache commons to read inputstream to a bytearray

#story[829]
2017-04-30 17:08:06 +12:00
Michael Rausch 1cf55f3e96 Fixed an issue where buffers aren't being sent properly
Tags #Story[829]
2017-04-30 16:16:44 +12:00
Michael Rausch 9a995ddcc1 Boat status changes to finished when a boat finishes the race
Tags: #story[829]
2017-04-30 01:55:49 +12:00
alistairjmcintyre 0b2ef3de00 XML data types are done. Easily navigated for future use. Some documentation has been done, tests aren't yet completed just yet.
#story[820]
2017-04-30 00:52:18 +12:00
Peter 99e50aa7ac Merge remote-tracking branch 'origin/Story30b_correcting_boat_movement' into Story30b_correcting_boat_movement 2017-04-29 20:21:49 +12:00
Peter Galloway a898290c0b partway through fixing boat movement to be updated from the data valid timestamp rather than the data sent timestamp #pair[kre39, ptg19] #story[820] 2017-04-29 20:21:26 +12:00
Kusal Ekanayake 6cbff1097b Fixed error'd tests which were based off old xml data. 2017-04-29 20:16:14 +12:00
Peter Galloway 246083460e partway through fixing boat movement to be updated from the data valid timestamp rather than the data sent timestamp #story[820] 2017-04-29 20:13:34 +12:00
Kusal Ekanayake 1d28334346 Removed the parser from queing packets incorrectly. Used the marker boat location packets to get the live updates of the marker positions and implemented the markers being updated much like the boats currently are. This means the course more closely resembles the actual intended course design.
#story[820]
2017-04-29 20:10:39 +12:00
Michael Rausch 3e97f016d5 Getting boat locations from race simulator & bug fixes
- Boat locations that are generated by the simulator are sent to the client as they happen
- Fixed heading and lat/lon encoding
- Fixed a bug where the header wasn't included in the sent byte stream
- Fixed the format of data as it's sent to the client.
- Data is now sent using a channel
- Removed tests that don't work with channels

Tags: #story[829]
2017-04-29 19:38:21 +12:00
Alistair McIntyre ab0d4634d6 Moved XML parsing to non static class to create objects. Changed the abstraction as using generics in maps lead to more headaches than anything. Still not quite completed. Needs documentation and validation for tags too.
#story[820]
2017-04-29 19:02:30 +12:00
Peter Galloway 1e1e482b79 Added a delay for reading packets from the packet buffer so packets that are recieved out of order have time to order by timestamp in the priority queue #story[820] 2017-04-29 18:56:41 +12:00
cir27 a0624cfef6 Merge branch 'wake_remake' into Mark_to_MarkGroup
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/Wake.java
#	src/main/java/seng302/models/parsers/StreamParser.java
#	src/main/resources/views/RaceView.fxml
2017-04-29 14:29:12 +12:00
cir27 02a35b4c02 Reduced the cost of updating wakes. Improved the logic for controlling indices.
#implement #story[820]
2017-04-29 14:20:52 +12:00
cir27 80409c08a6 Removed a graphical object added for testing. 2017-04-29 02:17:55 +12:00
cir27 6149f7be60 Wakes no longer become out of sync with boats after extended periods of time. Added in
a limit to the length of boat trails.

#implement
2017-04-29 02:14:55 +12:00
Calum 474f0ee427 Further work on new wake system. Wakes turn correctly but need to scale with velocity and
eventually desync with the boats. Needs to reset to the boats position on straights.
2017-04-28 23:25:49 +12:00
Zhi You Tan f3ee618900 Fixed broken race timer and timer now using stream timer data.
#story[818]
2017-04-28 21:44:23 +12:00
Zhi You Tan b939086e10 Updated welcome screen to show if race is finished or starting
#story[572]
2017-04-28 21:28:34 +12:00
Zhi You Tan ffdfc24e65 Created a start screen with a timer which shows the race progress
#story[572]
2017-04-28 20:09:17 +12:00
Peter Galloway 07bbd7e06d Added reasonable testing for StreamReciever, further testing would probably need StreamReciever to be rewritten #story[817] 2017-04-28 18:29:35 +12:00
Calum 765f27f987 Starting new wake implementation. 2017-04-28 17:08:08 +12:00
Kusal Ekanayake d204bee55d Started documentation on the stream parser.
#story[820]
2017-04-28 17:01:28 +12:00
Kusal Ekanayake 0f4ad48de0 Fixed and enables the old wakes. Enabled the fps counter by implementing the team-27s fps counter from their code, fixed trails from starting at the start of the startline no matter at what point in the race the stream is connected to (this is means the map starts a lot cleaner). Added live tracked speeds which are taken from the boat location packet. Linked the speeds coming in to their specified boats and allowed the onscreen speed tracker to keep up with the speeds. Linked the current speeds to the wakes so the wakes are redrawn for each change in speed and size to match the speed. Also added the toggle functionality back to the fps counter so they can be toggled on an off.
#story[818]
2017-04-28 16:41:35 +12:00
Alistair McIntyre fe480d5cb6 Finished parsing the Race XML data. Began making some optimizations to hopefully make parsing the Boat Data a quicker and simpler task.
#story[820]
2017-04-28 15:41:12 +12:00
Haoming Yin 8a04a0e5b7 Added documents for Boat, RaceParser and Simulator classes.
#story[828]
2017-04-28 14:53:26 +12:00
Haoming Yin 705a0a2eaf Added document and unit tests for GeoUtility class.
- three methods in GeoUtility have been tested and passed.

#story[828]
2017-04-28 14:34:24 +12:00
Kusal Ekanayake d1289b0de1 Fixed boats moving in the correct heading as according to the stream and attempted to fix the wakes direction.
#story[818]
2017-04-27 18:52:37 +12:00
Alistair McIntyre e1de5e0989 Parsed more course data from XML messages
#story[820]
2017-04-27 18:31:13 +12:00
Alistair McIntyre f5b9160304 Started parsing the different types of XML messages to Map objects so that we can extract the relevant data for the visualizer.
#story[820]
2017-04-27 17:22:46 +12:00
Kusal Ekanayake 0a22812165 Got the live ac35 data working in paralell with the parse and the app. Boats move and the live data works correctly. We need to fix the markers now
#story[818]
2017-04-27 16:18:33 +12:00
Kusal Ekanayake 104fd86179 Got the live ac35 data working in paralell with the parse and the app.
#story[820]
2017-04-27 14:40:26 +12:00
Calum 67a702ffcd Wakes still broken. Implemented dashed lines that track the progress of individual boats.
#implement #story[483]
2017-04-27 13:57:19 +12:00
Calum 65c0e6f77d Fixed markergroup bugs. Improved wakes. Still WIP 2017-04-27 11:58:50 +12:00
cir27 245bd184b4 Mark drawing moved to MarkGroup class. RaceObject and it's sub classes now describe
all functionality required for a on screen object. Improved wakes. Branch currently
untested.

#story[812, 820] #refactor #implement.
2017-04-27 02:44:25 +12:00
Haoming Yin 8c8f253233 Created simulator to generate mock data.
- simulator runs as a background thread and sleep for a given time lapse.
- simulator extends observable, so it can notify all its observers when boats positions have been updated

#story[828]
2017-04-26 22:58:13 +12:00
Haoming Yin 8b8422de3a Renamed course parser to race parser
- because in AC35 spec. race xml file contain course set up and all other general race settings

#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin 7bf2d4c40e Added Position class to better use GeoUtility.
- mark now inherit from Position

#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin 2a67f04d15 Create GeoUtility to process all geo calculations.
- calculate distance between two geo positions
- calculate the bearing from one geo position to another
- calculate the new geo position by passing original position, bearing and
distance

#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin b2ea8196d5 Fixed a bug of getCourse method as it didn't parse xml correctly.
- a typo 'CompoundmarkID'(should be 'CompoundMarkID') which caused parser failed to parse file.
- add typeOf method in RoundingType to convert strings to types

#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin 7f38191d03 Rewrote course parser to parse race xml file specified in AC35 spec.
#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin f6b7a3042f Rewrote all kind of marks to fit marks specified in AC35 spec.
- added compound mark
- added corner
- rewrote mark as a single mark
- added rounding type enum

#story[828]
2017-04-26 22:52:18 +12:00
Haoming Yin 3bdc6ce5cc Created a new course parser to parse race xml file specified in AC35 spec.
#story[828]
2017-04-26 22:52:18 +12:00
Michael Rausch bc31987f96 Added Boat location messages to the mock streaming data interface
- Added static methods to convert between binary packed lat/longs and floating point numbers

Tags: #story[829]
2017-04-26 22:38:39 +12:00
cir27 eaff4c5aac Added abstract class for all javafx object that a displayed during race. Began refactoring of mark implementation to be a subclass of the aforementioned abstract class. 2017-04-26 21:16:22 +12:00
Calum 95bafdc0d1 Fixed bug which caused boats to all travel to the same position. 2017-04-26 19:19:03 +12:00
Kusal Ekanayake c776d22941 Linking up course stream with visualiser. Boats moving, and course drawing. Boats however are not moving as intended. Needs to be fixed/looked into.
#story[820] #pair[kre39,cir27]
2017-04-26 18:45:58 +12:00
Calum 749c6b7fef Fixed bugs caused by horizontally scaling maps 2017-04-26 17:18:33 +12:00
Kusal Ekanayake 912c081606 Added marks with the test data coords to the xml
#story[820]
2017-04-26 15:55:51 +12:00
Kusal Ekanayake c73bf7dd3e Started merging received packets from the sample stream and reading lats and lons to move boats from them.
#story[820]
2017-04-26 15:51:15 +12:00
Kusal Ekanayake a3ae015be8 Merge remote-tracking branch 'origin/30b/30c_boat_animation' into StreamReceiverMergeWithVisualiser 2017-04-26 15:06:55 +12:00
Michael Rausch 1f8f1f0f86 Added boat location, and race start messages to the mock data interface
- Added proper support for signed and unsigned types. This includes automatic conversion to the correct data type (long to int, short, or byte).

- Moved code related to adding values into the byte buffer into the abstract Message class

Tags: #story[29]
2017-04-25 21:49:51 +12:00
Kusal Ekanayake 5eebab2748 Completed data extractors for: heartbeat, racestatus, display text msg, race start status, yacht event code, yacht action acode, chatter text, boat location, mark rounding, course wind and average wind. Some of the methods need to be validated but others have been tested. Will now need to link the parses with the model.
#story[820]
2017-04-25 17:57:22 +12:00
Alistair McIntyre 8cbd1cc4aa Added support to import XML packet to XML Document object.
#story[820]
2017-04-25 17:38:26 +12:00
Kusal Ekanayake 00c1a89f58 Fixed error in the switch (missing breaks)
#story[820]
2017-04-25 15:39:28 +12:00
Kusal Ekanayake d51825ffb7 Created separate streams for each different data type to be parsed into so the exact needed data would be able to be extracted.
#story[820]
2017-04-25 15:32:04 +12:00
cir27 42569e6ad7 Changed BoatPolygon is now a group instead of a polygon and is called BoatGroup.
BoatPolygon's functionality was more maintainable and scalable by having it extend Group.

 #story30c
2017-04-25 04:30:44 +12:00
cir27 ef874b4245 Added a transition time to rotational movement.
The aim is to make animations smoother when the boat turns. Unsure if current
 implementation will look good without testing on a datastream.

 #story30c
2017-04-25 03:12:30 +12:00
Calum 46037b5aea Refactored Boat class to better fit the MVC model by moving all GUI parts to BoatPolygon. Changed the way animation works so that it will work with a constantly updated set of lats and lons.
TODO - Change Mark class to no longer store XY pixel data.
TODO - Add in a timer force updates boat position if a packet has not been recieved for a while.

#story30b #story30c #implement #refactor
2017-04-24 23:06:30 +12:00
Michael Rausch 6874f288ee Added Race Status messages to the mock streaming data interface
Tags: #story[29]
2017-04-24 21:53:42 +12:00
Zhi You Tan b6fd90e9d7 Updated .mailmap for Zhi You Tan's information 2017-04-24 18:33:06 +12:00
Peter Galloway f078c34bf9 the stream receiver can now be passed a threadsafe priorityQueue that it will add the packets to as they are received (note the priority queue passed should be initialized with a comparitor for "StreamPacket"s) #story[817] 2017-04-24 18:29:50 +12:00
Peter Galloway c1e4a6156c re-engineered stream receiver to make it cleaner and ready to be used with the rest of the program. #story[817] 2017-04-24 17:38:29 +12:00
Kusal Ekanayake 71e14259f6 Started looking into boat location packets, am able to extract the lats an lons but needs validations. Can also see the device type, timestamp, and sequence number. Code needs to be cleaned up and will need to start looking into the set up packets, specifically the packets containing xml data so the course can be created.
#story[820]
2017-04-24 16:47:41 +12:00
Kusal Ekanayake 403dc480c4 Created packet enum to class packets and started progress on how the packets are read and parsed according to the type of packet.
#story[820]
2017-04-24 15:50:21 +12:00
Kusal Ekanayake 3dc1a7f9c0 StreamPacket class created so that we can store all packets generically. The timestamp has also been extracted and stored with the packet so that in the future we may turn the current ArrayList into a priority que.
#story[817]
2017-04-23 20:14:41 +12:00
Peter Galloway 672194adb4 changed Peter Galloway's name to specified format 2017-04-23 19:04:45 +12:00
Haoming Yin fdb84b6675 Update .mailmap for Haoming Yin's information. 2017-04-23 18:51:36 +12:00
Peter ba352183bf added functionality to check the CRC for the packet. I ran into a lot of trouble with this regarding everything in java being signed by twos compliment #story[817] 2017-04-23 16:53:35 +12:00
Kusal Ekanayake dd480080c9 Updated .mailmap to have the correct username for Kusal Ekanayake. 2017-04-23 14:57:26 +12:00
kre39 4047978ea2 Renamed file to match its functionality more accurately .
#story[30a]
2017-04-23 14:43:18 +12:00
kre39 a649b11bbf Reading relevant information (boats and race related info) from the stream so it can be moved to a parse and turned into objects for the actual race.
#story[30a]
2017-04-23 14:42:10 +12:00
Peter 247560ee43 converted prototype to be reading the stream byte by byte rather than by lines and characters which was very confusing and unreliable. currently extracting message type and payload length. #story[817] 2017-04-22 16:39:03 +12:00
Kusal Ekanayake 50e7ece477 Checking for the header of each packet as the stream parser checks for each byte to see if it matches with the desired header sequence. 2017-04-20 19:17:12 +12:00
Calum b5129c5c80 Moved the canvas drawing implementation from team27's codebase to team13's.
#story30b
2017-04-20 19:06:32 +12:00
Alistair McIntyre 6a27dedd74 Simple test to get stream data 2017-04-20 15:08:50 +12:00
Michael Rausch edc306da22 Created AC35 Streaming server
- Sends heartbeat messages every 5 seconds
- Sends XML at beginning

Tags: #story[29]
2017-04-19 19:05:19 +12:00
Kusal Ekanayake 15ded667fe Started to implement the group over the canvas in the code. Removed basic boat redrawing and timeline and replaced with boats being placed into a group and given coordinates. 2017-04-11 17:46:02 +12:00
William Muir 34872a822b Stripped back codebase to make to create basic model for streaming data
Removed many classes involved with visualisation such as controllers and multiple fxmls. Now there is just one for debugging

Merged in Boat updating pattern from team 27

#story[828]
2017-04-08 17:49:50 +12:00
Michael Rausch 9817fc9093 Fixed JavaDoc errors by adding missing @params 2017-04-04 19:29:05 +12:00
Michael Rausch dde4b2fcba gitlab ci test (passing) 2017-04-04 19:15:41 +12:00
Michael Rausch 623600a8a9 gitlab ci test (failing) 2017-04-04 19:15:01 +12:00
Michael Rausch bff4986242 Gitlab CI Build Test 2017-04-04 19:10:01 +12:00
Michael Rausch c689530068 Gitlab CI Build Test 2017-04-04 19:03:45 +12:00
Haoming Yin 7022be1979 Fixed unit test which failed due to the change of configure file.
#story[445]
2017-03-30 16:16:30 +13:00
Haoming Yin 971a3920a3 Fixed race timer to display real race time, and made race time scalable.
#story[445]
2017-03-30 16:12:01 +13:00
Michael Rausch b252797e9b Removed test case that failed when the config file was changed 2017-03-30 14:48:47 +13:00
Michael Rausch c758afe3e3 Merge branch 'master' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 2017-03-30 14:46:22 +13:00
Peter f8d3f53158 changed boat speed to a lower value 2017-03-30 14:45:33 +13:00
Michael Rausch 4a8672a20b Updated race timer test to use new controller
Tags:story[455]
2017-03-30 14:44:57 +13:00
Peter 32109e8565 Merge branch 'controller-refacto' into origin/master 2017-03-29 15:03:38 +13:00
Peter 5d6060c690 Merge remote-tracking branch 'origin/master' into controller-refacto 2017-03-29 15:01:38 +13:00
Peter a95d030817 Controllers and Fxml nicely refactored, tests still broken #story[463] 2017-03-29 14:59:37 +13:00
Michael Rausch ffa84c6e87 Removed testNextColor for now as the test seems to be broken.. 2017-03-29 13:28:49 +13:00
Peter 4a6978ff79 Fxml refactored, partway through refactoring controllers (app does not run) #story[463] 2017-03-29 12:58:49 +13:00
Haoming Yin 1497858cc0 Deleted the print exception stack statement to make terminal more tidy.
- the exception is aim to happen, so no need to print it out.

#story[377]
2017-03-28 19:38:57 +13:00
Michael Rausch 2c125d4ce0 Fixed test case for the boat heading #story[466] 2017-03-28 19:25:04 +13:00
Zhi You Tan 8bf5455f42 Updated fxml and canvas controller to display a resizable canvas.
#story[377]
2017-03-27 17:05:49 +13:00
Zhi You Tan 48f7e41905 Merge branch 'master' of /home/cosc/student/zyt10/Documents/[SENG302]/team-13 with conflicts. 2017-03-27 16:52:31 +13:00
Peter b50ac62a4b Merge branch 'add-fps-counter' 2017-03-27 16:47:34 +13:00
Peter 6fc55bb82c Added toggle for fps #story[463] 2017-03-27 16:45:02 +13:00
Peter 65ac864bf2 added count for fps inside handle loop and displayed fps on the canvas #story[463] 2017-03-27 16:34:54 +13:00
Haoming Yin cf6bbdd1f1 Fixed boat heading calculation method to get a correct direction
#story[480] #pair[hyi25, ptg19]
2017-03-27 14:29:03 +13:00
Haoming Yin bbe7cbee8f Use canvas polygon to draw a triangle
#story[480]
2017-03-27 14:26:38 +13:00
Michael Rausch 55ba343426 Merge branch 'change-to-short-names' into 'master'
Changed the team names to their abbreviated versions

- Also halved the size of the wake lines
- Updated tests to support the shorter team names
- Wake lines are now hidden with the other annotations

Tags: #story[23,21]

See merge request !25
2017-03-24 21:01:55 +13:00
Michael Rausch e6ace5fb2f Changed the team names to their abbreviated versions
- Also halved the size of the wake lines
- Updated tests to support the shorter team names
- Wake lines are now hidden with the other annotations

Tags: #story[23,21]
2017-03-24 20:56:52 +13:00
Michael Rausch 550ab59231 Merge branch 'master' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 2017-03-24 20:45:49 +13:00
Michael Rausch 213303d674 Merge branch 'create-wake-line' 2017-03-24 20:39:21 +13:00
Michael Rausch 74c81eb7b3 Removed a character that was accidentally inserted 2017-03-24 20:37:32 +13:00
Michael Rausch c33586e7f5 Changed test to work with the new getHeading method, and removed an unused import that was causing tests to fail
- There was a com.sun import that we were no longer using that was causing issues

Tags: story[466]
2017-03-24 20:33:53 +13:00
Michael Rausch 5dd5e50738 Implemented wake lines
- Changed heading calculation in event class
- The boats now go to the marker, rather than the center of a gate

Tags: #story[466]
2017-03-24 20:27:17 +13:00
Haoming Yin 304f30ece6 Reformatted and refactored the fileparser to get xml from resource folder
#fix #refactor #story[377] #pair[xyi25, zyt10]
2017-03-24 12:55:11 +13:00
zyt10 e8b1720fee Created a toggle checkbox to show and hide all annotation
#story[477]
2017-03-24 12:15:39 +13:00
Peter e0cea098c1 Merge remote-tracking branch 'origin/master' 2017-03-24 11:52:17 +13:00
Peter 50ab481b18 added knots beside boat name #story[447] 2017-03-24 11:51:07 +13:00
zyt10 d39aacba83 RaceController now uses new parsers & deleted OldApp and OldFileParser
#story[377]
2017-03-24 11:34:34 +13:00
Michael Rausch 798fe4da0e Changed FPS to 30 2017-03-24 11:15:46 +13:00
Peter e19f110f19 Merge branch 'origin/addBoatPositionsInSideBar' 2017-03-24 11:06:03 +13:00
Peter 59a4a74a97 changed boatPositionController to also add velocity to the boat ordering shown in the side bar #story[447] 2017-03-23 22:50:04 +13:00
Peter 590ef557d3 fixed bugs from last commit and updated some tests #story[426] 2017-03-23 22:24:42 +13:00
Peter 7bc5c8f8a4 Merge remote-tracking branch 'refs/remotes/origin/master'
Conflicts:
	src/main/java/seng302/controllers/CanvasController.java
	src/main/java/seng302/models/Boat.java
2017-03-23 22:17:45 +13:00
Peter bf8244ce49 tried to test BoatPositionController but as it's connected to the fxml I am unsure if it is possible to test (test code is commented out) #story[426] 2017-03-23 22:04:25 +13:00
Peter a860cc0ec1 connected up all the javafx components and got team positions displaying on the sidebar #story[426] 2017-03-23 21:21:54 +13:00
Peter bb8c681270 added a markpos value to event for use in displaying the team positions #story[426] 2017-03-23 21:19:39 +13:00
Michael Rausch 9872095e50 Merge branch 'boat-labels' into 'master'
Display team name and speed beside boat

- Also slowed down the AnimationTimer
- Removed the need to scale the canvas

Tags: #implement #story[18] #story[19]

See merge request !23
2017-03-23 18:06:31 +13:00
Michael Rausch 403aaa76ae Display team name and speed beside boat
- Also slowed down the AnimationTimer
- Removed the need to scale the canvas

Tags: #implement #story[18] #story[19]
2017-03-23 18:04:00 +13:00
Peter 8578bc4a5b changed the "sea" color of the canvas to be actually drawn on the canvas rather than the pane behind #story[377] 2017-03-23 16:18:42 +13:00
Peter ed004d1423 Merge branch 'master'
Conflicts:
	src/main/java/seng302/controllers/CanvasController.java
	src/main/resources/RaceView.fxml
2017-03-23 15:16:11 +13:00
Michael Rausch a91b2b4f7e Merge branch 'create-race-timer' into 'master'
Create race timer

Created race timer controller

#implement #test #story[16]

See merge request !22
2017-03-23 14:20:25 +13:00
Michael Rausch 24f9607e5a Added tests for the RaceTimerController #test #story[16] 2017-03-23 14:18:27 +13:00
Michael Rausch 2384013139 Created race timer
- Race starts when timer reaches 1 second
- Race waits 10 seconds before it starts

Tags: #implement #story[16]
2017-03-23 14:11:12 +13:00
Haoming Yin 42ffd1b1f8 Add rotated wind direction arrow to race view.
#story[422]
2017-03-23 01:10:07 +13:00
Haoming Yin a2d06909c9 Finished config parser to read race info from external xml file
- created config parser unit test.
- modified config.xml file
- write unit test for config parser

#story[422]
2017-03-23 00:21:18 +13:00
Michael Rausch c8b7b95df8 Merge branch 'finish-race-results' into 'master'
Added the race results to the RaceResultController. Also fixed some bugs

- Fixed a bug where the race results would be out of order.
- Changed the colour of the start and finish gates
- Added the race results to the RaceResultController and updated view

Tags: #fix #implement #story[13, 10, 11]

See merge request !21
2017-03-22 22:42:05 +13:00
Michael Rausch 9e22eac4d8 Added the race results to the RaceResultController. Also fixed some bugs
- Fixed a bug where the race results would be out of order.
- Changed the colour of the start and finish gates
- Added the race results to the RaceResultController and updated view

Tags: #fix #implement #story[13, 10, 11]
2017-03-22 22:30:49 +13:00
Michael Rausch a41f2e4bde Changed marks from circles to squares #fix 2017-03-22 15:10:57 +13:00
Michael Rausch 039e61def6 Fixed broken JavaFX file #fix 2017-03-22 14:50:28 +13:00
Peter 54d329c5cf changed raceView javafx structure and added an example of how the handle function works with keyframes #story[426] 2017-03-22 14:44:40 +13:00
Michael Rausch f46a98fad9 Merge branch 'master' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 2017-03-22 14:39:37 +13:00
Michael Rausch 6a6816dda1 Merge branch 'create-finish-display' 2017-03-22 14:31:06 +13:00
Michael Rausch f6ea2953e9 Changed lat/long for gates to midpoint and added colours for the marks
Tags: #fix #implement #story[10,11]
2017-03-22 14:28:51 +13:00
Michael Rausch 7591a79323 Fixed course rotation #fix 2017-03-22 14:05:55 +13:00
Michael Rausch c12760b70a Inserted missing } #fix 2017-03-22 12:53:42 +13:00
Michael Rausch a526b0d65a Rotated map by 180 degrees #fix 2017-03-22 12:52:43 +13:00
Michael Rausch ef098e63d7 Added finishing events #implement and rotated the map by 180 degrees#fix 2017-03-22 12:51:03 +13:00
zyt10 29a0b23670 Reformatted double value lat and lon to final double VIEW_CORNER_LAT and VIEW_CORNER_LON 2017-03-22 12:03:46 +13:00
zyt10 44b5b0b771 Added side pane to GUI and resize to 1280x960
#story[426]
2017-03-22 11:46:39 +13:00
Michael Rausch 00f9cc4698 Merge branch 'fix-boat-postions-and-keyframes' into 'master'
Fixed x and y coordinates #fix #story[9]



See merge request !19
2017-03-21 17:42:13 +13:00
Michael Rausch 3d4d5a3dab Fixed x and y coordinates #fix #story[9] 2017-03-20 21:13:30 +13:00
Peter ed577fad6a adding files missed in previous commit 2017-03-20 17:58:40 +13:00
Peter ae61260665 created a javafx view for the race finish and created a rough wireframe for the controller to change the view from race to finish #story[414] 2017-03-20 17:56:14 +13:00
zyt10 0e4bb0f942 Changed getMark to getThisMark. Added start point to KeyFrame. Race now starts from start point and ends at finish point
#story[377]
2017-03-20 17:34:24 +13:00
Haoming Yin ee34e5028f Reformatted and refactored the canvas controller
#fix #refactor #story[377]
2017-03-20 17:23:33 +13:00
Peter 3b8dd11758 Added drawBoats for use in animation timer, also fixed gate marks only drawing one mark #story[378] 2017-03-20 16:11:04 +13:00
zyt10 cc04e2dd6d Fixed boat colour, was in the wrong constructor
#story[377]
2017-03-20 16:03:07 +13:00
Haoming Yin b88cf6a101 Merge branch 'refactor-file-parser' 2017-03-18 21:38:38 +13:00
Haoming Yin d10985f890 Finished team parser to read team info from external xml file
- created team parser unit test
- refactored team parser functions

#fix #refactor #implement
2017-03-18 21:32:12 +13:00
zyt10 c08504293b Created canvas and race controllers to display boats on canvas and modified marks and parsers to support them.
#story[377] #pair[zyt10, ptg19]
2017-03-17 18:21:11 +13:00
zyt10 4bc49da10d Merge remote-tracking branch 'origin/master' 2017-03-17 16:18:24 +13:00
zyt10 683f4ba94e Mostly got boats going to marks on the canvas (code currently broken) #story[377] #pair[zyt10, ptg19] 2017-03-17 16:18:01 +13:00
Haoming Yin 8fd06c84ac Merged the refactored mark related class with course parser.
#fix #refactor #story[9] #story[10] #story[11]
2017-03-17 15:56:37 +13:00
Haoming Yin 23b163e6c1 Merge branch 'master' into refactor-file-parser
# Conflicts:
#	src/main/java/seng302/models/GateMark.java
2017-03-17 15:44:16 +13:00
Haoming Yin 6383b9a6f8 Deleted the old Mark and GateMark files 2017-03-17 15:26:38 +13:00
Haoming Yin 0b3ebf229f Refactor mark related classes.
- Mark is an abstract class which containing its name and type
- Single Mark is a sub class of Mark which containing only one GPS location
- Gate Mark is a sub class of Mark which containing two Single Marks

#refactor #fix #story[10] #story[11] #story[12]
2017-03-17 15:21:04 +13:00
Michael Rausch d6fe155d4d Changed distance calculation to use latitude and longitude
Tags: #fix
2017-03-17 11:08:35 +13:00
Michael Rausch 8829728f19 Merge branch 'master' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 2017-03-17 10:27:04 +13:00
Michael Rausch a6c9156ae5 Changed Main-Class in pom.xml to our new main class
Tags: #fix
2017-03-17 10:26:09 +13:00
Haoming Yin 121f996a43 created config, teams xml file, and teamsParser class
#implement
2017-03-17 01:04:10 +13:00
Ryan Tan 44d4f25413 Implemented Color Enum & boat will call function from enum to get next color. Using static colour cycling for now.
#story[377]
2017-03-17 00:57:50 +13:00
Haoming Yin 16abfcffda Created course parser as a subclass of file parser
- refactored file parser as an abstract class
- created course parser to parse course xml file

#implement #fix #refactor #story[9] #story[10]
2017-03-17 00:54:43 +13:00
Haoming Yin e7ba9d962d Deleted and modified previous sprint parser to cater the sprint2’s new requirement
- also added new method for GateMark

#story[9] #story[10] #fix
2017-03-17 00:53:08 +13:00
Haoming Yin b7631c0b46 Create unit test for course parser, and modified course xml file
#implement #fix #story[10] #story[9]
2017-03-17 00:51:16 +13:00
Michael Rausch 0039703f03 Merge branch 'master' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 2017-03-16 20:50:41 +13:00
Michael Rausch d439c9673f removed intellij files #chore 2017-03-16 20:50:27 +13:00
Haoming Yin f255b0ea6d Add intellij file type to .gitignore. 2017-03-16 20:47:55 +13:00
Michael Rausch 9d01ed8eb3 Merge branch 'refactor-events-to-update-boat-positions' into 'master'
Removed Legs from the race, using coordinates instead

Tags: #implement #refactor #test #story[9]

See merge request !17
2017-03-16 20:30:48 +13:00
Michael Rausch 94e4e853c3 Removed Legs from the race, using coordinates instead
Tags: #implement #refactor #test #story[9]
2017-03-16 20:29:17 +13:00
Ryan Tan ffa39e6a9c Changed size of canvas to 720 x 360
#story[377]
2017-03-16 17:40:31 +13:00
Ryan Tan 9e4ae60885 Added a function to draw boat on canvas
#story[377]
2017-03-16 17:25:17 +13:00
Peter 2cd4366d07 added course xml file #story[378] 2017-03-16 17:15:45 +13:00
Peter 11c5e1e9ba Added gate mark and refactored marker to mark #story[378] 2017-03-16 16:11:48 +13:00
Peter 550812d8e1 Currently displaying basic javafx window with canvas. Also changed the file structure a bit.
At this point the javafx is not tied to the old code in any way #story[377]
2017-03-15 18:16:43 +13:00
Haoming Yin 9ca5f5e7fd Updated design decision file in doc. 2017-03-09 16:22:40 +13:00
109 changed files with 8608 additions and 955 deletions
+3
View File
@@ -84,6 +84,9 @@ nbactions.xml
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff: # User-specific stuff:
# get rid of these annoying files
.idea/
*.iml
.idea/workspace.xml .idea/workspace.xml
.idea/tasks.xml .idea/tasks.xml
.idea/dictionaries .idea/dictionaries
+7 -1
View File
@@ -16,4 +16,10 @@
# https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html # https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html
# http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/ # http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/
Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz> Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz> <michael@michaelrausch.net>
Kusal Ekanayake <kre39@uclive.ac.nz> kre39 <kre39@uclive.ac.nz>
Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz>
Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz>
+1 -1
View File
@@ -10,4 +10,4 @@ Remember to set up your GitLab CI server (refer to the student guide for instruc
- `src/` Your application source - `src/` Your application source
- `doc/` User and design documentation - `doc/` User and design documentation
- `doc/examples/` Demo example files for use with your application - `doc/examples/` Demo example files for use with your application
+14
View File
@@ -1,2 +1,16 @@
# Design Decisions # Design Decisions
- Code structure
App creates a race instance which can:
instantiate a file parser to extract race setting and team information;
creates and passes legs and teams/boats into event generator to create events;
runs a race and iterates all events that returned from the generator;
prints out event details, including time, involved boats and legs.
- Configuration file
We decided to store the team information including team names and boat velocity, as well as race configuration setting in external file.
To read external files, "Json-simple" library has been used to parse information.
By using this library, we did not have to write our json parser and benefited from the flexibility of json files.
-31
View File
@@ -1,31 +0,0 @@
{
"race-name": "AC35",
"time-scale": 2.0,
"race-size": 6,
"teams": [
{
"team-name": "Oracle Team USA",
"velocity": 20.9
},
{
"team-name": "Artemis Racing",
"velocity": 18.3
},
{
"team-name": "Emirates Team New Zealand",
"velocity": 21.5
},
{
"team-name": "Groupama Team France",
"velocity": 19.9
},
{
"team-name": "Land Rover BAR",
"velocity": 17.6
},
{
"team-name": "SoftBank Team Japan",
"velocity": 16.6
}
]
}
+11
View File
@@ -20,11 +20,22 @@
<version>4.12</version> <version>4.12</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<dependency> <dependency>
<groupId>com.googlecode.json-simple</groupId> <groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId> <artifactId>json-simple</artifactId>
<version>1.1.1</version> <version>1.1.1</version>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.7.13</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
+64 -96
View File
@@ -1,111 +1,79 @@
package seng302; package seng302;
import java.lang.reflect.Array; import javafx.application.Application;
import java.util.ArrayList; import javafx.fxml.FXMLLoader;
import java.util.Collections; import javafx.scene.Parent;
import java.util.Map; import javafx.scene.Scene;
import java.util.Random; import javafx.stage.Stage;
import java.io.FileNotFoundException; import seng302.models.parsers.StreamParser;
import seng302.models.parsers.StreamReceiver;
import seng302.server.ServerThread;
public class App { public class App extends Application
{
@Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
primaryStage.setTitle("RaceVision");
primaryStage.setScene(new Scene(root));
primaryStage.setMaximized(true);
/** primaryStage.show();
* Builds a race object for the AC35 course primaryStage.setOnCloseRequest(e -> {
* StreamParser.appClose();
* @return a Race object for the AC35 course StreamReceiver.noMoreBytes();
*/ System.out.println("[CLIENT] Exiting program");
public static Race createRace(String configFile) throws Exception { System.exit(0);
Race race = new Race(); });
FileParser fp;
// Read team names from file
try{
fp = new FileParser(configFile);
}
catch (FileNotFoundException e){
System.out.println("Config file does not exist");
return null;
}
ArrayList<String> boatNames = new ArrayList<>();
ArrayList<Map<String, Object>> teams = fp.getTeams();
//get race size
int numberOfBoats = (int) fp.getRaceSize();
//get time scale
double timeScale = fp.getTimeScale();
race.setTimeScale(timeScale);
for (Map<String, Object> team : teams) {
boatNames.add((String) team.get("team-name"));
}
// Shuffle team names
long seed = System.nanoTime();
Collections.shuffle(boatNames, new Random(seed));
if (numberOfBoats > Array.getLength(boatNames.toArray())) {
return null;
}
// Add boats to the race
for (int i = 0; i < numberOfBoats; i++) {
race.addBoat(new Boat(boatNames.get(i), (Double) (teams.get(i).get("velocity"))));
}
race.addLeg(new Leg(35, 100, "Start"));
race.addLeg(new Leg(10, 300, "Marker 1"));
race.addLeg(new Leg(350, 400, "Leeward Gate"));
race.addLeg(new Leg(10, 400, "Windward Gate"));
Leg finishingLeg = new Leg(10, 400, "Leeward Gate");
finishingLeg.setFinishingLeg(true);
race.addLeg(finishingLeg);
return race;
} }
public static void main(String[] args) { public static void main(String[] args) {
Race race = null; StreamReceiver sr = null;
String raceConfigFile;
if (args.length == 2 && args[0].equals("-f")){ new ServerThread("Racevision Test Server");
raceConfigFile = args[1];
}
else{
// Use default config
raceConfigFile = "doc/examples/config.json";
}
try { try {
race = createRace(raceConfigFile); Thread.sleep(2000);
} catch (Exception e) { } catch (InterruptedException e) {
System.out.println("There was an error creating the race."); e.printStackTrace();
} }
// If race was created if (args.length == 1 && args[0].equals("-standalone")){
if (race != null) { return;
race.displayStartingBoats();
System.out.println("\n\n");
System.out.println("######################");
System.out.println("# Live Race Updates ");
System.out.println("######################");
race.startRace();
System.out.println("\n\n");
System.out.println("######################");
System.out.println("# Race Results ");
System.out.println("######################");
race.showRaceMarkerResults();
race.displayFinishingOrder();
} else {
System.out.println("There was an error creating the race. Exiting.");
} }
if (args.length == 3 && args[0].equals("-server")){
sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream");
} else if(args.length == 2 && args[0].equals("-server")){
switch (args[1]) {
case "internal":
sr = new StreamReceiver("localhost", 4949, "RaceStream");
break;
case "staffserver":
sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
break;
case "official":
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
break;
}
}
//Change the StreamReceiver in this else block to change the default data source.
else{
sr = new StreamReceiver("localhost", 4949, "RaceStream");
}
sr.start();
StreamParser streamParser = new StreamParser("StreamParser");
streamParser.start();
launch(args);
} }
} }
-62
View File
@@ -1,62 +0,0 @@
package seng302;
/**
* Represents a boat in the race.
*/
public class Boat {
private String teamName; // The name of the team, this is also the name of the boat
private double velocity; // In meters/second
public Boat(String teamName) {
this.teamName = teamName;
this.velocity = 10; // Default velocity
}
/**
* Represents a boat in the race.
*
* @param teamName The name of the team sailing the boat
* @param boatVelocity The speed of the boat in meters/second
*/
public Boat(String teamName, double boatVelocity) {
this.teamName = teamName;
this.velocity = boatVelocity;
}
/**
* Returns the name of the team sailing the boat
*
* @return The name of the team
*/
public String getTeamName() {
return this.teamName;
}
/**
* Sets the name of the team sailing the boat
*
* @param teamName The name of the team
*/
public void setTeamName(String teamName) {
this.teamName = teamName;
}
/**
* Gets velocity of the boat
*
* @return a float number of the boat velocity
*/
public double getVelocity() {
return this.velocity;
}
/**
* Sets velocity of the boat
*
* @param velocity The velocity of boat
*/
public void setVelocity(double velocity) {
this.velocity = velocity;
}
}
-140
View File
@@ -1,140 +0,0 @@
package seng302;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Event class containing the time of specific event, related team/boat, and
* event location such as leg.
*/
public class Event {
private long time; // Time the event occurs
private Boat boat;
private Leg leg; // Leg of the race the event occurs on
private boolean isFinishingEvent = false; // This event occurs when a boat finishes the race
/**
* Event class containing the time of specific event, related team/boat, and
* event location such as leg.
*
* @param eventTime, what time the event happens
* @param eventBoat, the boat that the event belongs to
* @param eventLeg, the leg the event happens on
*/
public Event(long eventTime, Boat eventBoat, Leg eventLeg) {
this.time = eventTime;
this.boat = eventBoat;
this.leg = eventLeg;
}
/**
* Event class containing the time of specific event, related team/boat, and
* event location such as leg.
*
* @param eventTime, what time the event happens
* @param eventBoat, the boat that the event belongs to
* @param eventLeg, the leg the event happens on
* @param isFinishingEvent, true if this event is the boat crossing the finishing line
*/
public Event(long eventTime, Boat eventBoat, Leg eventLeg, boolean isFinishingEvent) {
this.time = eventTime;
this.boat = eventBoat;
this.leg = eventLeg;
this.isFinishingEvent = isFinishingEvent;
}
/**
* Gets the time for the event
*
* @return the time for event in millisecond
*/
public long getTime() {
return this.time;
}
/**
* Sets the time for the event
*
* @param eventTime the time for event in millisecond
*/
public void setTime(long eventTime) {
this.time = eventTime;
}
/**
* Gets the time in a formatted string
*
* @return the string of time
*/
public String getTimeString() {
return (new SimpleDateFormat("mm:ss:SSS")).format(new Date(time));
}
/**
* Gets the involved boat
*
* @return the boat involved in the event
*/
public Boat getBoat() {
return this.boat;
}
/**
* Sets the involved boat
*
* @param eventBoat the involved boat
*/
public void setBoat(Boat eventBoat) {
this.boat = eventBoat;
}
/**
* Gets the involved location/leg
*
* @return the leg involved in the event
*/
public Leg getLeg() {
return this.leg;
}
/**
* Sets the involved location/leg
*
* @param eventLeg the involved leg
*/
public void setLeg(Leg eventLeg) {
this.leg = eventLeg;
}
/**
* Called when the boat in this event passes
* the marker.
*/
public void boatPassedMarker() {
this.leg.addBoatToMarker(boat);
}
/**
* Returns true if this event is the boat finishing the race
*/
public boolean getIsFinishingEvent() {
return this.isFinishingEvent;
}
/**
* Get a string that contains the timestamp and course information for this event
*
* @return A string that details what happened in this event
*/
public String getEventString() {
String currentHeading = Integer.toString(this.getLeg().getHeading());
// This event is a boat finishing the race
if (this.isFinishingEvent) {
return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " finished the race");
}
return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " passed " + this.getLeg().getMarkerLabel() + " going heading " + currentHeading + "°");
}
}
-132
View File
@@ -1,132 +0,0 @@
package seng302;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
/**
* Read team name from a given Json file. So that user can extract information
* efficiently from external files.
*/
public class FileParser {
private String filePath;
private JSONObject content;
/**
* used to construct an instance of file parser
*
* @param filePath a string like path to show location of desired file to
* be parsed
*/
public FileParser(String filePath) throws Exception {
this.filePath = filePath;
this.readFile();
}
/**
* Reads content from a given file, and return the content as JSONObject.
* Throws FileNotFoundException, if the given file cannot be found.
*/
private void readFile() throws FileNotFoundException {
JSONParser parser = new JSONParser();
try {
this.content = (JSONObject) parser.parse(new FileReader(filePath));
} catch (FileNotFoundException e) {
throw e;
} catch (IOException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
/**
* Gets time scale setting parameter.
*
* @return long time scale. -1 if parameter is invalid (eg. scale is
* negative number, or containing non numeric character) or cannot be found.
*/
@SuppressWarnings("unchecked")
public double getTimeScale() {
try {
double timeScale = (double) this.content.get("time-scale");
return timeScale >= 0 ? timeScale : -1;
} catch (Exception e) {
e.printStackTrace();
return 1;
}
}
/**
* Gets race name in the setting file.
*
* @return a string of race name. null if race name is invalid or cannot
* be found.
*/
@SuppressWarnings("unchecked")
public String getRaceName() {
try {
return (String) this.content.get("race-name");
} catch (Exception e) {
return null;
}
}
/**
* Gets an array of teams who participate the race.
*
* @return an ArrayList containing strings of team names. null if teams
* setting is invalid or there is no team.
*/
@SuppressWarnings("unchecked")
public ArrayList<Map<String, Object>> getTeams() {
try {
return (ArrayList<Map<String, Object>>) this.content.get("teams");
} catch (Exception e) {
return null;
}
}
/**
* Gets the total number of teams.
*
* @return the number of teams. 0 if no teams or anything goes wrong.
*/
@SuppressWarnings("unchecked")
public long getTotalNumberOfTeams() {
ArrayList<Map<String, Object>> teams = getTeams();
try {
return teams.size();
} catch (Exception e) {
return 0;
}
}
/**
* Gets the number of boats that would compete during a race. Returns the
* total number of race size if parameter is invalid or cannot be found.
*
* @return an int of the race size.
*/
@SuppressWarnings("unchecked")
public long getRaceSize() {
long totalTeams = this.getTotalNumberOfTeams();
try {
long raceSize = (long) this.content.get("race-size");
return raceSize >= 0 && raceSize <= totalTeams ? raceSize : totalTeams;
} catch (Exception e) {
e.printStackTrace();
return totalTeams;
}
}
}
-57
View File
@@ -1,57 +0,0 @@
package seng302;
import java.util.ArrayList;
/**
* Represents the marker at the beginning of a leg
*/
class Marker{
private String name;
private ArrayList<Boat> boatOrder;
/**
* Represents the marker at the beginning of a leg
*
* @param name, the name of the marker
*/
public Marker(String name){
this.name = name;
this.boatOrder = new ArrayList<Boat>();
}
/**
* Set the name of the marker
*
* @param name, the name of the marker
*/
public void setName(String name){
this.name = name;
}
/**
* Get the name of the marker
*
* @return the name of the marker
*/
public String getName(){
return this.name;
}
/**
* Add a boat as they pass the marker
*
* @param boat, the boat that passed the marker
*/
public void addBoat(Boat boat){
this.boatOrder.add(boat);
}
/**
* Get a list of boats in the order they passed the marker
*
* @return An array of boats in the order they passed the marker
*/
public Boat[] getBoats(){
return this.boatOrder.toArray(new Boat[this.boatOrder.size()]);
}
}
-249
View File
@@ -1,249 +0,0 @@
package seng302;
import java.lang.reflect.Array;
import java.util.*;
/**
* Race class containing the boats and legs in the race
*/
public class Race {
private ArrayList<Boat> boats; // The boats in the race
private ArrayList<Leg> legs; // The legs in the race
private ArrayList<Boat> finishingOrder; // The order in which the boats finish the race
private PriorityQueue<Event> events; // The events that occur in the race
private int numberOfBoats = 0;
private long startTime = 0;
private double timeScale = 1;
/**
* Race class containing the boats and legs in the race
*/
public Race() {
this.boats = new ArrayList<Boat>();
this.legs = new ArrayList<Leg>();
this.finishingOrder = new ArrayList<Boat>();
// create a priority queue with a custom Comparator to order events
this.events = new PriorityQueue<Event>(new Comparator<Event>() {
@Override
public int compare(Event o1, Event o2) {
Long time1 = o1.getTime();
Long time2 = o2.getTime();
// order event asc. by time. if tie appears, then order team
// name alphabetically.
if (time1 != time2) {
return time1.compareTo(time2);
} else {
return o1.getBoat().getTeamName().compareTo(o2.getBoat().getTeamName());
}
}
});
}
/**
* Add a boat to the race
*
* @param boat, the boat to add
*/
public void addBoat(Boat boat) {
boats.add(boat);
numberOfBoats += 1;
}
/**
* Returns a list of boats in a random order
*
* @returns a list of boats
*/
public Boat[] getShuffledBoats() {
// Shuffle the list of boats
long seed = System.nanoTime();
Collections.shuffle(this.boats, new Random(seed));
return boats.toArray(new Boat[boats.size()]);
}
/**
* Returns a list of boats in the order that they
* finished the race (position 0 is first place)
*
* @returns a list of boats
*/
public Boat[] getFinishedBoats() {
return this.finishingOrder.toArray(new Boat[this.finishingOrder.size()]);
}
/**
* Returns the number of boats in the race
*
* @returns the number of boats in the race
*/
public int getNumberOfBoats() {
return numberOfBoats;
}
/**
* Returns a list of boats in the race
*
* @return a list of the boats competing in the race
*/
public Boat[] getBoats() {
return boats.toArray(new Boat[boats.size()]);
}
/**
* Prints the order in which the boats finished the race
*/
public void displayFinishingOrder() {
int numberOfBoats = this.getNumberOfBoats();
Boat[] boats = this.getFinishedBoats();
System.out.println("--- Finishing Order ---");
for (int i = 0; i < Array.getLength(boats); i++) {
System.out.println("#" + Integer.toString(i + 1) + " - " + boats[i].getTeamName());
}
}
/**
* Prints the list of boats competing in the race
*/
public void displayStartingBoats() {
int numberOfBoats = this.getNumberOfBoats();
Boat[] boats = this.getBoats();
System.out.println("######################");
System.out.println("# Competing Boats ");
System.out.println("######################");
for (int i = 0; i < numberOfBoats; i++) {
String velocityKnots = String.format("%1.2f", boats[i].getVelocity() * 1.943844492);
System.out.println(boats[i].getTeamName() + " Velocity: " + velocityKnots + " Knots/s");
}
}
/**
* Adds a leg to the race
*
* @param leg, the leg to add to the race
*/
public void addLeg(Leg leg) {
this.legs.add(leg);
}
/**
* Gets legs array
*
* @return an array of legs
*/
public ArrayList<Leg> getLegs() {
return this.legs;
}
/**
* Sets time scale
*
* @param timeScale
*/
public void setTimeScale(double timeScale) {
this.timeScale = timeScale;
}
/**
* Generate all events that will happen during the race.
*/
private void generateEvents() {
//calculate the time every boat passes each leg, and create an event
for (Boat boat : this.boats) {
long totalDistance = 0;
for (Leg leg : this.legs) {
long time = (long) (1000 * totalDistance / boat.getVelocity());
Event event = new Event(time, boat, leg);
events.add(event);
totalDistance += leg.getDistance();
// If finishing leg, add another event for when the boat finishes the race
if (leg.getIsFinishingLeg()) {
time = (long) (1000 * totalDistance / boat.getVelocity());
event = new Event(time, boat, leg, true);
events.add(event);
}
}
}
}
/**
* Calculates how far a boat has travelled in meters
*
* @param velocity the velocity of boat
* @return a float number of distance the boat has been travelled
*/
public float getDistanceTravelled(long velocity) {
long timeDiff = System.currentTimeMillis() - this.startTime;
long timeElapse = (long) (timeDiff / 1000 * this.timeScale);
return timeElapse * velocity;
}
/**
* Iterate over events in the race and print the
* event string for each event
*/
public void iterateEvents() {
// iterates all events. ends when no event in events.
while (!events.isEmpty()) {
Event peekEvent = events.peek();
long currentTime = (long) ((System.currentTimeMillis() - this.startTime) * this.timeScale);
if (currentTime > peekEvent.getTime()) {
Event nextEvent = events.poll();
// Display a summary of the event
System.out.println(nextEvent.getEventString());
nextEvent.boatPassedMarker();
// If event is a boat finishing the race
if (nextEvent.getIsFinishingEvent()) {
this.finishingOrder.add(nextEvent.getBoat());
}
}
// Wait for 100ms to throttle the while loop
try {
Thread.sleep(100);
} catch (java.lang.InterruptedException e) {
continue;
}
}
}
/**
* Start the race and print each marker with the order
* in which the boats passed that marker
*/
public void startRace() {
// record start time.
generateEvents();
this.startTime = System.currentTimeMillis();
iterateEvents();
}
/**
* Print the order in which the boats passed each marker
*/
public void showRaceMarkerResults() {
for (Leg leg : this.legs) {
Boat[] boats = leg.getMarker().getBoats();
System.out.println("--- " + leg.getMarkerLabel() + " ---");
// Print the order in which the boats passed the marker
for (int i = 0; i < this.getNumberOfBoats(); i++) {
System.out.println("#" + Integer.toString(i + 1) + " - " + boats[i].getTeamName());
}
System.out.println("");
}
}
}
@@ -0,0 +1,539 @@
package seng302.controllers;
import javafx.animation.*;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import seng302.models.*;
import seng302.models.mark.*;
import seng302.models.parsers.StreamParser;
import seng302.models.parsers.StreamReceiver;
import seng302.models.parsers.packets.BoatPositionPacket;
import seng302.models.parsers.XMLParser;
import seng302.models.parsers.XMLParser.RaceXMLObject.CompoundMark;
import seng302.models.parsers.XMLParser.RaceXMLObject.Limit;
import seng302.models.mark.Mark;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.PriorityBlockingQueue;
/**
* Created by ptg19 on 15/03/17.
* Modified by Haoming Yin (hyi25) on 20/3/2017.
*/
public class CanvasController {
@FXML
private AnchorPane canvasPane;
private RaceViewController raceViewController;
private ResizableCanvas canvas;
private Group group;
private GraphicsContext gc;
private final int MARK_SIZE = 10;
private final int BUFFER_SIZE = 50;
private final int CANVAS_WIDTH = 720;
private final int CANVAS_HEIGHT = 720;
private final int LHS_BUFFER = BUFFER_SIZE;
private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2;
private final int TOP_BUFFER = BUFFER_SIZE;
private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2;
private double distanceScaleFactor;
private ScaleDirection scaleDirection;
private Mark minLatPoint;
private Mark minLonPoint;
private Mark maxLatPoint;
private Mark maxLonPoint;
private double referencePointX;
private double referencePointY;
private double metersToPixels;
private List<RaceObject> raceObjects = new ArrayList<>();
private List<Mark> raceMarks = new ArrayList<>();
//FRAME RATE
private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps
private final long[] frameTimes = new long[30];
private int frameTimeIndex = 0;
private boolean arrayFilled = false;
private DecimalFormat decimalFormat2dp = new DecimalFormat("0.00");
public AnimationTimer timer;
private enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
public void setup(RaceViewController raceViewController){
this.raceViewController = raceViewController;
}
public void initialize() {
raceViewController = new RaceViewController();
canvas = new ResizableCanvas();
group = new Group();
canvasPane.getChildren().add(canvas);
canvasPane.getChildren().add(group);
// Bind canvas size to stack pane size.
canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH));
canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT));
//group.minWidth(CANVAS_WIDTH);
//group.minHeight(CANVAS_HEIGHT);
}
public void initializeCanvas (){
gc = canvas.getGraphicsContext2D();
gc.save();
gc.setFill(Color.SKYBLUE);
gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT);
gc.restore();
fitMarksToCanvas();
// TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml
drawBoats();
timer = new AnimationTimer() {
@Override
public void handle(long now) {
//fps stuff
long oldFrameTime = frameTimes[frameTimeIndex] ;
frameTimes[frameTimeIndex] = now ;
frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length ;
if (frameTimeIndex == 0) {
arrayFilled = true ;
}
long elapsedNanos;
if (arrayFilled) {
elapsedNanos = now - oldFrameTime ;
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
Double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
drawFps(frameRate.intValue());
}
// TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay.
elapsedNanos = 1000 / 60;
updateRaceObjects();
if (StreamParser.isRaceFinished()) {
this.stop();
}
}
};
}
/**
* Adds border marks to the canvas, taken from the XML file
*
* NOTE: This is quite confusing as objects are grabbed from the XMLParser such as Mark and CompoundMark which are
* named the same as those in the model package but are, however not the same, so they do not have things such as
* a type and must be derived from the number of marks in a compound mark etc..
*/
private void addRaceBorder() {
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML();
ArrayList<Limit> courseLimits = raceXMLObject.getCourseLimit();
gc.setStroke(Color.DARKBLUE);
gc.setLineWidth(3);
double[] xBoundaryPoints = new double[courseLimits.size()];
double[] yBoundaryPoints = new double[courseLimits.size()];
for (int i = 0; i < courseLimits.size() - 1; i++) {
Limit thisPoint1 = courseLimits.get(i);
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID());
Limit thisPoint2 = courseLimits.get(i+1);
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID());
Point2D borderPoint1 = findScaledXY(thisMark1);
Point2D borderPoint2 = findScaledXY(thisMark2);
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
borderPoint2.getX(), borderPoint2.getY());
xBoundaryPoints[i] = borderPoint1.getX();
yBoundaryPoints[i] = borderPoint1.getY();
}
Limit thisPoint1 = courseLimits.get(courseLimits.size()-1);
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID());
Limit thisPoint2 = courseLimits.get(0);
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID());
Point2D borderPoint1 = findScaledXY(thisMark1);
Point2D borderPoint2 = findScaledXY(thisMark2);
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
borderPoint2.getX(), borderPoint2.getY());
xBoundaryPoints[courseLimits.size()-1] = borderPoint1.getX();
yBoundaryPoints[courseLimits.size()-1] = borderPoint1.getY();
gc.setFill(Color.LIGHTBLUE);
gc.fillPolygon(xBoundaryPoints,yBoundaryPoints,yBoundaryPoints.length);
}
/**
* Adds the course marks to the canvas, taken from the XMl file
*
* NOTE: This is quite confusing as objects are grabbed from the XMLParser such as Mark and CompoundMark which are
* named the same as those in the model package but are, however not the same, so they do not have things such as
* a type and must be derived from the number of marks in a compound mark etc..
*/
private void addCourseMarks() {
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML();
ArrayList<CompoundMark> compoundMarks = raceXMLObject.getCompoundMarks();
RaceObject markGroup;
for (CompoundMark compoundMark : compoundMarks) {
//If the compound mark has 2 marks then its a gate mark
if (compoundMark.getMarks().size() == 2) {
CompoundMark.Mark mark1 = compoundMark.getMarks().get(0);
CompoundMark.Mark mark2 = compoundMark.getMarks().get(1);
SingleMark singleMark1 = new SingleMark(mark1.getMarkName(), mark1.getTargetLat(), mark1.getTargetLng(), mark1.getSourceID());
SingleMark singleMark2 = new SingleMark(mark1.getMarkName(), mark2.getTargetLat(), mark2.getTargetLng(), mark2.getSourceID());
GateMark thisGateMark = new GateMark(compoundMark.getcMarkName(),
(compoundMark.getMarkID().equals(1)) ? MarkType.OPEN_GATE : MarkType.CLOSED_GATE,
singleMark1,
singleMark2,
singleMark1.getLatitude(),
singleMark1.getLongitude());
markGroup = new MarkGroup(thisGateMark,
findScaledXY(thisGateMark.getSingleMark1()),
findScaledXY(thisGateMark.getSingleMark2()));
raceObjects.add(markGroup);
raceMarks.add(thisGateMark);
//Otherwise its a single mark
} else {
CompoundMark.Mark singleMark = compoundMark.getMarks().get(0);
Mark thisSingleMark = new SingleMark(singleMark.getMarkName(),
singleMark.getTargetLat(),
singleMark.getTargetLng(),
singleMark.getSourceID());
markGroup = new MarkGroup(thisSingleMark, findScaledXY(thisSingleMark));
raceObjects.add(markGroup);
raceMarks.add(thisSingleMark);
}
}
}
private void updateRaceObjects(){
for (RaceObject raceObject : raceObjects) {
raceObject.updatePosition(1000 / 60);
// some raceObjects will have multiply ID's (for instance gate marks)
for (long id : raceObject.getRaceIds()) {
//checking if the current "ID" has any updates associated with it
if (StreamParser.boatPositions.containsKey(id)) {
move(id, raceObject);
}
}
}
}
private void move(long id, RaceObject raceObject){
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatPositions.get(id);
if (movementQueue.size() > 0){
BoatPositionPacket positionPacket = movementQueue.peek();
//this code adds a delay to reading from the movementQueue
//in case things being put into the movement queue are slightly
//out of order
int delayTime = 1000;
int loopTime = delayTime * 10;
long timeDiff = (System.currentTimeMillis()%loopTime - positionPacket.getTimeValid()%loopTime);
if (timeDiff < 0){
timeDiff = loopTime + timeDiff;
}
if (timeDiff > delayTime) {
try {
positionPacket = movementQueue.take();
Point2D p2d = latLonToXY(positionPacket.getLat(), positionPacket.getLon());
double heading = 360.0 / 0xffff * positionPacket.getHeading();
raceObject.setDestination(p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), (int) id);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
class ResizableCanvas extends Canvas {
ResizableCanvas() {
// Redraw canvas when size changes.
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
}
private void draw() {
double width = getWidth();
double height = getHeight();
GraphicsContext gc = getGraphicsContext2D();
gc.clearRect(0, 0, width, height);
}
@Override
public boolean isResizable() {
return true;
}
@Override
public double prefWidth(double height) {
return getWidth();
}
@Override
public double prefHeight(double width) {
return getHeight();
}
}
private void drawFps(int fps){
if (raceViewController.isDisplayFps()){
gc.clearRect(5,5,50,20);
gc.setFill(Color.SKYBLUE);
gc.fillRect(4,4,51,21);
gc.setFill(Color.BLACK);
gc.setFont(new Font(14));
gc.setLineWidth(3);
gc.fillText(fps + " FPS", 5, 20);
} else {
gc.clearRect(5,5,50,20);
gc.setFill(Color.SKYBLUE);
gc.fillRect(4,4,51,21);
}
}
/**
* Draws all the boats.
*/
private void drawBoats() {
// Map<Boat, TimelineInfo> timelineInfos = raceViewController.getTimelineInfos();
// List<Boat> boats = raceViewController.getStartingBoats();
Map<Integer, Yacht> boats = StreamParser.getBoats();
Double startingX = raceObjects.get(0).getLayoutX();
Double startingY = raceObjects.get(0).getLayoutY();
Group boatAnnotations = new Group();
for (Yacht boat : boats.values()) {
// for (Boat boat : boats) {
boat.setColour(Colors.getColor());
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
boatGroup.moveTo(startingX, startingY, 0d);
//boatGroup.setStage(raceViewController.getStage());
raceObjects.add(boatGroup);
boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations());
}
group.getChildren().add(boatAnnotations);
group.getChildren().addAll(raceObjects);
}
/**
* Calculates x and y location for every marker that fits it to the canvas the race will be drawn on.
*/
private void fitMarksToCanvas() {
findMinMaxPoint();
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
givePointsXY();
addRaceBorder();
findMetersToPixels();
}
/**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker with the leftmost
* marker, rightmost marker, southern most marker and northern most marker respectively.
*/
private void findMinMaxPoint() {
List<Limit> sortedPoints = new ArrayList<>();
for (Limit limit : StreamParser.getXmlObject().getRaceXML().getCourseLimit()) {
sortedPoints.add(limit);
}
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
Limit minLatMark = sortedPoints.get(0);
Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1);
minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(), minLatMark.getLng(), minLatMark.getSeqID());
maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.getSeqID());
sortedPoints.sort(Comparator.comparingDouble(Limit::getLng));
//If the course is on a point on the earth where longitudes wrap around.
Limit minLonMark = sortedPoints.get(0);
Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1);
SingleMark thisMinLon = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID());
SingleMark thisMaxLon = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID());
// TODO: 30/03/17 cir27 - Correctly account for longitude wrapping around.
if (thisMaxLon.getLongitude() - thisMinLon.getLongitude() > 180) {
SingleMark temp = thisMinLon;
thisMinLon = thisMaxLon;
thisMaxLon = temp;
}
minLonPoint = thisMinLon;
maxLonPoint = thisMaxLon;
}
/**
* Calculates the location of a reference point, this is always the point with minimum latitude, in relation to the
* canvas.
*
* @param minLonToMaxLon The horizontal distance between the point of minimum longitude to maximum longitude.
*/
private void calculateReferencePointLocation (double minLonToMaxLon) {
Mark referencePoint = minLatPoint;
double referenceAngle;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
referencePointX = LHS_BUFFER + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint));
referencePointY = CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += TOP_BUFFER;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
} else {
referencePointY = CANVAS_HEIGHT - BOT_BUFFER;
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
referencePointX = LHS_BUFFER;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
referencePointX += ((CANVAS_WIDTH - (LHS_BUFFER + RHS_BUFFER)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
}
}
/**
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns it to distanceScaleFactor
* Returns the max horizontal distance of the map.
*/
private double scaleRaceExtremities () {
double vertAngle = Math.abs(Mark.calculateHeadingRad(minLatPoint, maxLatPoint));
double vertDistance = Math.cos(vertAngle) * Mark.calculateDistance(minLatPoint, maxLatPoint);
double horiAngle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint);
if (horiAngle <= (Math.PI / 2))
horiAngle = (Math.PI / 2) - horiAngle;
else
horiAngle = horiAngle - (Math.PI / 2);
double horiDistance = Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint);
double vertScale = (CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER)) / vertDistance;
if ((horiDistance * vertScale) > (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER))) {
distanceScaleFactor = (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
distanceScaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return horiDistance;
}
/**
* Give all markers in the course an x,y location relative to a given reference with a known x,y location. Distances
* are scaled according to the distanceScaleFactor variable.
*/
private void givePointsXY() {
List<Mark> allPoints = new ArrayList<>(raceViewController.getRace().getCourse());
List<Mark> processed = new ArrayList<>();
RaceObject markGroup;
for (Mark mark : allPoints) {
if (!processed.contains(mark)) {
if (mark.getMarkType() != MarkType.SINGLE_MARK) {
GateMark gateMark = (GateMark) mark;
markGroup = new MarkGroup(mark, findScaledXY(gateMark.getSingleMark1()), findScaledXY(gateMark.getSingleMark2()));
raceObjects.add(markGroup);
} else {
markGroup = new MarkGroup(mark, findScaledXY(mark));
raceObjects.add(markGroup);
}
processed.add(mark);
}
}
}
private Point2D findScaledXY (Mark unscaled) {
return findScaledXY (minLatPoint.getLatitude(), minLatPoint.getLongitude(),
unscaled.getLatitude(), unscaled.getLongitude());
}
private Point2D findScaledXY (double latA, double lonA, double latB, double lonB) {
double distanceFromReference;
double angleFromReference;
int xAxisLocation = (int) referencePointX;
int yAxisLocation = (int) referencePointY;
angleFromReference = Mark.calculateHeadingRad(latA, lonA, latB, lonB);
distanceFromReference = Mark.calculateDistance(latA, lonA, latB, lonB);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
/**
* Find the number of meters per pixel.
*/
private void findMetersToPixels () {
Double angularDistance;
Double angle;
Double straightLineDistance;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
angularDistance = Mark.calculateDistance(minLonPoint, maxLonPoint);
angle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint);
if (angle > Math.PI / 2) {
straightLineDistance = Math.cos(angle - Math.PI) * angularDistance;
} else {
straightLineDistance = Math.cos(angle) * angularDistance;
}
metersToPixels = (CANVAS_WIDTH - RHS_BUFFER - LHS_BUFFER) / straightLineDistance;
} else {
angularDistance = Mark.calculateDistance(minLatPoint, maxLatPoint);
angle = Mark.calculateHeadingRad(minLatPoint, maxLatPoint);
if (angle < Math.PI / 2) {
straightLineDistance = Math.cos(angle) * angularDistance;
} else {
straightLineDistance = Math.cos(-angle + Math.PI * 2) * angularDistance;
}
metersToPixels = (CANVAS_HEIGHT - TOP_BUFFER - BOT_BUFFER) / straightLineDistance;
}
}
private Point2D latLonToXY (double latitude, double longitude) {
return findScaledXY(minLatPoint.getLatitude(), minLatPoint.getLongitude(), latitude, longitude);
}
List<RaceObject> getRaceObjects() {
return raceObjects;
}
}
@@ -0,0 +1,164 @@
package seng302.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import seng302.models.Yacht;
import seng302.models.parsers.StreamParser;
import seng302.models.parsers.XMLParser;
import java.io.IOException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
public class Controller implements Initializable {
@FXML
private AnchorPane contentPane;
@FXML
private Label timeTillLive;
@FXML
private Button streamButton;
@FXML
private Button switchToRaceViewButton;
@FXML
private TableView<Yacht> teamList;
@FXML
private TableColumn<Yacht, String> boatNameCol;
@FXML
private TableColumn<Yacht, String> shortNameCol;
@FXML
private TableColumn<Yacht, String> countryCol;
@FXML
private TableColumn<Yacht, String> posCol;
@FXML
private Label realTime;
private XMLParser xmlParser;
private void setContentPane(String jfxUrl){
try{
contentPane.getChildren().removeAll();
contentPane.getChildren().clear();
contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
}
catch(javafx.fxml.LoadException e){
System.err.println(e.getCause());
}
catch(IOException e){
System.err.println(e);
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
//DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
//format.setTimeZone(TimeZone.getTimeZone("GMT-8"));
//realTime.setText(format.format(new Date()));
}
/**
* Running a timer to update the livestream status on welcome screen. Update interval is 1 second.
*/
public void startStream() {
if (StreamParser.isStreamStatus()) {
xmlParser = StreamParser.getXmlObject();
streamButton.setVisible(false);
realTime.setVisible(true);
timeTillLive.setVisible(true);
timeTillLive.setTextFill(Color.GREEN);
timeTillLive.setText("Connecting...");
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Platform.runLater(() -> {
if (StreamParser.isRaceFinished()) {
realTime.setText(StreamParser.getCurrentTimeString());
timeTillLive.setTextFill(Color.RED);
timeTillLive.setText("Race finished! Waiting for new race...");
switchToRaceViewButton.setDisable(true);
} else if (StreamParser.getTimeSinceStart() > 0) {
realTime.setText(StreamParser.getCurrentTimeString());
updateTeamList();
timeTillLive.setTextFill(Color.RED);
switchToRaceViewButton.setDisable(false);
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
String timerString = "-" + timerMinute + ":" + timerSecond;
timeTillLive.setText(timerString);
} else {
realTime.setText(StreamParser.getCurrentTimeString());
updateTeamList();
timeTillLive.setTextFill(Color.BLACK);
switchToRaceViewButton.setDisable(false);
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
String timerString = timerMinute + ":" + timerSecond;
timeTillLive.setText(timerString);
}
});
}
}, 0, 1000);
} else {
timeTillLive.setText("Stream not available.");
timeTillLive.setTextFill(Color.RED);
}
}
public void switchToRaceView() {
setContentPane("/views/RaceView.fxml");
}
private void updateTeamList() {
ObservableList<Yacht> data = FXCollections.observableArrayList();
teamList.setItems(data);
boatNameCol.setCellValueFactory(
new PropertyValueFactory<>("boatName")
);
shortNameCol.setCellValueFactory(
new PropertyValueFactory<>("shortName")
);
countryCol.setCellValueFactory(
new PropertyValueFactory<>("country")
);
posCol.setCellValueFactory(
new PropertyValueFactory<>("position")
);
// if (StreamParser.isRaceStarted()) {
data.addAll(StreamParser.getBoatsPos().values());
// } else {
// for (Yacht boat : StreamParser.getBoats().values()) {
// boat.setPosition("-");
// data.add(boat);
// }
// }
teamList.refresh();
// posCol.setSortType(TableColumn.SortType.ASCENDING);
// teamList.getSortOrder().add(posCol);
// posCol.setSortable(false);
}
}
@@ -0,0 +1,82 @@
package seng302.controllers;
import seng302.models.Race;
import seng302.models.Yacht;
import seng302.models.parsers.ConfigParser;
import seng302.models.parsers.CourseParser;
import seng302.models.parsers.StreamParser;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Random;
/**
* Created by zyt10 on 17/03/17.
* run before CanvasController to initialize race events
* the CanvasController then uses the event data to make the animations
*/
public class RaceController {
Race race = null;
public void initializeRace() {
String raceConfigFile = "/config/config.xml";
String teamsConfigFile = "/config/teams.xml";
try {
race = createRace(raceConfigFile, teamsConfigFile);
} catch (Exception e) {
System.out.println("There was an error creating the race.");
}
if (race != null) {
race.startRace();
} else {
System.out.println("There was an error creating the race. Exiting.");
}
}
public Race createRace(String configFile, String teamsConfigFile) throws Exception {
Race race = new Race();
// StreamParser.xmlObject
// Read team names from file
// TeamsParser tp = new TeamsParser(teamsConfigFile);
// Read course from file
// ConfigParser config = new ConfigParser(configFile);
ArrayList<String> boatNames = new ArrayList<>();
// ArrayList<Boat> teams = tp.getBoats();
Map<Long, Yacht> teams = StreamParser.getBoatsPos();
//get race size
int numberOfBoats = teams.size();
//get time scale
// double timeScale = config.getTimeScale();
// race.setTimeScale(timeScale);
for (Yacht boat : teams.values()) {
boatNames.add(boat.getBoatName());
race.addBoat(boat);
}
// Shuffle team names
long seed = System.nanoTime();
Collections.shuffle(boatNames, new Random(seed));
if (numberOfBoats > Array.getLength(boatNames.toArray())) {
return null;
}
CourseParser course = new CourseParser("/config/course.xml");
race.addCourse(course.getCourse());
return race;
}
public Race getRace() {
return race;
}
}
@@ -0,0 +1,37 @@
package seng302.controllers;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import seng302.models.Race;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Created by ptg19 on 20/03/17.
*/
public class RaceResultController implements Initializable{
@FXML private AnchorPane window;
@FXML private VBox resultsVBox;
private Race race;
RaceResultController(Race race){
this.race = race;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
int boatPosition = this.race.getFinishedBoats().length;
for (int i = this.race.getFinishedBoats().length - 1; i >= 0; i--){
resultsVBox.getChildren().add(0, new Text(boatPosition + ": " + this.race.getFinishedBoats()[i].getBoatName()));
boatPosition--;
}
}
}
@@ -0,0 +1,437 @@
package seng302.controllers;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Slider;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.util.StringConverter;
import seng302.models.*;
import seng302.models.parsers.ConfigParser;
import seng302.models.parsers.StreamParser;
import java.io.IOException;
import java.util.*;
/**
* Created by ptg19 on 29/03/17.
*/
public class RaceViewController extends Thread{
@FXML
private VBox positionVbox;
@FXML
private CheckBox toggleFps;
@FXML
private Text timerLabel;
@FXML
private AnchorPane contentAnchorPane;
@FXML
private Text windArrowText, windDirectionText;
@FXML
private Slider annotationSlider;
@FXML
private CanvasController includedCanvasController;
private ArrayList<Yacht> startingBoats = new ArrayList<>();
private boolean displayFps;
private Timeline timerTimeline;
private Map<Yacht, TimelineInfo> timelineInfos = new HashMap<>();
private ArrayList<Yacht> boatOrder = new ArrayList<>();
private Race race;
private Stage stage;
public void initialize() {
RaceController raceController = new RaceController();
raceController.initializeRace();
race = raceController.getRace();
for (Yacht boat : race.getBoats()) {
startingBoats.add(boat);
}
// try{
// initializeTimelines();
// }
// catch (Exception e){
// e.printStackTrace();
// }
includedCanvasController.setup(this);
includedCanvasController.initializeCanvas();
initializeTimer();
initializeSettings();
initialiseWindDirection();
initialisePositionVBox();
//set wind direction!!!!!!! can't find another place to put my code --haoming
// double windDirection = new ConfigParser("/config/config.xml").getWindDirection();
// windDirectionText.setText(String.format("%.1f°", windDirection));
// windArrowText.setRotate(windDirection);
includedCanvasController.timer.start();
}
private void initializeSettings() {
displayFps = true;
toggleFps.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
displayFps = !displayFps;
}
});
//SLIFER STUFF BELOW
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
@Override
public String toString(Double n) {
if (n == 0) return "None";
if (n == 1) return "Low";
if (n == 2) return "Medium";
if (n == 3) return "All";
return "All";
}
@Override
public Double fromString(String s) {
switch (s) {
case "None":
return 0d;
case "Low":
return 1d;
case "Medium":
return 2d;
case "All":
return 3d;
default:
return 3d;
}
}
});
annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
setAnnotations((int)annotationSlider.getValue()));
annotationSlider.setValue(3);
}
private void initializeTimer(){
timerTimeline = new Timeline();
timerTimeline.setCycleCount(Timeline.INDEFINITE);
// Run timer update every second
timerTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
event -> {
if (StreamParser.isRaceFinished()) {
timerLabel.setFill(Color.RED);
timerLabel.setText("Race Finished!");
} else {
timerLabel.setText(currentTimer());
}
})
);
// Start the timer
timerTimeline.playFromStart();
}
private void initialiseWindDirection() {
Timeline windDirTimeline = new Timeline();
windDirTimeline.setCycleCount(Timeline.INDEFINITE);
windDirTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
event -> {
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
windArrowText.setRotate(StreamParser.getWindDirection());
})
);
windDirTimeline.playFromStart();
}
private void initialisePositionVBox() {
Timeline posVBoxTimeline = new Timeline();
posVBoxTimeline.setCycleCount(Timeline.INDEFINITE);
posVBoxTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
event -> {
showOrder();
})
);
posVBoxTimeline.playFromStart();
}
/**
* Generates time line for each boat, and stores time time into timelineInfos hash map
*/
private void initializeTimelines() {
HashMap<Yacht, List> boat_events = race.getEvents();
for (Yacht boat : boat_events.keySet()) {
startingBoats.add(boat);
// // x, y are the real time coordinates
// DoubleProperty x = new SimpleDoubleProperty();
// DoubleProperty y = new SimpleDoubleProperty();
//
// List<KeyFrame> keyFrames = new ArrayList<>();
// List<Event> events = boat_events.get(boat);
//
// // iterates all events and convert each event to keyFrame, then add them into a list
// for (Event event : events) {
// if (event.getIsFinishingEvent()) {
// keyFrames.add(
// new KeyFrame(Duration.seconds(event.getTime()),
// onFinished -> {race.setBoatFinished(boat); handleEvent(event);},
// new KeyValue(x, event.getThisMark().getLatitude()),
// new KeyValue(y, event.getThisMark().getLongitude())
// )
// );
// } else {
// keyFrames.add(
// new KeyFrame(Duration.seconds(event.getTime()),
// onFinished ->{
// handleEvent(event);
// boat.setHeading(event.getBoatHeading());
// },
// new KeyValue(x, event.getThisMark().getLatitude()),
// new KeyValue(y, event.getThisMark().getLongitude())
// )
// );
// }
// }
// timelineInfos.put(boat, new TimelineInfo(new Timeline(keyFrames.toArray(new KeyFrame[keyFrames.size()])), x, y));
}
setRaceDuration();
}
private void setRaceDuration(){
Double maxDuration = 0.0;
Timeline maxTimeline = null;
for (TimelineInfo timelineInfo : timelineInfos.values()) {
Timeline timeline = timelineInfo.getTimeline();
if (timeline.getTotalDuration().toMillis() >= maxDuration) {
maxDuration = timeline.getTotalDuration().toMillis();
maxTimeline = timeline;
}
// Timelines are paused by default
timeline.play();
timeline.pause();
}
maxTimeline.setOnFinished(event -> {
race.setRaceFinished();
loadRaceResultView();
});
}
/**
* Play each boats timerTimeline
*/
public void playTimelines(){
for (TimelineInfo timelineInfo : timelineInfos.values()){
Timeline timeline = timelineInfo.getTimeline();
if (timeline.getStatus() == Animation.Status.PAUSED){
timeline.play();
}
}
}
/**
* Pause each boats timerTimeline
*/
public void pauseTimelines(){
for (TimelineInfo timelineInfo : timelineInfos.values()){
Timeline timeline = timelineInfo.getTimeline();
if (timeline.getStatus() == Animation.Status.RUNNING){
timeline.pause();
}
}
}
/**
* Display the list of boats in the order they finished the race
*/
private void loadRaceResultView() {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
loader.setController(new RaceResultController(race));
try {
contentAnchorPane.getChildren().removeAll();
contentAnchorPane.getChildren().clear();
contentAnchorPane.getChildren().addAll((Pane) loader.load());
} catch (javafx.fxml.LoadException e) {
System.err.println(e.getCause());
} catch (IOException e) {
System.err.println(e);
}
}
public void handleEvent(Event event) {
Yacht boat = event.getBoat();
boatOrder.remove(boat);
boat.setMarkLastPast(event.getMarkPosInRace());
boatOrder.add(boat);
boatOrder.sort(new Comparator<Yacht>() {
@Override
public int compare(Yacht b1, Yacht b2) {
return b2.getMarkLastPast() - b1.getMarkLastPast();
}
});
showOrder();
}
private void showOrder() {
positionVbox.getChildren().clear();
positionVbox.getChildren().removeAll();
// for (Boat boat : boatOrder) {
// positionVbox.getChildren().add(new Text(boat.getShortName() + " " + boat.getSpeedInKnots() + " Knots"));
// }
for (Yacht boat : StreamParser.getBoatsPos().values()) {
// System.out.println(boat.getBoatStatus());
if (boat.getBoatStatus() == 3) { // 3 is finish status
positionVbox.getChildren().add(new Text(boat.getPosition() + ". " +
boat.getShortName() + " (Finished)"));
} else {
positionVbox.getChildren().add(new Text(boat.getPosition() + ". " +
boat.getShortName() + " "));
}
}
}
/**
* Convert seconds to a string of the format mm:ss
*
* @param time the time in seconds
* @return a formatted string
*/
public String convertTimeToMinutesSeconds(int time) {
if (time < 0) {
return String.format("-%02d:%02d", (time * -1) / 60, (time * -1) % 60);
}
return String.format("%02d:%02d", time / 60, time % 60);
}
private String currentTimer() {
String timerString = "0:00";
if (StreamParser.getTimeSinceStart() > 0) {
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
timerString = "-" + timerMinute + ":" + timerSecond;
} else {
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
timerString = timerMinute + ":" + timerSecond;
}
return timerString;
}
public void stopTimer() {
timerTimeline.stop();
}
public void startTimer() {
timerTimeline.play();
}
public boolean isDisplayFps() {
return displayFps;
}
public Race getRace() {
return race;
}
public Map<Yacht, TimelineInfo> getTimelineInfos() {
return timelineInfos;
}
public ArrayList<Yacht> getStartingBoats(){
return startingBoats;
}
private void setAnnotations(Integer annotationLevel) {
switch (annotationLevel) {
case 0:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if(ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(false);
bg.setVelocityObjectVisible(false);
bg.setLineGroupVisible(false);
bg.setWakeVisible(false);
}
}
break;
case 1:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if(ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(true);
bg.setVelocityObjectVisible(false);
bg.setLineGroupVisible(false);
bg.setWakeVisible(false);
}
}
break;
case 2:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if(ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(true);
bg.setVelocityObjectVisible(false);
bg.setLineGroupVisible(true);
bg.setWakeVisible(false);
}
}
break;
case 3:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if(ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(true);
bg.setVelocityObjectVisible(true);
bg.setLineGroupVisible(true);
bg.setWakeVisible(true);
}
}
break;
}
}
void setStage (Stage stage) {
this.stage = stage;
}
Stage getStage () {
return stage;
}
}
+358
View File
@@ -0,0 +1,358 @@
package seng302.models;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
/**
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 dimensional boat.
* It contains a single polygon for the boat, a group of lines to show it's path, a wake object and two text labels to
* annotate the boat teams name and the boats velocity. The boat will update it's position onscreen everytime
* UpdatePosition is called unless the window is minimized in which case it attempts to store animations and apply them
* when the window is maximised.
*/
public class BoatGroup extends RaceObject{
//Constants for drawing
private static final double TEAMNAME_X_OFFSET = 10d;
private static final double TEAMNAME_Y_OFFSET = -15d;
private static final double VELOCITY_X_OFFSET = 10d;
private static final double VELOCITY_Y_OFFSET = -5d;
private static final double BOAT_HEIGHT = 15d;
private static final double BOAT_WIDTH = 10d;
//Variables for boat logic.
private Point2D lastPoint;
private int wakeGenerationDelay = 10;
private double distanceTravelled;
//Graphical objects
private Yacht boat;
private Group lineGroup = new Group();
private Polygon boatPoly;
private Text teamNameObject;
private Text velocityObject;
private Wake wake;
//Handles boat moving when connecting to a stream
private boolean setToInitialLocation = false;
private boolean destinationSet;
//Variables for handling minimization
private Stage stage;
private boolean isMaximized= true;
private List<Line> lineStorage = new ArrayList<>();
private int setCallCount = 5;
/**
* Creates a BoatGroup with the default triangular boat polygon.
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which
* BoatGroup to update.
* @param color The colour of the boat polygon and the trailing line.
*/
public BoatGroup (Yacht boat, Color color){
this.boat = boat;
initChildren(color);
}
/**
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be at point (0,0).
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which
* BoatGroup to update.
* @param color The colour of the boat polygon and the trailing line.
* @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)
{
this.boat = boat;
initChildren(color, points);
}
/**
* Creates the javafx objects that will be the in the group by default.
* @param color The colour of the boat polygon and the trailing line.
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat polygon.
*/
private void initChildren (Color color, double... points) {
boatPoly = new Polygon(points);
boatPoly.setFill(color);
teamNameObject = new Text(boat.getShortName());
velocityObject = new Text(String.valueOf(boat.getVelocity()));
teamNameObject.setX(TEAMNAME_X_OFFSET);
teamNameObject.setY(TEAMNAME_Y_OFFSET);
teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY());
velocityObject.setX(VELOCITY_X_OFFSET);
velocityObject.setY(VELOCITY_Y_OFFSET);
velocityObject.relocate(velocityObject.getX(), velocityObject.getY());
destinationSet = false;
wake = new Wake(0, -BOAT_HEIGHT);
super.getChildren().addAll(teamNameObject, velocityObject, boatPoly);
}
/**
* Creates the javafx objects that will be the in the group by default.
* @param color The colour of the boat polygon and the trailing line.
*/
private void initChildren (Color color) {
initChildren(color,
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
0.0, -BOAT_HEIGHT / 2,
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
}
/**
* Moves the boat and its children annotations from its current coordinates by specified amounts.
* @param dx The amount to move the X coordinate by
* @param dy The amount to move the Y coordinate by
*/
public void moveGroupBy(double dx, double dy, double rotation) {
boatPoly.setLayoutX(boatPoly.getLayoutX() + dx);
boatPoly.setLayoutY(boatPoly.getLayoutY() + dy);
teamNameObject.setLayoutX(teamNameObject.getLayoutX() + dx);
teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy);
velocityObject.setLayoutX(velocityObject.getLayoutX() + dx);
velocityObject.setLayoutY(velocityObject.getLayoutY() + dy);
wake.setLayoutX(wake.getLayoutX() + dx);
wake.setLayoutY(wake.getLayoutY() + dy);
rotateTo(rotation + currentRotation);
}
/**
* Moves the boat and its children annotations to coordinates specified
* @param x The X coordinate to move the boat to
* @param y The Y coordinate to move the boat to
* @param rotation The heading in degrees from north the boat should rotate to.
*/
public void moveTo (double x, double y, double rotation) {
rotateTo(rotation);
moveTo(x, y);
}
/**
* Moves the boat and its children annotations to coordinates specified
* @param x The X coordinate to move the boat to
* @param y The Y coordinate to move the boat to
*/
public void moveTo (double x, double y) {
boatPoly.setLayoutX(x);
boatPoly.setLayoutY(y);
teamNameObject.setLayoutX(x);
teamNameObject.setLayoutY(y);
velocityObject.setLayoutX(x);
velocityObject.setLayoutY(y);
wake.setLayoutX(x);
wake.setLayoutY(y);
wake.rotate(currentRotation);
}
/**
* Updates the position of all graphics in the BoatGroup based off of the given time interval.
* @param timeInterval The interval, in milliseconds, the boat should update it's position based on.
*/
public void updatePosition (long timeInterval) {
//Calculate the movement of the boat.
if (isMaximized) {
double dx = pixelVelocityX * timeInterval;
double dy = pixelVelocityY * timeInterval;
double rotation = rotationalVelocity * timeInterval;
distanceTravelled += Math.abs(dx) + Math.abs(dy);
moveGroupBy(dx, dy, rotation);
//Draw a new section of the trail every 20 pixels of movement.
if (distanceTravelled > 20) {
distanceTravelled = 0;
if (lastPoint != null) {
Line l = new Line(
lastPoint.getX(),
lastPoint.getY(),
boatPoly.getLayoutX(),
boatPoly.getLayoutY()
);
l.getStrokeDashArray().setAll(3d, 7d);
l.setStroke(boatPoly.getFill());
lineGroup.getChildren().add(l);
}
if (destinationSet) { //Only begin drawing after the first destination is set
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
}
}
wake.updatePosition(timeInterval);
}
}
/**
* Sets the destination of the boat and the headng it should have once it reaches
* @param newXValue The X co-ordinate the boat needs to move to.
* @param newYValue The Y co-ordinate the boat needs to move to.
* @param rotation Rotation to move graphics to.
* @param raceIds RaceID of the object to move.
*/
public void setDestination (double newXValue, double newYValue, double rotation, double groundSpeed, int... raceIds) {
if (hasRaceId(raceIds)) {
if (setToInitialLocation) {
destinationSet = true;
boat.setVelocity(groundSpeed);
if (currentRotation < 0)
currentRotation = 360 - currentRotation;
double dx = newXValue - boatPoly.getLayoutX();
double dy = newYValue - boatPoly.getLayoutY();
//Check movement is reasonable. Assumes a 1000 * 1000 canvas
if (Math.abs(dx) > 50 || Math.abs(dy) > 50) {
dx = 0;
dy = 0;
moveTo(newXValue, newYValue);
}
pixelVelocityX = dx / expectedUpdateInterval;
pixelVelocityY = dy / expectedUpdateInterval;
rotationalGoal = rotation;
calculateRotationalVelocity();
if (wakeGenerationDelay > 0) {
wake.rotate(rotationalGoal);
rotateTo(rotationalGoal); //Need to test with this removed.
rotationalVelocity = 0;
wakeGenerationDelay--;
} else {
wake.setRotationalVelocity(rotationalVelocity, rotationalGoal, boat.getVelocity());
}
velocityObject.setText(String.format("%.2f m/s", boat.getVelocity()));
} else {
setToInitialLocation = true;
rotationalGoal = rotation;
moveTo(newXValue, newYValue, rotation);
}
}
//If minimized generate lines every 5 calls to set destination.
if (!isMaximized) {
setToInitialLocation = false;
wakeGenerationDelay = 2;
if(setCallCount-- == 0) {
setCallCount = 5;
if (lastPoint != null) {
Line l = new Line(
lastPoint.getX(),
lastPoint.getY(),
newXValue,
newYValue
);
l.getStrokeDashArray().setAll(3d, 7d);
l.setStroke(boatPoly.getFill());
lineStorage.add(l);
}
if (destinationSet) { //Only begin drawing after the first destination is set
lastPoint = new Point2D(newXValue, newYValue);
}
}
}
}
public void setDestination (double newXValue, double newYValue, double groundSpeed, int... raceIDs) {
destinationSet = true;
if (hasRaceId(raceIDs)) {
double rotation = Math.abs(
Math.toDegrees(
Math.atan(
(newYValue - boatPoly.getLayoutY()) / (newXValue - boatPoly.getLayoutX())
)
)
);
setDestination(newXValue, newYValue, rotation, groundSpeed, raceIDs);
}
}
public void rotateTo (double rotation) {
currentRotation = rotation;
boatPoly.getTransforms().setAll(new Rotate(rotation));
}
public void forceRotation () {
rotateTo (rotationalGoal);
wake.rotate(rotationalGoal);
}
public void setTeamNameObjectVisible(Boolean visible) {
teamNameObject.setVisible(visible);
}
public void setVelocityObjectVisible(Boolean visible) {
velocityObject.setVisible(visible);
}
public void setLineGroupVisible(Boolean visible) {
lineGroup.setVisible(visible);
}
public void setWakeVisible(Boolean visible) {
wake.setVisible(visible);
}
public Yacht getBoat() {
return boat;
}
/**
* Returns true if this BoatGroup contains at least one of the given IDs.
*
* @param raceIds The ID's to check the BoatGroup for.
* @return True if the BoatGroup contains at east one of the given IDs, false otherwise.
*/
public boolean hasRaceId (int... raceIds) {
for (int id : raceIds) {
if (id == boat.getSourceID())
return true;
}
return false;
}
/**
* Returns all raceIds associated with this group. For BoatGroups the ID's are for the boat.
*
* @return An array containing all ID's associated with this RaceObject.
*/
public int[] getRaceIds () {
return new int[] {boat.getSourceID()};
}
/**
* Due to javaFX limitations annotations associated with a boat that you want to appear below all boats in the
* Z-axis need to be pulled out of the BoatGroup and added to the parent group of the BoatGroups. This function
* returns these annotations as a group.
*
* @return A group containing low priority annotations.
*/
public Group getLowPriorityAnnotations () {
Group group = new Group();
group.getChildren().addAll(wake, lineGroup);
return group;
}
/**
* Use this function to let the BoatGroup know about the stage it is in. If it knows about it's stage then it will
* listen to the iconified property of that stage and change it's behaviour upon minimization. Without setting the
* Stage there is guarantee that the BoatGroup will draw properly when the stage is minimized.
*
* @param stage The stage that the BoatGroup is added to.
*/
public void setStage (Stage stage) {
/* TODO: 4/05/17 cir27 - Find a way to get the stage to this point. Need to pass it through multiple controllers.
App.start() -> Controller.setContentPane -> RaceViewController -> CanvasController
*/
this.stage = stage;
this.stage.iconifiedProperty().addListener(e -> {
isMaximized = !stage.isIconified();
if (!lineStorage.isEmpty()) {
lineGroup.getChildren().addAll(lineStorage);
lineStorage.clear();
}
});
}
}
+19
View File
@@ -0,0 +1,19 @@
package seng302.models;
import javafx.scene.paint.Color;
/**
* Created by ryan_ on 16/03/2017.
*/
public enum Colors {
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE;
static Integer index = 0;
public static Color getColor() {
if (index == 6) {
index = 0;
}
return Color.valueOf(values()[index++].toString());
}
}
+172
View File
@@ -0,0 +1,172 @@
package seng302.models;
import seng302.models.mark.Mark;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Event class containing the time of specific event, related team/boat, and
* event location such as leg.
*/
public class Event {
private Double time; // Time the event occurs
private Yacht boat;
private boolean isFinishingEvent = false; // This event occurs when a boat finishes the race
private Mark mark1; // This mark
private Mark mark2; // Next mark
private int markPosInRace; // the position of the current mark in the race course
private double heading;
private final double ORIGIN_LAT = 32.320504;
private final double ORIGIN_LON = -64.857063;
private final double SCALE = 16000;
/**
* Event class containing the time of specific event, related team/boat, and
* event location such as leg.
*
* @param eventTime, what time the event happens
* @param eventBoat, the boat that the event belongs to
*/
public Event(Double eventTime, Yacht eventBoat, Mark mark1, Mark mark2, int markPosInRace) {
this.time = eventTime;
this.boat = eventBoat;
this.mark1 = mark1;
this.mark2 = mark2;
this.markPosInRace = markPosInRace;
this.heading = angleFromCoordinate(mark1, mark2);
}
/**
* Event class containing the time of specific event, related team/boat, and
* event location such as leg.
*
* @param eventTime, what time the event happens
* @param eventBoat, the boat that the event belongs to
*/
public Event(Double eventTime, Yacht eventBoat, Mark mark1, int markPosInRace) {
this.time = eventTime;
this.boat = eventBoat;
this.mark1 = mark1;
this.markPosInRace = markPosInRace;
this.isFinishingEvent = true;
}
public double getTime() {
return this.time;
}
public void setTime(double eventTime) {
this.time = eventTime;
}
/**
* Gets the time in a formatted string
*
* @return the string of time
*/
public String getTimeString() {
return (new SimpleDateFormat("mm:ss:SSS")).format(new Date(time.longValue()));
}
public Yacht getBoat() {
return this.boat;
}
public void setBoat(Yacht eventBoat) {
this.boat = eventBoat;
}
public boolean getIsFinishingEvent() {
return this.isFinishingEvent;
}
/**
* Get a string that contains the timestamp and course information for this event
*
* @return A string that details what happened in this event
*/
public String getEventString() {
// This event is a boat finishing the race
if (this.isFinishingEvent) {
return (this.getTimeString() + ", " + this.getBoat().getBoatName() + " finished the race");
}
// System.out.println(this.getDistanceBetweenMarks());
return (this.getTimeString() + ", " + this.getBoat().getBoatName() + " passed " + this.mark1.getName() + " going heading " + this.getBoatHeading() + "°");
}
/**
* @return the distance between the two marks
*/
public double getDistanceBetweenMarks() {
double earth_radius = 6378.137;
double dLat = this.mark2.getLatitude() * Math.PI / 180 - this.mark1.getLatitude() * Math.PI / 180;
double dLon = this.mark2.getLongitude() * Math.PI / 180 - this.mark1.getLongitude() * Math.PI / 180;
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.mark1.getLatitude() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double d = earth_radius * c;
return d * 1000;
}
/**
* Calculates current boat heading direction.
* @return the boats heading as degree. vertical upward is 0 degree, and degree goes up clockwise.
*/
public double getBoatHeading() {
if (mark2 == null){
return 0.0;
}
double x1 = (mark1.getLongitude() - ORIGIN_LON) * SCALE;
double y1 = (ORIGIN_LAT - mark1.getLatitude()) * SCALE;
double x2 = (mark2.getLongitude() - ORIGIN_LON) * SCALE;
double y2 = (ORIGIN_LAT - mark2.getLatitude()) * SCALE;
double headingRadians = Math.atan2(y2-y1, x2-x1);
if (headingRadians < 0){
headingRadians += 2 * Math.PI;
}
// Convert back to degrees, and flip 180 degrees
// return ((headingRadians) * 180) / Math.PI;
return (Math.toDegrees(headingRadians) + 90) % 360;
}
/**
* Calculates the angle between to angular co-ordinates on a sphere.
*
* @param geoPointOne first geographical location
* @param geoPointTwo second geographical location
* @return the angle from point one to point two
*/
private Double angleFromCoordinate(Mark geoPointOne, Mark geoPointTwo) {
if (geoPointTwo == null)
return null;
double x1 = geoPointOne.getLatitude();
double y1 = -geoPointOne.getLongitude();
double x2 = geoPointTwo.getLatitude();
double y2 = -geoPointTwo.getLongitude();
return Math.toDegrees(Math.atan2(x2-x1, y2-y1));
}
public double getHeading() {
return heading;
}
public Mark getThisMark() {
return this.mark1;
}
public int getMarkPosInRace() {
return markPosInRace;
}
}
@@ -1,4 +1,6 @@
package seng302; package seng302.models;
import seng302.models.mark.SingleMark;
/** /**
* Represents the leg of a race. * Represents the leg of a race.
@@ -7,19 +9,19 @@ public class Leg {
private int heading; private int heading;
private int distance; private int distance;
private boolean isFinishingLeg; private boolean isFinishingLeg;
private Marker startingMarker; private SingleMark startingSingleMark;
/** /**
* Create a new leg * Create a new leg
* *
* @param heading, the magnetic heading of this leg * @param heading, the magnetic heading of this leg
* @param distance, the total distance of this leg in meters * @param distance, the total distance of this leg in meters
* @param marker, the marker this leg starts on * @param singleMark, the singleMark this leg starts on
*/ */
public Leg(int heading, int distance, Marker marker) { public Leg(int heading, int distance, SingleMark singleMark) {
this.heading = heading; this.heading = heading;
this.distance = distance; this.distance = distance;
this.startingMarker = marker; this.startingSingleMark = singleMark;
this.isFinishingLeg = false; this.isFinishingLeg = false;
} }
@@ -33,12 +35,13 @@ public class Leg {
public Leg(int heading, int distance, String markerName) { public Leg(int heading, int distance, String markerName) {
this.heading = heading; this.heading = heading;
this.distance = distance; this.distance = distance;
this.startingMarker = new Marker(markerName); this.startingSingleMark = new SingleMark(markerName);
this.isFinishingLeg = false; this.isFinishingLeg = false;
} }
/** /**
* Get the heading of this leg * Get the heading of this leg
* @return int
*/ */
public int getHeading() { public int getHeading() {
return this.heading; return this.heading;
@@ -46,6 +49,7 @@ public class Leg {
/** /**
* Set the heading for this leg * Set the heading for this leg
* @param heading
*/ */
public void setHeading(int heading) { public void setHeading(int heading) {
this.heading = heading; this.heading = heading;
@@ -53,6 +57,7 @@ public class Leg {
/** /**
* Get the total distance of this leg in meters * Get the total distance of this leg in meters
* @return int
*/ */
public int getDistance() { public int getDistance() {
return this.distance; return this.distance;
@@ -60,6 +65,7 @@ public class Leg {
/** /**
* Set the distance of this leg in meters * Set the distance of this leg in meters
* @param distance
*/ */
public void setDistance(int distance) { public void setDistance(int distance) {
this.distance = distance; this.distance = distance;
@@ -67,31 +73,29 @@ public class Leg {
/** /**
* Returns the marker this leg started on * Returns the marker this leg started on
* @return SingleMark
*/ */
public Marker getMarker() { public SingleMark getMarker() {
return this.startingMarker; return this.startingSingleMark;
} }
/** /**
* Set the marker this leg starts on * Set the singleMark this leg starts on
* @param singleMark
*/ */
public void setMarker(Marker marker) { public void setMarker(SingleMark singleMark) {
this.startingMarker = marker; this.startingSingleMark = singleMark;
} }
/** /**
* Returns the name of the marker this leg started on * Returns the name of the marker this leg started on
* @return String
*/ */
public String getMarkerLabel() { public String getMarkerLabel() {
return this.startingMarker.getName(); return this.startingSingleMark.getName();
} }
/**
* Tell the marker that the boat has passed it
*/
public void addBoatToMarker(Boat boat) {
this.startingMarker.addBoat(boat);
}
/** /**
* Specify whether or not the race finishes on this leg * Specify whether or not the race finishes on this leg
+198
View File
@@ -0,0 +1,198 @@
package seng302.models;
import seng302.models.mark.Mark;
import java.util.*;
/**
* Race class containing the boats and legs in the race
* Created by mra106 on 8/3/2017.
*/
public class Race {
private ArrayList<Yacht> boats; // The boats in the race
private ArrayList<Yacht> finishingOrder; // The order in which the boats finish the race
private HashMap<Yacht, List> events = new HashMap<>(); // The events that occur in the race
private List<Mark> course; // Marks in the race
private long startTime = 0;
private double timeScale = 1;
private boolean raceFinished = false; // Race is finished
private int raceTime = -2; // Current time in the race
/**
* Race class containing the boats and legs in the race
*/
public Race() {
this.boats = new ArrayList<>();
this.finishingOrder = new ArrayList<>();
this.course = new ArrayList<>();
}
/**
* Add a boat to the race
*
* @param boat, the boat to add
*/
public void addBoat(Yacht boat) {
boats.add(boat);
}
/**
* Returns a list of boats in a random order
*
* @return a list of boats
*/
public Yacht[] getShuffledBoats() {
// Shuffle the list of boats
long seed = System.nanoTime();
Collections.shuffle(this.boats, new Random(seed));
return boats.toArray(new Yacht[boats.size()]);
}
/**
* Returns a list of boats in the order that they
* finished the race (position 0 is first place)
*
* @return a list of boats
*/
public Yacht[] getFinishedBoats() {
return this.finishingOrder.toArray(new Yacht[this.finishingOrder.size()]);
}
/**
* Returns a list of boats in the race
*
* @return a list of the boats competing in the race
*/
public Yacht[] getBoats() {
return boats.toArray(new Yacht[boats.size()]);
}
/**
* Sets time scale
*
* @param timeScale
*/
public void setTimeScale(double timeScale) {
this.timeScale = timeScale;
}
/**
* Generate all events that will happen during the race.
*/
private void generateEvents() {
for (Yacht boat : this.boats) {
double totalDistance = 0;
int numberOfMarks = this.course.size();
for (int i = 0; i < numberOfMarks; i++) {
Double time = (totalDistance / boat.getVelocity() / timeScale);
// If there are singleMarks after this event
if (i < numberOfMarks - 1) {
Event event = new Event(time, boat, course.get(i), course.get(i + 1), i);
try {
events.get(boat).add(event);
} catch (NullPointerException e) {
events.put(boat, new ArrayList<>(Arrays.asList(event)));
}
totalDistance += event.getDistanceBetweenMarks();
//System.out.println(totalDistance);
//System.out.println(boat.getVelocity());
}
// There are no more marks after this event
else{
Event event = new Event(time, boat, course.get(i), i);
events.get(boat).add(event);
}
}
}
}
/**
* Starts a race and generates all events for the race.
*/
public void startRace() {
// record start time.
this.startTime = System.currentTimeMillis();
generateEvents();
}
/**
* Set the race course
* @param course a list of marks in the course
*/
public void addCourse(List<Mark> course) {
this.course = course;
}
/**
* Get a list of marks in the course
* @return
*/
public List<Mark> getCourse() {
return course;
}
/**
* Get a map of the events in the race
* @return
*/
public HashMap<Yacht, List> getEvents() {
return events;
}
/**
* Set a boat as finished
* @param boat The boat that has finished the race/home/cosc/student/wmu16
*/
public void setBoatFinished(Yacht boat){
this.finishingOrder.add(boat);
}
/**
* Set the race as finished
*/
public void setRaceFinished(){
this.raceFinished = true;
}
/**
* Return whether or not the race is finished
* @return true if the race is finished
*/
public boolean isRaceFinished(){
return this.raceFinished;
}
/**
* Set the race time
* @param raceTime the race time in seconds
*/
public void setRaceTime(int raceTime){
this.raceTime = raceTime;
}
/**
* Return the race time
* @return the race time in seconds
*/
public int getRaceTime(){
return this.raceTime;
}
/**
* Increment the race time by one second
*/
public void incrementRaceTime(){
this.raceTime += this.timeScale;
}
}
@@ -0,0 +1,87 @@
package seng302.models;
import javafx.geometry.Point2D;
import javafx.scene.Group;
/**
* RaceObject defines the behaviour that animated objects whose position is updated from a yacht race data stream must
* adhere to.
*/
public abstract class RaceObject extends Group {
//Time between sections of race
protected static double expectedUpdateInterval = 200;
protected double rotationalGoal;
protected double currentRotation;
protected double rotationalVelocity;
protected double pixelVelocityX;
protected double pixelVelocityY;
public Point2D getPosition () {
return new Point2D(super.getLayoutX(), getLayoutY());
}
public static double getExpectedUpdateInterval() {
return expectedUpdateInterval;
}
/**
*
*/
public static void setExpectedUpdateInterval(double expectedUpdateInterval) {
RaceObject.expectedUpdateInterval = expectedUpdateInterval;
}
/**
* Calculates the rotational velocity required to reach the rotationalGoal from the currentRotation.
*/
protected void calculateRotationalVelocity () {
if (Math.abs(rotationalGoal - currentRotation) > 180) {
if (rotationalGoal - currentRotation >= 0) {
this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval;
} else {
this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval;
}
} else {
this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval;
}
//Sometimes the rotation is too large to be realistic. In that case just do it instantly.
if (Math.abs(rotationalVelocity) > 1) {
rotationalVelocity = 0;
rotateTo(rotationalGoal);
}
}
/**
* Sets the destination of everything within the RaceObject that has an ID in the array raceIds. The destination is
* set to the co-ordinates (x, y) with the given rotation.
* @param x X co-ordinate to move the graphics to.
* @param y Y co-ordinate to move the graphics to.
* @param rotation Rotation to move graphics to.
* @param raceIds RaceID of the object to move.
*/
public abstract void setDestination (double x, double y, double rotation, double groundSpeed, int... raceIds);
/**
* Sets the destination of everything within the RaceObject that has an ID in the array raceIds. The destination is
* set to the co-ordinates (x, y).
* @param x X co-ordinate to move the graphic to.
* @param y Y co-ordinate to move the graphic to.
* @param raceIds RaceID to the object to move.
*/
public abstract void setDestination (double x, double y, double groundSpeed, int... raceIds);
public abstract void updatePosition (long timeInterval);
public abstract void moveTo (double x, double y, double rotation);
public abstract void moveTo (double x, double y);
public abstract void moveGroupBy(double x, double y, double rotation);
public abstract void rotateTo (double rotation);
public abstract boolean hasRaceId (int... raceIds);
public abstract int[] getRaceIds ();
}
@@ -0,0 +1,31 @@
package seng302.models;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
/**
* Created by zyt10 on 17/03/17.
* this class is literally just to associate a timeline with a DoubleProperty x and y
*/
public class TimelineInfo {
private Timeline timeline;
private DoubleProperty x;
private DoubleProperty y;
public TimelineInfo(Timeline timeline, DoubleProperty x, DoubleProperty y) {
this.timeline = timeline;
this.x = x;
this.y = y;
}
public Timeline getTimeline() {
return timeline;
}
public DoubleProperty getX() {
return x;
}
public DoubleProperty getY() {
return y;
}
}
+107
View File
@@ -0,0 +1,107 @@
package seng302.models;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.transform.Rotate;
/**
* By default wake is a group containing 5 arcs. Each arc starts from the same point. Each arc is larger and more
* transparent than the last. On calling updatePositions() arcs rotate at velocities given by setRotationalVelocity().
* The larger and more transparent an arc is the longer the delay before it rotates at the latest velocity. It is
* assumed that rotationalVelocities() are set regularly as wakes do not stop rotating and an array of velocities needs
* to be populated for the class to work as expected.
*/
class Wake extends Group {
private int numWakes = 5;
private double[] velocities = new double[13];
private Arc[] arcs = new Arc[numWakes];
private double[] rotations = new double[numWakes];
private int[] velocityIndices = new int[numWakes];
private double sum = 0;
private static double max;
/**
* Create a wake at the given location.
* @param startingX x location where the tip of wake arcs will be.
* @param startingY y location where the tip of wake arcs will be.
*/
Wake(double startingX, double startingY) {
super.setLayoutX(startingX);
super.setLayoutY(startingY);
Arc arc;
for (int i = 0; i < numWakes; i++) {
//Default triangle is -110 deg out of phase with a default wake and has angle of 40 deg.
arc = new Arc(0,0,0,0,-110,40);
//Opacity increases from 0.5 -> 0 evenly over the 5 wake arcs.
arc.setFill(new Color(0.18, 0.7, 1.0, 1.0 + -0.175 * i));
arc.setType(ArcType.ROUND);
arcs[i] = arc;
}
super.getChildren().addAll(arcs);
}
/**
* Sets the rotationalVelocity of each arc. Each arc is 3 velocities behind the next smallest arc. The smallest uses
* the latest given velocity.
* @param rotationalVelocity The rotationalVelocity the wake should move at.
* @param rotationGoal Where the wake will rotate to if the wake is calculated to be on a straight section. This is
* used to prevent desynchronisation with the Boat polygon.
* @param velocity The real world velocity of the boat in m/s.
*/
void setRotationalVelocity (double rotationalVelocity, double rotationGoal, double velocity) {
sum -= Math.abs(velocities[(velocityIndices[0] + 10) % 13]);
sum += Math.abs(rotationalVelocity);
max = Math.max(max, rotationalVelocity);
if (sum < (max / 3))
rotate (rotationGoal); //In relatively straight segments the wake snaps to match the boats current position.
//This stops the wake from eventually becoming out of sync with the boat.
//This accounts for rogue rotations that are greater than what would be realistic. Value is kinda rough.
//Basically just for our internal mock.
if (Math.abs(rotationalVelocity) > 0.05) {
rotationalVelocity = 0;
rotate(rotationGoal);
}
//Update the index of the array of recent velocities that each wake uses. Each wake is 3 velocities behind the
//next smallest wake.
velocityIndices[0] = (13 + (velocityIndices[0] - 1) % 13) % 13;
velocities[velocityIndices[0]] = rotationalVelocity;
for (int i = 1; i < numWakes; i++)
velocityIndices[i] = (velocityIndices[0] + 3 * i) % 13;
//Scale wakes based on velocity.
double baseRad = 20;
double rad;
for (Arc arc :arcs) {
rad = baseRad + velocity;
arc.setRadiusX(rad);
arc.setRadiusY(rad);
baseRad += 5 + (velocity / 2);
}
}
/**
* Arcs rotate based on the distance they would have travelled over the supplied time interval.
* @param timeInterval the time interval, in microseconds, that the wake should move.
*/
void updatePosition (long timeInterval) {
for (int i = 0; i < numWakes; i++) {
rotations[i] = rotations[i] + velocities[velocityIndices[i]] * timeInterval;
arcs[i].getTransforms().setAll(new Rotate(rotations[i]));
}
}
/**
* Rotate all wakes to the given rotation.
* @param rotation the from north angle in degrees to rotate to.
*/
void rotate (double rotation) {
for (int i = 0; i < arcs.length; i++) {
rotations[i] = rotation;
arcs[i].getTransforms().setAll(new Rotate(rotation));
}
}
}
+167
View File
@@ -0,0 +1,167 @@
package seng302.models;
import javafx.scene.paint.Color;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
/**
* Yacht class for the racing boat.
*
* 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 {
private Color colour;
private double velocity;
private Integer markLastPast;
private String boatType;
private Integer sourceID;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
private String boatName;
private String country;
// Boat status
private Integer boatStatus;
private Integer legNumber;
private Integer penaltiesAwarded;
private Integer penaltiesServed;
private Long estimateTimeAtNextMark;
private Long estimateTimeAtFinish;
private String position;
/**
* Used in EventTest and RaceTest.
*
* @param boatName Create a yacht object with name.
*/
public Yacht (String boatName) {
this.boatName = boatName;
}
/**
* Used in BoatGroupTest.
*
* @param boatName The name of the team sailing the boat
* @param boatVelocity The speed of the boat in meters/second
* @param shortName A shorter version of the teams name
*/
public Yacht(String boatName, double boatVelocity, String shortName, int id) {
this.boatName = boatName;
this.velocity = boatVelocity;
this.shortName = shortName;
this.sourceID = id;
}
public Yacht(String boatType, Integer sourceID, String hullID, String shortName, String boatName, String country) {
this.boatType = boatType;
this.sourceID = sourceID;
this.hullID = hullID;
this.shortName = shortName;
this.boatName = boatName;
this.country = country;
}
public String getBoatType() {
return boatType;
}
public Integer getSourceID() {
return sourceID;
}
public String getHullID() {
return hullID;
}
public String getShortName() {
return shortName;
}
public String getBoatName() {
return boatName;
}
public String getCountry() {
return country;
}
public Integer getBoatStatus() {
return boatStatus;
}
public void setBoatStatus(Integer boatStatus) {
this.boatStatus = boatStatus;
}
public Integer getLegNumber() {
return legNumber;
}
public void setLegNumber(Integer 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 Long getEstimateTimeAtNextMark() {
// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
// return format.format(estimateTimeAtNextMark);
return estimateTimeAtNextMark;
}
public void setEstimateTimeAtNextMark(Long estimateTimeAtNextMark) {
this.estimateTimeAtNextMark = estimateTimeAtNextMark;
}
public String getEstimateTimeAtFinish() {
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
return format.format(estimateTimeAtFinish);
}
public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) {
this.estimateTimeAtFinish = estimateTimeAtFinish;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public Color getColour() {
return colour;
}
public void setColour(Color colour) {
this.colour = colour;
}
public double getVelocity() {
return velocity;
}
public void setVelocity(double velocity) {
this.velocity = velocity;
}
public Integer getMarkLastPast() {
return markLastPast;
}
public void setMarkLastPast(Integer markLastPast) {
this.markLastPast = markLastPast;
}
}
@@ -0,0 +1,51 @@
package seng302.models.mark;
/**
* To represent a gate mark which contains two single marks.
* Created by ptg19 on 16/03/17.
* Modified by Haoming Yin (hyi25) on 17/3/2017.
*/
public class GateMark extends Mark {
private SingleMark singleMark1;
private SingleMark singleMark2;
/**
* Create an instance of Gate Mark which contains two single mark
* @param name the name of the gate mark
* @param singleMark1 one single mark inside of the gate mark
* @param singleMark2 the second mark inside of the gate mark
*/
public GateMark(String name, MarkType type, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude) {
super(name, type, latitude, longitude);
this.singleMark1 = singleMark1;
this.singleMark2 = singleMark2;
}
public SingleMark getSingleMark1() {
return singleMark1;
}
public void setSingleMark1(SingleMark singleMark1) {
this.singleMark1 = singleMark1;
}
public SingleMark getSingleMark2() {
return singleMark2;
}
public void setSingleMark2(SingleMark singleMark2) {
this.singleMark2 = singleMark2;
}
public double getLatitude(){
//return (this.getSingleMark1().getLatitude() + this.getSingleMark2().getLatitude()) / 2;
return (this.getSingleMark1().getLatitude());
}
public double getLongitude(){
//return (this.getSingleMark1().getLongitude() + this.getSingleMark2().getLongitude()) / 2;
return (this.getSingleMark1().getLongitude());
}
}
+136
View File
@@ -0,0 +1,136 @@
package seng302.models.mark;
/**
* An abstract class to represent general marks
* Created by Haoming Yin (hyi25) on 17/3/17.
*/
public abstract class Mark {
private String name;
private MarkType markType;
private double latitude;
private double longitude;
private int id;
/**
* Create a mark instance by passing its name and type
* @param name the name of the mark
* @param markType the type of mark. either GATE_MARK or SINGLE_MARK.
*/
public Mark (String name, MarkType markType, int id) {
this.name = name;
this.markType = markType;
this.id = id;
}
public Mark(String name, MarkType markType, double latitude, double longitude) {
this.name = name;
this.markType = markType;
this.latitude = latitude;
this.longitude = longitude;
id = 0;
}
/**
* Calculated the heading in radians from first Mark to the second Mark.
*
* @param pointOne First Mark
* @param pointTwo Second Mark
* @return Heading in radians
*/
public static Double calculateHeadingRad(Mark pointOne, Mark pointTwo) {
Double longitude1 = pointOne.getLongitude();
Double longitude2 = pointTwo.getLongitude();
Double latitude1 = pointOne.getLatitude();
Double latitude2 = pointTwo.getLatitude();
return calculateHeadingRad(latitude1, longitude1, latitude2, longitude2);
}
/**
* Calculate the heading in radians from geographical location with latitude1, longitude 1 to geographical
* latitude2, longitude 2
* @param longitude1 Longitude of first point in degrees
* @param longitude2 Longitude of second point in degrees
* @param latitude1 Latitude of first point in degrees
* @param latitude2 Latitude of first point in degrees
* @return Heading in radians
*/
public static double calculateHeadingRad (Double latitude1, Double longitude1, Double latitude2, Double longitude2) {
latitude1 = Math.toRadians(latitude1);
latitude2 = Math.toRadians(latitude2);
Double longDiff= Math.toRadians(longitude2-longitude1);
Double y = Math.sin(longDiff)*Math.cos(latitude2);
Double x = Math.cos(latitude1)*Math.sin(latitude2)-Math.sin(latitude1)*Math.cos(latitude2)*Math.cos(longDiff);
return Math.atan2(y, x);
}
/**
* Calculates the distance in meters from the first Mark to a second Mark
*
* @param pointOne First Mark
* @param pointTwo Second Mark
* @return Distance in meters
*/
public static Double calculateDistance(Mark pointOne, Mark pointTwo) {
Double longitude1 = pointOne.getLongitude();
Double longitude2 = pointTwo.getLongitude();
Double latitude1 = pointOne.getLatitude();
Double latitude2 = pointTwo.getLatitude();
return calculateDistance(latitude1, longitude1, latitude2, longitude2);
}
/**
* Calculate the distance in meters from geographical location with latitude1, longitude 1 to geographical
* latitude2, longitude 2
*
* @param longitude1 Longitude of first point in degrees
* @param longitude2 Longitude of second point in degrees
* @param latitude1 Latitude of first point in degrees
* @param latitude2 Latitude of first point in degrees
* @return Distance in meters
*/
public static Double calculateDistance (Double latitude1, Double longitude1, Double latitude2, Double longitude2) {
Double theta = longitude1 - longitude2;
Double dist = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) +
Math.cos(Math.toRadians(latitude1)) * Math.cos(Math.toRadians(latitude2)) *
Math.cos(Math.toRadians(theta));
dist = Math.acos(dist);
dist = Math.toDegrees(dist);
dist = dist * 60 * 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute)
dist = dist * 1609.344; //ratio of miles to metres
return dist;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public MarkType getMarkType() {
return markType;
}
public void setMarkType(MarkType markType) {
this.markType = markType;
}
public double getLatitude() {
return latitude;
}
public double getLongitude() {
return longitude;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
@@ -0,0 +1,231 @@
package seng302.models.mark;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.transform.Rotate;
import seng302.models.RaceObject;
import java.util.ArrayList;
import java.util.List;
/**
* Created by CJIRWIN on 26/04/2017.
*/
public class MarkGroup extends RaceObject {
private static int MARK_RADIUS = 5;
private static int LINE_THICKNESS = 2;
private static double DASHED_GAP_LEN = 2d;
private static double DASHED_LINE_LEN = 5d;
private List<Mark> marks = new ArrayList<>();
private Mark mainMark;
private double[] nodePixelVelocitiesX;
private double[] nodePixelVelocitiesY;
private Point2D[] nodeDestinations;
public MarkGroup (Mark mark, Point2D... points) {
nodePixelVelocitiesX = new double[points.length];
nodePixelVelocitiesY = new double[points.length];
nodeDestinations = new Point2D[points.length];
marks.add(mark);
mainMark = mark;
Color color = Color.BLACK;
if (mark.getName().equals("Start")){
color = Color.GREEN;
} else if (mark.getName().equals("Finish")){
color = Color.RED;
}
Circle markCircle;
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
markCircle = new Circle(
points[0].getX(),
points[0].getY(),
MARK_RADIUS,
color
);
nodeDestinations = new Point2D[]{
new Point2D(markCircle.getCenterX(), markCircle.getCenterY()
)
};
super.getChildren().add(markCircle);
} else {
marks.add(((GateMark) mark).getSingleMark1());
marks.add(((GateMark) mark).getSingleMark2());
nodePixelVelocitiesX = new double[]{0d,0d};
nodePixelVelocitiesY = new double[]{0d,0d};
nodeDestinations = new Point2D[2];
markCircle = new Circle(
points[0].getX(),
points[0].getY(),
MARK_RADIUS,
color
);
nodeDestinations[0] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY());
super.getChildren().add(markCircle);
markCircle = new Circle(
points[1].getX(),
points[1].getY(),
MARK_RADIUS,
color
);
nodeDestinations[1] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY());
super.getChildren().add(markCircle);
Line line = new Line(
points[0].getX(),
points[0].getY(),
points[1].getX(),
points[1].getY()
);
line.setStrokeWidth(LINE_THICKNESS);
line.setStroke(color);
if (mark.getMarkType() == MarkType.OPEN_GATE) {
line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN);
}
super.getChildren().add(line);
}
}
public void setDestination (double x, double y, double rotation, double groundSpeed, int... raceIds) {
setDestination(x, y, 0, raceIds);
this.rotationalGoal = rotation;
calculateRotationalVelocity();
}
public void setDestination (double x, double y, double groundSpeed, int... raceIds) {
for (int i = 0; i < marks.size(); i++)
for (int id : raceIds)
if (id == marks.get(i).getId())
setDestinationChild(x, y, 0, Math.max(0, i-1));
}
private void setDestinationChild (double x, double y, double speed, int childIndex) {
//double relativeX = x - super.getLayoutX();
//double relativeY = y - super.getLayoutY();
Circle markCircle = (Circle) super.getChildren().get(childIndex);
this.nodeDestinations[childIndex] = new Point2D(x, y);
//if (Math.abs(relativeX - markCircle.getCenterX()) > 30 && Math.abs(relativeY - markCircle.getCenterY()) > 30) {
this.nodePixelVelocitiesX[childIndex] = (x - markCircle.getCenterX()) / expectedUpdateInterval;
this.nodePixelVelocitiesY[childIndex] = (y - markCircle.getCenterY()) / expectedUpdateInterval;
//}
}
public void rotateTo (double rotation) {
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
Line line = (Line) super.getChildren().get(2);
double xCenter = Math.abs(line.getEndX() - line.getStartX());
double yCenter = Math.abs(line.getEndY() - line.getStartY());
super.getTransforms().setAll(new Rotate(rotation, xCenter, yCenter));
}
}
public void updatePosition (long timeInterval) {
Circle markCircle = (Circle) super.getChildren().get(0);
if (nodePixelVelocitiesX[0] > 0 && markCircle.getCenterX() > nodeDestinations[0].getX() ||
nodePixelVelocitiesX[0] < 0 && markCircle.getCenterX() < nodeDestinations[0].getY())
nodePixelVelocitiesX[0] = 0;
else if (nodePixelVelocitiesX[0] != 0)
markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[0] * timeInterval);
if (nodePixelVelocitiesY[0] > 0 && markCircle.getCenterY() > nodeDestinations[0].getY() ||
nodePixelVelocitiesY[0] < 0 && markCircle.getCenterY() < nodeDestinations[0].getY())
nodePixelVelocitiesY[0] = 0;
else if (nodePixelVelocitiesY[0] != 0)
markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[0] * timeInterval);
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
Line line = (Line) super.getChildren().get(2);
line.setStartX(markCircle.getCenterX());
line.setStartY(markCircle.getCenterY());
markCircle = (Circle) super.getChildren().get(1);
if (nodePixelVelocitiesX[1] > 0 && markCircle.getCenterX() >= nodeDestinations[1].getX() ||
nodePixelVelocitiesX[1] < 0 && markCircle.getCenterX() <= nodeDestinations[1].getX())
nodePixelVelocitiesX[1] = 0;
else if (nodePixelVelocitiesX[1] != 0)
markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[1] * timeInterval);
if (nodePixelVelocitiesY[1] > 0 && markCircle.getCenterY() > nodeDestinations[1].getY() ||
nodePixelVelocitiesY[1] < 0 && markCircle.getCenterY() < nodeDestinations[1].getY())
nodePixelVelocitiesY[1] = 0;
else if (nodePixelVelocitiesY[1] != 0)
markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[1] * timeInterval);
line.setEndX(markCircle.getCenterX());
line.setEndY(markCircle.getCenterY());
}
}
public void moveGroupBy (double x, double y, double rotation) {
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
Line line = (Line) super.getChildren().get(2);
for (int childIndex = 0; childIndex < 2; childIndex++){
Circle mark = (Circle) super.getChildren().get(childIndex);
mark.setCenterY(mark.getCenterY() + y);
mark.setCenterX(mark.getCenterX() + x);
}
line.setStartX(line.getStartX() + x);
line.setStartY(line.getStartY() + y);
line.setEndX(line.getEndX() + x);
line.setEndY(line.getEndY() + y);
} else {
Circle mark = (Circle) super.getChildren().get(0);
mark.setCenterY(mark.getCenterY() + y);
mark.setCenterX(mark.getCenterX() + x);
}
rotateTo(currentRotation + rotation);
}
public void moveTo (double x, double y, double rotation) {
moveTo(x, y);
rotateTo(rotation);
}
public void moveTo (double x, double y) {
Circle markCircle = (Circle) super.getChildren().get(0);
markCircle.setCenterX(x);
markCircle.setCenterY(y);
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
markCircle = (Circle) super.getChildren().get(1);
markCircle.setCenterX(x);
markCircle.setCenterY(y);
Line line = (Line) super.getChildren().get(2);
line.setStartX(x);
line.setStartY(y);
line.setEndX(x);
line.setEndY(y);
}
}
public boolean hasRaceId (int... raceIds) {
for (int id : raceIds)
for (Mark mark : marks)
if (id == mark.getId())
return true;
return false;
}
public static int getMarkRadius() {
return MARK_RADIUS;
}
public static void setMarkRadius(int markRadius) {
MARK_RADIUS = markRadius;
}
public int[] getRaceIds () {
int[] idArray = new int[marks.size()];
int i = 0;
for (Mark mark : marks)
idArray[i++] = mark.getId();
return idArray;
}
}
@@ -0,0 +1,9 @@
package seng302.models.mark;
/**
* To represent two types of mark
* Created by Haoming Yin (hyi25) on 17/3/17.
*/
public enum MarkType {
SINGLE_MARK, OPEN_GATE, CLOSED_GATE
}
@@ -0,0 +1,48 @@
package seng302.models.mark;
/**
* Represents the marker as a single mark
*
* Created by Haoming Yin (hyi25) on 17/3/2017
*/
public class SingleMark extends Mark {
private double lat;
private double lon;
private String name;
private int id;
/**
* Represents a marker
*
* @param name, the name of the marker*
* @param lat, the latitude of the marker
* @param lon, the longitude of the marker
*/
public SingleMark(String name, double lat, double lon, int id) {
super(name, MarkType.SINGLE_MARK, id);
this.lat = lat;
this.lon = lon;
this.id = id;
}
/**
* Represents the marker at the beginning of a leg
*
* @param name, the name of the marker
*/
public SingleMark(String name) {
super(name, MarkType.SINGLE_MARK, 0);
this.lat = 0;
this.lon = 0;
this.id = 0;
}
public double getLatitude() {
return this.lat;
}
public double getLongitude() {
return this.lon;
}
}
@@ -0,0 +1,78 @@
package seng302.models.parsers;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.DoubleSummaryStatistics;
public class ConfigParser extends FileParser {
private Document doc;
public ConfigParser(String path) {
super(path);
this.doc = this.parseFile();
}
/**
* Gets wind direction from config file.
*
* @return a double type degree, or 0 if no value or invalid value is found
*/
public double getWindDirection() {
return getDoubleByTagName("wind-direction", 0.0);
}
/**
* Gets a non negative time scale for the race
*
* @return a double type scale, or 0 if no scale or invalid scale is found
*/
public double getTimeScale() {
return getDoubleByTagName("time-scale", 1.0);
}
/**
* Gets a double type number by given tag name found in xml file
*
* @param tagName a string of tag name
* @param defaultVal value returned if no value or invalid value is found
* @return value found
*/
public double getDoubleByTagName(String tagName, double defaultVal) {
double val = defaultVal;
try {
Node node = this.doc.getElementsByTagName(tagName).item(0);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
val = Double.valueOf(element.getTextContent());
}
} catch (Exception e) {
} finally {
return val;
}
}
/**
* Gets a string by given tag name found in xml file
*
* @param tagName a string of tag name
* @param defaultVal a string returned if no value or invalid value is found
* @return string found
*/
public String getStringByTagName(String tagName, String defaultVal) {
String string = defaultVal;
try {
Node node = this.doc.getElementsByTagName(tagName).item(0);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
string = element.getTextContent();
}
} catch (Exception e) {
} finally {
return string;
}
}
}
@@ -0,0 +1,145 @@
package seng302.models.parsers;
import org.w3c.dom.*;
import seng302.models.mark.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.NoSuchElementException;
/**
* parse a course xml file
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public class CourseParser extends FileParser {
private Document doc;
private HashMap<String, Mark> marks = new HashMap<>();
public CourseParser(String path) {
super(path);
this.doc = this.parseFile();
}
/**
* create a mark by given node
*
* @param node
* @return a mark, or null if fails to create a mark
*/
private SingleMark generateSingleMark(Node node) {
try {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getElementsByTagName("name").item(0).getTextContent();
double lat = Double.valueOf(element.getElementsByTagName("latitude").item(0).getTextContent());
double lon = Double.valueOf(element.getElementsByTagName("longitude").item(0).getTextContent());
int id = Integer.valueOf(element.getElementsByTagName("id").item(0).getTextContent());
SingleMark singleMark = new SingleMark(name, lat, lon, id);
return singleMark;
} else {
throw new NoSuchElementException("Cannot generate a mark by given node.");
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* generate an arrayList of gates
*
* @return an arrayList of gates, or null if no gate has been found.
*/
private void generateGateMarks() {
ArrayList<GateMark> gateMarks = new ArrayList<>();
try {
NodeList nodes = doc.getElementsByTagName("gate");
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getElementsByTagName("name").item(0).getTextContent();
SingleMark mark1 = generateSingleMark(element.getElementsByTagName("mark").item(0));
SingleMark mark2 = generateSingleMark(element.getElementsByTagName("mark").item(1));
GateMark gateMark;
if (name.equals("Start") || name.equals("Finish"))
gateMark = new GateMark(name, MarkType.CLOSED_GATE, mark1, mark2, mark1.getLatitude(), mark1.getLongitude());
else
gateMark = new GateMark(name, MarkType.OPEN_GATE, mark1, mark2, mark1.getLatitude(), mark1.getLongitude());
marks.put(name, gateMark);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* generate an arrayList of marks
*
* @return an arrayList of marks, or null if no gate has been found.
*/
private void generateSingleMarks() {
ArrayList<SingleMark> singleMarks = new ArrayList<>();
try {
// find the "marks" tag
Node node = doc.getElementsByTagName("marks").item(0);
// iterate all "marks"'s children
for (Node n = node.getFirstChild(); n != null; n = n.getNextSibling()) {
// if node's tag name is "mark"
if (n.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) n;
if (element.getNodeName() == "mark") {
Mark mark = generateSingleMark(n);
marks.put(mark.getName(), mark);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* return the order of all the marks along a course
*
* @return an arrayList of the names of ordered course marks
*/
private ArrayList<String> getOrder() {
ArrayList<String> markOrder = new ArrayList<>();
try {
Node orderNode = doc.getElementsByTagName("order").item(0);
for (Node node = orderNode.getFirstChild(); node != null; node = node.getNextSibling()) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getTextContent();
markOrder.add(name);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return markOrder;
}
public ArrayList<Mark> getCourse() {
generateSingleMarks();
generateGateMarks();
ArrayList<Mark> course = new ArrayList<>();
try {
for (String mark : getOrder()) {
course.add(marks.get(mark));
}
} catch (Exception e) {
e.printStackTrace();
}
return course;
}
}
@@ -0,0 +1,54 @@
package seng302.models.parsers;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
/**
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public abstract class FileParser {
private String filePath;
public FileParser() {}
public FileParser(String path) {
this.filePath = path;
}
protected Document parseFile() {
try {
InputStream is = getClass().getResourceAsStream(this.filePath);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(is);
// optional, in order to recover info from broken line.
doc.getDocumentElement().normalize();
return doc;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
protected Document parseFile(String xmlString) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xmlString)));
// optional, in order to recover info from broken line.
doc.getDocumentElement().normalize();
return doc;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
@@ -0,0 +1,53 @@
package seng302.models.parsers;
/**
* Created by Kusal on 4/24/2017.
*/
public enum PacketType {
HEARTBEAT,
RACE_STATUS,
DISPLAY_TEXT_MESSAGE,
XML_MESSAGE,
RACE_START_STATUS,
YACHT_EVENT_CODE,
YACHT_ACTION_CODE,
CHATTER_TEXT,
BOAT_LOCATION,
MARK_ROUNDING,
COURSE_WIND,
AVG_WIND,
OTHER;
static PacketType assignPacketType(int packetType){
switch(packetType){
case 1:
return HEARTBEAT;
case 12:
return RACE_STATUS;
case 20:
return DISPLAY_TEXT_MESSAGE;
case 26:
return XML_MESSAGE;
case 27:
return RACE_START_STATUS;
case 29:
return YACHT_EVENT_CODE;
case 31:
return YACHT_ACTION_CODE;
case 36:
return CHATTER_TEXT;
case 37:
return BOAT_LOCATION;
case 38:
return MARK_ROUNDING;
case 44:
return COURSE_WIND;
case 47:
return AVG_WIND;
default:
}
return OTHER;
}
}
@@ -0,0 +1,44 @@
package seng302.models.parsers;
/**
* Created by kre39 on 23/04/17.
*/
public class StreamPacket {
//Change int to an ENUM for the type
private PacketType type;
private long messageLength;
private long timeStamp;
private byte[] payload;
StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
this.type = PacketType.assignPacketType(type);
this.messageLength = messageLength;
this.timeStamp = timeStamp;
this.payload = payload;
// System.out.println("type = " + this.type.toString());
//switch the packet type to deal with what ever specific packet you want to deal with
// if (this.type == PacketType.XML_MESSAGE){
// //System.out.println("--------");
// System.out.println(new String(payload));
// //StreamParser.parsePacket(this);
// }
}
PacketType getType() {
return type;
}
public long getMessageLength() {
return messageLength;
}
byte[] getPayload() {
return payload;
}
long getTimeStamp() {
return timeStamp;
}
}
@@ -0,0 +1,583 @@
package seng302.models.parsers;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.models.Yacht;
import seng302.models.parsers.packets.BoatPositionPacket;
import seng302.models.parsers.packets.StreamPacket;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.PriorityBlockingQueue;
/**
* 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 extends Thread{
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatPositions = new ConcurrentHashMap<>();
private String threadName;
private Thread t;
private static boolean raceStarted = false;
private static XMLParser xmlObject;
private static boolean raceFinished = false;
private static boolean streamStatus = false;
private static long timeSinceStart = -1;
private static Map<Integer, Yacht> boats = new HashMap<>();
private static Map<Long, Yacht> boatsPos = new TreeMap<>();
private static double windDirection = 0;
private static String currentTimeString;
private static boolean appRunning;
/**
* Used to initialise the thread name and stream parser object so a thread can be executed
*
* @param threadName name of the thread
*/
public StreamParser(String threadName){
this.threadName = threadName;
}
/**
* Used to within threading so when the stream parser thread runs, it will keep looking for a packet to
* process until it is unable to find anymore packets
*
*/
public void run(){
appRunning = true;
try {
System.out.println("[CLIENT] Start of stream");
streamStatus = true;
xmlObject = new XMLParser();
while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) {
Thread.sleep(1);
}
while (appRunning){
StreamPacket packet = StreamReceiver.packetBuffer.peek();
//this code adds a delay to reading from the packetBuffer so
//out of order packets have time to order themselves in the queue
int delayTime = 1000;
int loopTime = delayTime * 10;
long transitTime = (System.currentTimeMillis()%loopTime - packet.getTimeStamp()%loopTime);
if (transitTime < 0){
transitTime = loopTime + transitTime;
}
if (transitTime < delayTime) {
long sleepTime = delayTime - (transitTime);
Thread.sleep(sleepTime);
}
packet = StreamReceiver.packetBuffer.take();
parsePacket(packet);
Thread.sleep(1);
while (StreamReceiver.packetBuffer.peek() == null) {
Thread.sleep(1);
}
}
} catch (Exception e){
e.printStackTrace();
}
}
/**
* Used to start the stream parser thread when multithreading
*
*/
public void start () {
System.out.println("[CLIENT] Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
/**
* 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
*/
private 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:
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;
default:
break;
//System.out.println(packet.getType().toString());
}
}
catch (NullPointerException e){
System.out.println("Error parsing packet");
}
}
/**
* 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());
}
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];
// System.out.println("raceStatus = " + raceStatus);
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18));
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;
//System.out.println("Time till start: " + timeTillStart + " Seconds");
} else {
if (raceStatus == 4 || raceStatus == 8){
raceFinished = true;
raceStarted = false;
System.out.println("[CLIENT] Race has finished");
} else if (!raceStarted){
raceStarted = true;
raceFinished = false;
System.out.println("[CLIENT] Race has started");
}
//System.out.println("Time since start: " + -1 * timeTillStart + " Seconds");
timeSinceStart = timeTillStart;
}
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc...
windDirection = windDir / windDirFactor;
long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
int noBoats = payload[22];
int raceType = payload[23];
// ArrayList<String> boatStatuses = new ArrayList<>();
boatsPos = new TreeMap<>();
for (int i = 0; i < noBoats; i++){
Long boatStatusSourceID = bytesToLong(Arrays.copyOfRange(payload,24 + (i * 20),28+ (i * 20)));
Yacht boat = boats.get((int)(long) boatStatusSourceID);
boat.setBoatStatus((int)payload[28 + (i * 20)]);
boat.setLegNumber((int)payload[29 + (i * 20)]);
boat.setPenaltiesAwarded((int)payload[29 + (i * 20)]);
boat.setPenaltiesServed((int)payload[30 + (i * 20)]);
Long estTimeAtNextMark = bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20)));
boat.setEstimateTimeAtNextMark(estTimeAtNextMark);
Long estTimeAtFinish = bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (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("-");
}
}
}
/**
* 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();
//System.out.println("xmlMessage2 = " + xmlMessage);
//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();
}
}
/**
* 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];
// System.out.println("eventId = " + eventId);
}
/**
* 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));
}
/**
* 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 || deviceType == 3){
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed);
//add a new priority que to the boatPositions HashMap
if (!boatPositions.containsKey(boatId)){
boatPositions.put(boatId, new PriorityBlockingQueue<BoatPositionPacket>(256, new Comparator<BoatPositionPacket>() {
@Override
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
return (int) (p1.getTimeValid() - p2.getTimeValid());
}
}));
}
//Adding the boatPacket to the priority que
boatPositions.get(boatId).put(boatPacket);
}
}
/**
* 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];
}
/**
* 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];
ArrayList<String> windInfo = new ArrayList<>();
for (int i = 0; i < loopCount; i++){
String wind = "WindId: " + payload[3 + (20 * i)];
wind += "\nTime: " + bytesToLong(Arrays.copyOfRange(payload,4 + (20 * i),10 + (20 * i)));
wind += "\nRaceId: " + bytesToLong(Arrays.copyOfRange(payload,10 + (20 * i),14 + (20 * i)));
wind += "\nWindDirection: " + bytesToLong(Arrays.copyOfRange(payload,14 + (20 * i),16 + (20 * i)));
wind += "\nWindSpeed: " + bytesToLong(Arrays.copyOfRange(payload,16 + (20 * i),18 + (20 * i)));
wind += "\nBestUpWindAngle: " + bytesToLong(Arrays.copyOfRange(payload,18 + (20 * i),20 + (20 * i)));
wind += "\nBestDownWindAngle: " + bytesToLong(Arrays.copyOfRange(payload,20 + (20 * i),22 + (20 * i)));
wind += "\nFlags: " + String.format("%8s", Integer.toBinaryString(payload[22 + (20 * i)] & 0xFF)).replace(' ', '0');
windInfo.add(wind);
}
}
/**
* This packet conatins the average wind to ground speed
*
* @param packet The packet containing the paylaod
*/
private static void extractAvgWind(StreamPacket packet){
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload,1,7));
long rawPeriod = bytesToLong(Arrays.copyOfRange(payload,7,9));
long rawSamplePeriod = bytesToLong(Arrays.copyOfRange(payload,9,11));
long period2 = bytesToLong(Arrays.copyOfRange(payload,11,13));
long speed2 = bytesToLong(Arrays.copyOfRange(payload,13,15));
long period3 = bytesToLong(Arrays.copyOfRange(payload,15,17));
long speed3 = bytesToLong(Arrays.copyOfRange(payload,17,19));
long period4 = bytesToLong(Arrays.copyOfRange(payload,19,21));
long speed4 = bytesToLong(Arrays.copyOfRange(payload,21,23));
}
/**
* takes an array of up to 7 bytes and returns a positive
* long constructed from the input bytes
*
* @return a positive long if there is less than 7 bytes -1 otherwise
*/
private static long bytesToLong(byte[] bytes){
long partialLong = 0;
int index = 0;
for (byte b: bytes){
if (index > 6){
return -1;
}
partialLong = partialLong | (b & 0xFFL) << (index * 8);
index++;
}
return partialLong;
}
/**
* returns false if race not started, true otherwise
*
* @return race started status
*/
public static boolean isRaceStarted() {
return raceStarted;
}
/**
* returns false if stream not connected, true otherwise
*
* @return stream started status
*/
public static boolean isStreamStatus() {
return streamStatus;
}
/**
* returns race timer
*
* @return race timer in long
*/
public static long getTimeSinceStart() {
return timeSinceStart;
}
/**
* return false if race not finished, true otherwise
*
* @return race finished status
*/
public static boolean isRaceFinished() {
return raceFinished;
}
/**
* return a map of boats with sourceID and the boat
*
* @return map of boats
*/
public static Map<Integer, Yacht> getBoats() {
return boats;
}
/**
* returns the latest updated object from xml parser
*
* @return the latest xml object
*/
public static XMLParser getXmlObject() {
return xmlObject;
}
/**
* returns the wind direction in degrees
*
* @return a double wind direction value
*/
public static double getWindDirection() {
return windDirection;
}
/**
* returns 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<Long, Yacht> getBoatsPos() {
return boatsPos;
}
public static void appClose(){
appRunning = false;
System.out.println("[CLIENT] Shutting down stream parser");
}
}
@@ -0,0 +1,162 @@
package seng302.models.parsers;
import seng302.models.parsers.packets.StreamPacket;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
public class StreamReceiver extends Thread {
private InputStream stream;
private Socket host;
private ByteArrayOutputStream crcBuffer;
private Thread t;
private String threadName;
public static PriorityBlockingQueue<StreamPacket> packetBuffer;
private static boolean moreBytes;
public StreamReceiver(String hostAddress, int hostPort, String threadName) {
this.threadName = threadName;
this.setDaemon(true);
try {
host = new Socket(hostAddress, hostPort);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void run(){
PriorityBlockingQueue<StreamPacket> pq = new PriorityBlockingQueue<>(256, new Comparator<StreamPacket>() {
@Override
public int compare(StreamPacket s1, StreamPacket s2) {
return (int) (s1.getTimeStamp() - s2.getTimeStamp());
}
});
packetBuffer = pq;
connect();
}
public void start () {
System.out.println("[CLIENT] Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
public StreamReceiver(Socket host, PriorityBlockingQueue packetBuffer){
this.host=host;
this.packetBuffer = packetBuffer;
}
public void connect(){
try {
stream = host.getInputStream();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
int sync1;
int sync2;
moreBytes = true;
while(moreBytes) {
try {
crcBuffer = new ByteArrayOutputStream();
sync1 = readByte();
sync2 = readByte();
//checking if it is the start of the packet
if(sync1 == 0x47 && sync2 == 0x83) {
int type = readByte();
//No. of milliseconds since Jan 1st 1970
long timeStamp = bytesToLong(getBytes(6));
skipBytes(4);
long payloadLength = bytesToLong(getBytes(2));
byte[] payload = getBytes((int) payloadLength);
Checksum checksum = new CRC32();
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
long computedCrc = checksum.getValue();
long packetCrc = bytesToLong(getBytes(4));
if (computedCrc == packetCrc) {
packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload));
} else {
System.err.println("Packet has been dropped");
}
}
} catch (Exception e) {
moreBytes = false;
}
}
}
private int readByte() throws Exception {
int currentByte = -1;
try {
currentByte = stream.read();
crcBuffer.write(currentByte);
} catch (IOException e) {
e.printStackTrace();
}
if (currentByte == -1){
throw new Exception();
}
return currentByte;
}
private byte[] getBytes(int n) throws Exception{
byte[] bytes = new byte[n];
for (int i = 0; i < n; i++){
bytes[i] = (byte) readByte();
}
return bytes;
}
private void skipBytes(long n) throws Exception{
for (int i=0; i < n; i++){
readByte();
}
}
/**
* 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");
//StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread2");
sr.start();
}
public static void noMoreBytes(){
moreBytes = false;
System.out.println("[CLIENT] Shutting down stream receiver");
}
}
@@ -0,0 +1,64 @@
//package seng302.models.parsers;
//
//import org.w3c.dom.*;
//import seng302.models.Yacht;
//
//import java.util.ArrayList;
//import java.util.NoSuchElementException;
//
//public class TeamsParser extends FileParser {
//
// private Document doc;
//
// public TeamsParser(String path) {
// super(path);
// this.doc = this.parseFile();
// }
//
// /**
// * Create a boat instance by a given team node
// * @param node a boat node containing name, alias and velocity
// * @return an instance of Boat
// */
// private Yacht parseBoat(Node node) {
// try {
// if (node.getNodeType() == Node.ELEMENT_NODE) {
// Element element = (Element) node;
// String name = element.getElementsByTagName("name").item(0).getTextContent();
// String alias = element.getElementsByTagName("alias").item(0).getTextContent();
// double velocity = Double.valueOf(element.getElementsByTagName("velocity").item(0).getTextContent());
// int id = Integer.valueOf(element.getElementsByTagName("id").item(0).getTextContent());
// Yacht boat = new Yacht(name, velocity, alias, id);
// return boat;
// } else {
// throw new NoSuchElementException("Cannot generate a boat by given node");
// }
// } catch (Exception e) {
// e.printStackTrace();
// return null;
// }
// }
//
// /**
// * Create an arraylist of boats instance.
// * @return an arraylist of boats in teams file
// */
// public ArrayList<Yacht> getBoats() {
// ArrayList<Yacht> boats = new ArrayList<>();
//
// try {
// NodeList nodes = this.doc.getElementsByTagName("team");
// for (int i = 0; i < nodes.getLength(); i++) {
// Node node = nodes.item(i);
// boats.add(parseBoat(node));
// }
// return boats;
// } catch (Exception e) {
// e.printStackTrace();
// return null;
// }
// }
//
//
//}
//
@@ -0,0 +1,492 @@
package seng302.models.parsers;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import seng302.models.Yacht;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Class to create an XML object from the XML Packet Messages.
*
* Example usage:
*
* Document doc; // some xml document
* Integer xmlMessageType; // an Integer of value 5, 6, 7
*
* xmlP = new XMLParser(doc, xmlMessageType);
* RegattaXMLObject rXmlObj = xmlP.createRegattaXML(); // creates a regattaXML object.
*
*/
public class XMLParser {
private Document xmlDoc;
private RaceXMLObject raceXML;
private RegattaXMLObject regattaXML;
private BoatXMLObject boatXML;
public XMLParser() {}
/**
* Constructor for XMLParser
* @param doc Document to create XML object.
* @param messageType Defines if a message is a RegattaXML(5), RaceXML(6), BoatXML(7).
*/
public void constructXML(Document doc, Integer messageType) {
this.xmlDoc = doc;
switch (messageType) {
case 5:
regattaXML = new RegattaXMLObject(this.xmlDoc);
break;
case 6:
raceXML = new RaceXMLObject(this.xmlDoc);
break;
case 7:
boatXML = new BoatXMLObject(this.xmlDoc);
break;
}
}
public RaceXMLObject getRaceXML() { return raceXML; }
public RegattaXMLObject getRegattaXML() { return regattaXML; }
public BoatXMLObject getBoatXML() { return boatXML; }
/**
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static Integer getElementInt(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Integer.parseInt(tagList.item(0).getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of a given child element tag, assuming it exists, as an String.
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static String getElementString(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return tagList.item(0).getTextContent();
} else {
return null;
}
}
/**
* Returns the text content of a given child element tag, assuming it exists, as a Double.
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static Double getElementDouble(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Double.parseDouble(tagList.item(0).getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as a String.
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The String representation of the text content of an attribute in the given node, else returns null.
*/
private static String getNodeAttributeString(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return attrItem.getTextContent();
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as an Integer.
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The Integer representation of the text content of an attribute in the given node, else returns null.
*/
private static Integer getNodeAttributeInt(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return Integer.parseInt(attrItem.getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as a Double.
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The Double representation of the text content of an attribute in the given node, else returns null.
*/
private static Double getNodeAttributeDouble(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return Double.parseDouble(attrItem.getTextContent());
} else {
return null;
}
}
public class RegattaXMLObject {
//Regatta Info
private Integer regattaID;
private String regattaName;
private String courseName;
private Double centralLat;
private Double centralLng;
private Integer utcOffset;
/**
* Constructor for a RegattaXMLObject.
* Takes the information from a Document object and creates a more usable format.
* @param doc XML Document Object
*/
RegattaXMLObject(Document doc) {
Element docEle = doc.getDocumentElement();
this.regattaID = getElementInt(docEle, "RegattaID");
this.regattaName = getElementString(docEle, "RegattaName");
this.courseName = getElementString(docEle, "CourseName");
this.centralLat = getElementDouble(docEle, "CentralLatitude");
this.centralLng = getElementDouble(docEle, "CentralLongitude");
this.utcOffset = getElementInt(docEle, "UtcOffset");
}
public Integer getRegattaID() { return regattaID; }
public String getRegattaName() { return regattaName; }
public String getCourseName() { return courseName; }
public Double getCentralLat() { return centralLat; }
public Double getCentralLng() { return centralLng; }
public Integer getUtcOffset() { return utcOffset; }
}
public class RaceXMLObject {
// Race Info
private Integer raceID;
private String raceType;
private String creationTimeDate; // XML Creation Time
//Race Start Details
private String raceStartTime;
private Boolean postponeStatus;
//Non atomic race attributes
private ArrayList<Participant> participants;
private ArrayList<CompoundMark> course;
private ArrayList<Corner> compoundMarkSequence;
private ArrayList<Limit> courseLimit;
/**
* 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
course = 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")) {
CompoundMark cMark = new CompoundMark(cMarkNode);
course.add(cMark);
}
}
//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);
}
}
}
public Integer getRaceID() { return raceID; }
public String getRaceType() { return raceType; }
public String getCreationTimeDate() { return creationTimeDate; }
public String getRaceStartTime() { return raceStartTime; }
public Boolean getPostponeStatus() { return postponeStatus; }
public ArrayList<Participant> getParticipants() { return participants; }
public ArrayList<CompoundMark> getCompoundMarks() { return course; }
public ArrayList<Corner> getCompoundMarkSequence() { return compoundMarkSequence; }
public ArrayList<Limit> getCourseLimit() { return courseLimit; }
public class Participant {
Integer sourceID;
String entry;
Participant(Integer sourceID, String entry) {
this.sourceID = sourceID;
this.entry = entry;
}
public Integer getsourceID() { return sourceID; }
public String getEntry() { return entry; }
}
public class CompoundMark {
private Integer markID;
private String cMarkName;
private ArrayList<Mark> marks;
CompoundMark(Node compoundMark) {
marks = new ArrayList<>();
this.markID = getNodeAttributeInt(compoundMark, "CompoundMarkID");
this.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")) {
Mark mark = new Mark(markNode);
marks.add(mark);
}
}
}
public Integer getMarkID() { return markID; }
public String getcMarkName() { return cMarkName; }
public ArrayList<Mark> getMarks() { return marks; }
public class Mark {
private Integer seqID;
private Integer sourceID;
private String markName;
private Double targetLat;
private Double targetLng;
Mark(Node markNode) {
this.seqID = getNodeAttributeInt(markNode, "SeqID");
this.sourceID = getNodeAttributeInt(markNode, "SourceID");
this.markName = getNodeAttributeString(markNode, "Name");
this.targetLat = getNodeAttributeDouble(markNode, "TargetLat");
this.targetLng = getNodeAttributeDouble(markNode, "TargetLng");
}
public Integer getSeqID() { return seqID; }
public Integer getSourceID() { return sourceID; }
public String getMarkName() { return markName; }
public Double getTargetLat() { return targetLat; }
public Double getTargetLng() { return targetLng; }
}
}
public class Corner {
private Integer seqID;
private Integer compoundMarkID;
private String rounding;
private Integer zoneSize;
Corner(Node cornerNode) {
this.seqID = getNodeAttributeInt(cornerNode, "SeqID");
this.compoundMarkID = getNodeAttributeInt(cornerNode, "CompoundMarkID");
this.rounding = getNodeAttributeString(cornerNode, "Rounding");
this.zoneSize = getNodeAttributeInt(cornerNode, "ZoneSize");
}
public Integer getSeqID() { return seqID; }
public Integer getCompoundMarkID() { return compoundMarkID; }
public String getRounding() { return rounding; }
public Integer getZoneSize() { return zoneSize; }
}
public class Limit {
private Integer seqID;
private Double lat;
private Double lng;
Limit(Node limitNode) {
this.seqID = getNodeAttributeInt(limitNode, "SeqID");
this.lat = getNodeAttributeDouble(limitNode, "Lat");
this.lng = getNodeAttributeDouble(limitNode, "Lon");
}
public Integer getSeqID() { return seqID; }
public Double getLat() { return lat; }
public Double getLng() { return lng; }
}
}
public class BoatXMLObject {
private String lastModified;
private Integer version;
//Settings for the boat type in the race. This may end up having to be reworked if multiple boat types compete.
private String boatType;
private Double boatLength;
private Double hullLength;
private Double markZoneSize;
private Double courseZoneSize;
private ArrayList<Double> zoneLimits;// will only contain 5 elements. Limits 1-5
//Boats
ArrayList<Yacht> boats;
//Competing boats
Map<Integer, Yacht> competingBoats = new HashMap<>();
/**
* Constructor for a BoatXMLObject.
* Takes the information from a Document object and creates a more usable format.
* @param doc XML Document Object
*/
BoatXMLObject(Document doc) {
Element docEle = doc.getDocumentElement();
this.lastModified = getElementString(docEle, "Modified");
this.version = getElementInt(docEle, "Version");
NodeList settingsList = docEle.getElementsByTagName("Settings").item(0).getChildNodes();
this.boatType = getNodeAttributeString(settingsList.item(1), "Type");
this.boatLength = getNodeAttributeDouble(settingsList.item(3), "BoatLength");
this.hullLength = getNodeAttributeDouble(settingsList.item(3), "HullLength");
this.markZoneSize = getNodeAttributeDouble(settingsList.item(5), "MarkZoneSize");
this.courseZoneSize = getNodeAttributeDouble(settingsList.item(5), "CourseZoneSize");
Node zoneLimitsList = settingsList.item(7);
this.zoneLimits = new ArrayList<>();
for (int i = 0; i < zoneLimitsList.getAttributes().getLength(); i++) {
String tag = String.format("Limit%d", i+1);
this.zoneLimits.add(getNodeAttributeDouble(zoneLimitsList, tag));
}
this.boats = new ArrayList<>();
NodeList boatsList = docEle.getElementsByTagName("Boats").item(0).getChildNodes();
for (int i = 0; i < boatsList.getLength(); i++) {
Node currentBoat = boatsList.item(i);
if (currentBoat.getNodeName().equals("Boat")) {
// Boat boat = new Boat(currentBoat);
Yacht boat = new Yacht(getNodeAttributeString(currentBoat, "Type"),
getNodeAttributeInt(currentBoat, "SourceID"),
getNodeAttributeString(currentBoat, "HullNum"),
getNodeAttributeString(currentBoat, "ShortName"),
getNodeAttributeString(currentBoat, "BoatName"),
getNodeAttributeString(currentBoat, "Country"));
this.boats.add(boat);
if (boat.getBoatType().equals("Yacht")) {
competingBoats.put(boat.getSourceID(), boat);
}
}
//System.out.println(this.getBoats());
}
}
public String getLastModified() { return lastModified; }
public Integer getVersion() { return version; }
public String getBoatType() { return boatType; }
public Double getBoatLength() { return boatLength; }
public Double getHullLength() { return hullLength; }
public Double getMarkZoneSize() { return markZoneSize; }
public Double getCourseZoneSize() { return courseZoneSize; }
public ArrayList<Double> getZoneLimits() { return zoneLimits; }
public ArrayList<Yacht> getBoats() { return boats; }
public Map<Integer, Yacht> getCompetingBoats() {
return competingBoats;
}
// public class Boat {
//
// private String boatType;
// private Integer sourceID;
// private String hullID; //matches HullNum in the XML spec.
// private String shortName;
// private String boatName;
// private String country;
//
// Boat(Node boatNode) {
// this.boatType = getNodeAttributeString(boatNode, "Type");
// this.sourceID = getNodeAttributeInt(boatNode, "SourceID");
// this.hullID = getNodeAttributeString(boatNode, "HullNum");
// this.shortName = getNodeAttributeString(boatNode, "ShortName");
// this.boatName = getNodeAttributeString(boatNode, "BoatName");
// this.country = getNodeAttributeString(boatNode, "Country");
// }
//
// public String getBoatType() { return boatType; }
// public Integer getSourceID() { return sourceID; }
// public String getHullID() { return hullID; }
// public String getShortName() { return shortName; }
// public String getBoatName() { return boatName; }
// public String getCountry() { return country; }
//
// }
}
}
@@ -0,0 +1,39 @@
package seng302.models.parsers.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;
}
}
@@ -0,0 +1,53 @@
package seng302.models.parsers.packets;
/**
* Created by Kusal on 4/24/2017.
*/
public enum PacketType {
HEARTBEAT,
RACE_STATUS,
DISPLAY_TEXT_MESSAGE,
XML_MESSAGE,
RACE_START_STATUS,
YACHT_EVENT_CODE,
YACHT_ACTION_CODE,
CHATTER_TEXT,
BOAT_LOCATION,
MARK_ROUNDING,
COURSE_WIND,
AVG_WIND,
OTHER;
public static PacketType assignPacketType(int packetType){
switch(packetType){
case 1:
return HEARTBEAT;
case 12:
return RACE_STATUS;
case 20:
return DISPLAY_TEXT_MESSAGE;
case 26:
return XML_MESSAGE;
case 27:
return RACE_START_STATUS;
case 29:
return YACHT_EVENT_CODE;
case 31:
return YACHT_ACTION_CODE;
case 36:
return CHATTER_TEXT;
case 37:
return BOAT_LOCATION;
case 38:
return MARK_ROUNDING;
case 44:
return COURSE_WIND;
case 47:
return AVG_WIND;
default:
}
return OTHER;
}
}
@@ -0,0 +1,37 @@
package seng302.models.parsers.packets;
/**
* Created by kre39 on 23/04/17.
*/
public class StreamPacket {
//Change int to an ENUM for the type
private PacketType type;
private long messageLength;
private long timeStamp;
private byte[] payload;
public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
this.type = PacketType.assignPacketType(type);
this.messageLength = messageLength;
this.timeStamp = timeStamp;
this.payload = payload;
}
public PacketType getType() {
return type;
}
public long getMessageLength() {
return messageLength;
}
public byte[] getPayload() {
return payload;
}
public long getTimeStamp() {
return timeStamp;
}
}
@@ -0,0 +1,323 @@
package seng302.server;
import seng302.server.messages.*;
import seng302.server.simulator.Boat;
import seng302.server.simulator.Simulator;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
public class ServerThread implements Runnable, Observer {
private StreamingServerSocket server;
private long startTime;
private boolean raceStarted = false;
private Map<Integer,Boolean> boatsFinished = new HashMap<>();
private List<Boat> boats;
private Simulator raceSimulator;
private boolean sendingRaceFinishedLocationMessages = true;
private final int HEARTBEAT_PERIOD = 5000;
private final int RACE_STATUS_PERIOD = 1000/2;
private final int RACE_START_STATUS_PERIOD = 1000;
private final int BOAT_LOCATION_PERIOD = 1000/5;
private final int PORT_NUMBER = 4949;
private final int TIME_TILL_RACE_START = 20*1000;
private static final int LOG_LEVEL = 1;
public ServerThread(String threadName){
Thread runner = new Thread(this, threadName);
runner.setDaemon(true);
serverLog("Spawning Server", 0);
raceSimulator = new Simulator(BOAT_LOCATION_PERIOD);
raceSimulator.addObserver(this);
// run race simulator, so it can send boats' static location.
Thread raceSimulatorThread = new Thread(raceSimulator, "Race Simulator");
boats = raceSimulator.getBoats();
for (Boat b : boats){
boatsFinished.put(b.getSourceID(), false);
}
runner.start();
raceSimulatorThread.start();
}
static void serverLog(String message, int logLevel){
if(logLevel <= LOG_LEVEL){
System.out.println("[SERVER] " + message);
}
}
/**
* Creates and returns an XML Message from the file specified
* @param fileName The source XML file
* @param type The XML Message type
* @return The XML Message
*/
private Message getXmlMessage(String fileName, XMLMessageSubType type){
String fileContents = null;
try {
InputStream thisStream = this.getClass().getResourceAsStream(fileName);
fileContents = new String(org.apache.commons.io.IOUtils.toByteArray(thisStream));
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException e){
return null;
}
if (fileContents != null){
return new XMLMessage(fileContents, type, server.getSequenceNumber());
}
return null;
}
/**
* @return Get a race status message for the current race
*/
private Message getRaceStatusMessage(){
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
BoatStatus boatStatus;
RaceStatus raceStatus;
boolean thereAreBoatsNotFinished = false;
for (Boat b : boats){
if (!raceStarted){
boatStatus = BoatStatus.PRESTART;
thereAreBoatsNotFinished = true;
}
else if(boatsFinished.get(b.getSourceID())){
boatStatus = BoatStatus.FINISHED;
}
else{
boatStatus = BoatStatus.PRESTART;
thereAreBoatsNotFinished = true;
}
BoatSubMessage m = new BoatSubMessage(b.getSourceID(), boatStatus, b.getLastPassedCorner().getSeqID(), 0, 0, b.getEstimatedTimeTillFinish(), b.getEstimatedTimeTillFinish());
boatSubMessages.add(m);
}
if (thereAreBoatsNotFinished){
if (raceStarted){
raceStatus = RaceStatus.STARTED;
}
else{
long currentTime = System.currentTimeMillis();
long timeDifference = startTime - currentTime;
if (timeDifference > 60*3){
raceStatus = RaceStatus.PRESTART;
}
else if (timeDifference > 60){
raceStatus = RaceStatus.WARNING;
}
else{
raceStatus = RaceStatus.PREPARATORY;
}
}
}
else{
raceStatus = RaceStatus.TERMINATED;
}
return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.SOUTH,
100, boats.size(), RaceType.MATCH_RACE, 1, boatSubMessages);
}
/**
* Starts an instance of the race simulator
*/
private void startRaceSim(){
serverLog("Starting Running Race Simulator", 0);
// set race started to true, so the simulator will start moving boats
raceSimulator.setRaceStarted(true);
}
/**
* Starts sending heartbeat messages to the client
*/
private void startSendingHeartbeats(){
serverLog("Sending Heartbeats", 0);
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Message heartbeat = new Heartbeat(server.getSequenceNumber());
try {
server.send(heartbeat);
} catch (IOException e) {
System.out.print("");
}
}
}, 0, HEARTBEAT_PERIOD);
}
/**
* Start sending race start status messages until race starts
*/
private void startSendingRaceStartStatusMessages(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Message raceStartStatusMessage = new RaceStartStatusMessage(server.getSequenceNumber(), startTime , 1,
RaceStartNotificationType.SET_RACE_START_TIME);
try {
if (startTime < System.currentTimeMillis() && !raceStarted){
startRaceSim();
raceStarted = true;
serverLog("Race Started", 0);
}
else{
server.send(raceStartStatusMessage);
}
} catch (IOException e) {
System.out.print("");
}
}
}, 0, RACE_START_STATUS_PERIOD);
}
/**
* Start sending race start status messages until race starts
*/
private void startSendingRaceStatusMessages(){
serverLog("Sending race status messages", 0);
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Message raceStatusMessage = getRaceStatusMessage();
try {
server.send(raceStatusMessage);
} catch (IOException e) {
System.out.print("");
}
}
}, 0, RACE_STATUS_PERIOD);
}
/**
* Sends the race, boat, and regatta XML files to the client
*/
private void sendXml(){
try{
Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE);
Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT);
Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA);
if (raceData != null){
server.send(raceData);
serverLog("Sending race data", 0);
}
if (boatData != null){
server.send(boatData);
serverLog("Sending boat data", 0);
}
if (regatta != null){
server.send(regatta);
serverLog("Sending regatta data", 0);
}
} catch (IOException e) {
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
}
}
public void run() {
try{
server = new StreamingServerSocket(PORT_NUMBER);
}
catch (IOException e){
serverLog("Failed to bind socket: " + e.getMessage(), 0);
}
// Wait for client to connect
server.start();
startTime = System.currentTimeMillis() + TIME_TILL_RACE_START;
startSendingHeartbeats();
sendXml();
startSendingRaceStartStatusMessages();
startSendingRaceStatusMessages();
}
/**
* Start sending static boat position updates when race has finished
*/
private void startSendingRaceFinishedBoatPostions(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
try {
for (Boat b : raceSimulator.getBoats()){
Message m = new BoatLocationMessage(b.getSourceID(), server.getSequenceNumber(), b.getLat(),
b.getLng(), b.getLastPassedCorner().getBearingToNextCorner(),
((long) 0));
server.send(m);
}
} catch (IOException e) {
System.out.print("");
}
}
}, 0, BOAT_LOCATION_PERIOD);
}
/**
* Send a boat location message when they are updated by the simulator
* @param o .
* @param arg .
*/
@Override
@SuppressWarnings("unchecked")
public void update(Observable o, Object arg) {
// Only send if server started
// TODO: I don't understand why i need to check server is null or not ... confused - haoming 2/5/17
if(server == null || !server.isStarted()){
return;
}
int numOfBoatsFinished = 0;
for (Boat boat : (List<Boat>) arg){
try {
if (boat.isFinished()) {
numOfBoatsFinished ++;
if (!boatsFinished.get(boat.getSourceID())) {
boatsFinished.put(boat.getSourceID(), true);
serverLog("Boat " + boat.getSourceID() + " finished the race", 1);
}
}
Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(),
boat.getLng(), boat.getLastPassedCorner().getBearingToNextCorner(),
((long) boat.getSpeed()));
server.send(m);
} catch (IOException e) {
serverLog("Couldn't send a boat status message", 3);
return;
}
catch (NullPointerException e){
e.printStackTrace();
}
}
if (numOfBoatsFinished == ((List<Boat>) arg).size()) {
startSendingRaceFinishedBoatPostions();
}
}
}
@@ -0,0 +1,63 @@
package seng302.server;
import seng302.server.messages.Message;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.Channels;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
class StreamingServerSocket {
private ServerSocketChannel socket;
private SocketChannel client;
private short seqNum;
private boolean isServerStarted;
StreamingServerSocket(int port) throws IOException{
socket = ServerSocketChannel.open();
socket.socket().bind(new InetSocketAddress("localhost", port));
//socket.setSoTimeout(10000);
seqNum = 0;
isServerStarted = false;
}
void start(){
ServerThread.serverLog("Listening For Connections",0);
try {
client = socket.accept();
} catch (IOException e) {
e.getMessage();
}
if (client.socket() == null){
start();
}
else{
isServerStarted = true;
ServerThread.serverLog("client connected from " + client.socket().getInetAddress(),0);
}
}
void send(Message message) throws IOException{
if (client == null){
return;
}
message.send(client);
seqNum++;
}
public short getSequenceNumber(){
return seqNum;
}
public boolean isStarted(){
return isServerStarted;
}
}
@@ -0,0 +1,167 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class BoatLocationMessage extends Message {
private final int MESSAGE_SIZE = 56;
private long messageVersionNumber;
private long time;
private long sourceId;
private long sequenceNum;
private DeviceType deviceType;
private double latitude;
private double longitude;
private long altitude;
private Double heading;
private long pitch;
private long roll;
private long boatSpeed;
private long COG;
private long SOG;
private long apparentWindSpeed;
private long apparentWindAngle;
private long trueWindSpeed;
private long trueWindDirection;
private long trueWindAngle;
private long currentDrift;
private long currentSet;
private long rudderAngle;
/**
* Describes the location, altitude and sensor data from the boat.
* @param sourceId ID of the boat
* @param sequenceNum Sequence number of the message
* @param latitude The boats latitude
* @param longitude The boats longitude
* @param heading The boats heading
* @param boatSpeed The boats speed
*/
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
boatSpeed /= 10;
messageVersionNumber = 1;
time = System.currentTimeMillis() / 1000L;
this.sourceId = sourceId;
this.sequenceNum = sequenceNum;
this.deviceType = DeviceType.RACING_YACHT;
this.latitude = latitude;
this.longitude = longitude;
this.altitude = 0;
this.heading = heading;
this.pitch = 0;
this.roll = 0;
this.boatSpeed = boatSpeed;
this.COG = 2;
this.SOG = boatSpeed ;
this.apparentWindSpeed = 0;
this.apparentWindAngle = 0;
this.trueWindSpeed = 0;
this.trueWindDirection = 0;
this.trueWindAngle = 0;
this.currentDrift = 0;
this.currentSet = 0;
this.rudderAngle = 0;
setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize()));
}
/**
* Convert binary latitude or longitude to floating point number
* @param binaryPackedLatLon Binary packed lat OR lon
* @return Floating point lat/lon
*/
public static double binaryPackedToLatLon(long binaryPackedLatLon){
return (double)binaryPackedLatLon * 180.0 / 2147483648.0;
}
/**
* Convert binary packed heading to floating point number
* @param binaryPackedHeading Binary packed heading
* @return heading as a decimal
*/
public static double binaryPackedHeadingToDouble(long binaryPackedHeading){
return (double)binaryPackedHeading * 360.0 / 65536.0;
}
/**
* Convert binary packed wind angle to floating point number
* @param binaryPackedWindAngle Binary packed wind angle
* @return wind angle as a decimal
*/
public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle){
return (double)binaryPackedWindAngle*180.0/32768.0;
}
/**
* Convert a latitude or longitude to a binary packed long
* @param latLon A floating point latitude/longitude
* @return A binary packed lat/lon
*/
public static long latLonToBinaryPackedLong(double latLon){
return (long)((536870912 * latLon) / 45);
}
/**
* Convert a heading to a binary packed long
* @param heading A floating point heading
* @return A binary packed heading
*/
public static long headingToBinaryPackedLong(double heading){
return (long)((8192*heading)/45);
}
/**
* Convert a wind angle to a binary packed long
* @param windAngle Floating point wind angle
* @return A binary packed wind angle
*/
public static long windAngleToBinaryPackedLong(double windAngle){
return (long)((8192*windAngle)/45);
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException{
allocateBuffer();
writeHeaderToBuffer();
long headingToSend = (long)((heading/360.0) * 65535.0);
putByte((byte) messageVersionNumber);
putInt(time, 6);
putInt((int) sourceId, 4);
putUnsignedInt((int) sequenceNum, 4);
putByte((byte) deviceType.getCode());
putInt((int) latLonToBinaryPackedLong(latitude), 4);
putInt((int) latLonToBinaryPackedLong(longitude), 4);
putInt((int) altitude, 4);
putInt(headingToSend, 2);
putInt((int) pitch, 2);
putInt((int) roll, 2);
putInt((int) boatSpeed, 2);
putUnsignedInt((int) COG, 2);
putUnsignedInt((int) SOG, 2);
putUnsignedInt((int) apparentWindSpeed, 2);
putInt((int) apparentWindAngle, 2);
putUnsignedInt((int) trueWindSpeed, 2);
putUnsignedInt((int) trueWindDirection, 2);
putInt((int) trueWindAngle, 2);
putUnsignedInt((int) currentDrift, 2);
putUnsignedInt((int) currentSet, 2);
putInt((int) rudderAngle, 2);
writeCRC();
rewind();
outputStream.write(getBuffer());
}
}
@@ -0,0 +1,25 @@
package seng302.server.messages;
/**
* The current status of a boat
*/
public enum BoatStatus {
UNDEFINED(0),
PRESTART(1),
RACING(2),
FINISHED(3),
DNS(4),
DNF(5),
DSQ(6),
CS(7);
private long code;
BoatStatus(long code) {
this.code = code;
}
public long getCode(){
return code;
}
}
@@ -0,0 +1,85 @@
package seng302.server.messages;
import java.nio.ByteBuffer;
/**
* The status of each boat, sent within a race status message
*/
public class BoatSubMessage{
private final int MESSAGE_SIZE = 20;
private long sourceId;
private BoatStatus boatStatus;
private long legNumber;
private long numberPenaltiesAwarded;
private long numberPenaltiesServed;
private long estimatedTimeAtNextMark;
private long estimatedTimeAtFinish;
private ByteBuffer buff = ByteBuffer.allocate(getSize());
private int buffPos = 0;
/**
* Boat Sub message from section 4.2 of the AC35 streaming data interface spec
* @param sourceId The source ID of the boat
* @param boatStatus The boats status
* @param legNumber The leg the boat is on (0= prestart, 1=start to first mark etc)
* @param numberPenaltiesAwarded The number of penalties awarded to the boat
* @param numberPenaltiesServed The number of penalties served to the boat
* @param estimatedTimeAtFinish The estimated time (UTC) the boat will finish the race
* @param estimatedTimeAtNextMark The estimated time (UTC) the boat will arrive at the next mark
*/
public BoatSubMessage(long sourceId, BoatStatus boatStatus, long legNumber, long numberPenaltiesAwarded, long numberPenaltiesServed,
long estimatedTimeAtFinish, long estimatedTimeAtNextMark){
this.sourceId = sourceId;
this.boatStatus = boatStatus;
this.legNumber = legNumber;
this.numberPenaltiesAwarded = numberPenaltiesAwarded;
this.numberPenaltiesServed = numberPenaltiesServed;
this.estimatedTimeAtFinish = estimatedTimeAtFinish;
this.estimatedTimeAtNextMark = estimatedTimeAtNextMark;
}
/**
* @return The size of this message in bytes
*/
public int getSize(){
return MESSAGE_SIZE;
}
private void putInBuffer(byte[] bytes, long val){
byte[] tmp = bytes.clone();
Message.reverse(tmp);
buff.put(tmp);
buffPos += tmp.length;
buff.position(buffPos);
}
/**
* @return a ByteBuffer containing this boat status message
*/
public ByteBuffer getByteBuffer(){
// Source ID, 4 bytes
putInBuffer(Message.intToByteArray(sourceId, 4), 4);
// Boat Status, 1 byte
putInBuffer(ByteBuffer.allocate(1).put((byte) (boatStatus.getCode() & 0xff)).array(), 1);
// Leg number, 1 byte
putInBuffer(ByteBuffer.allocate(1).put((byte) (legNumber & 0xff)).array(), 1);
// Number of penalties awarded, 1 byte
putInBuffer(ByteBuffer.allocate(1).put((byte) (numberPenaltiesAwarded & 0xff)).array(), 1);
// Number of penalties served, 1 byte
putInBuffer(ByteBuffer.allocate(1).put((byte) (numberPenaltiesServed & 0xff)).array(), 1);
// Estimated time at next mark, 6 bytes
putInBuffer(Message.intToByteArray((int) estimatedTimeAtNextMark, 6),6);
// Estimated time at finish, 6 bytes
putInBuffer(Message.intToByteArray((int) estimatedTimeAtFinish, 6), 6);
return buff;
}
}
@@ -0,0 +1,16 @@
package seng302.server.messages;
public enum DeviceType {
UNKNOWN(0),
RACING_YACHT(1);
private long code;
DeviceType(long code) {
this.code = code;
}
public long getCode(){
return code;
}
}
@@ -0,0 +1,72 @@
package seng302.server.messages;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collections;
public class Header {
// From API spec
private final int syncByte1 = 0x47;
private final int syncByte2 = 0x83;
private MessageType messageType;
private int timeStamp;
private int sourceId;
private short messageLength;
private static final int MESSAGE_LEN = 15;
private ByteBuffer buff;
private int buffPos;
/**
* Message Header from section 3.2 of the AC35 Streaming
* Data spec
* @param messageType The type of the message following this header
* @param sourceId The message source (as defined in the spec)
* @param messageLength The length of the message following this header
*/
public Header(MessageType messageType, int sourceId, Short messageLength){
this.messageType = messageType;
this.sourceId = sourceId;
this.messageLength = messageLength;
timeStamp = (int) (System.currentTimeMillis() / 1000L);
buff = ByteBuffer.allocate(MESSAGE_LEN);
buffPos = 0;
}
private void putInBuffer(byte[] bytes, long val){
byte[] tmp = bytes.clone();
Message.reverse(tmp);
buff.put(tmp);
buffPos += tmp.length;
buff.position(buffPos);
}
/**
* @return a ByteBuffer containing the message header
*/
public ByteBuffer getByteBuffer(){
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
putInBuffer(ByteBuffer.allocate(1).put((byte)messageType.getCode()).array(), messageType.getCode());
putInBuffer(Message.intToByteArray(timeStamp, 6), timeStamp);
putInBuffer(Message.intToByteArray(sourceId, 4), sourceId);
putInBuffer(Message.intToByteArray(messageLength, 2), messageLength);
return buff;
}
/**
* Returns the size of this message
* @return the size of the message
*/
public static Integer getSize(){
return MESSAGE_LEN;
}
}
@@ -0,0 +1,42 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.zip.CRC32;
public class Heartbeat extends Message {
private final int MESSAGE_SIZE = 4;
private int seqNo;
/**
* Heartbeat from the AC35 Streaming data spec
* @param seqNo Increment every time a message is sent
*/
public Heartbeat(int seqNo){
this.seqNo = seqNo;
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException {
setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putUnsignedInt(seqNo, 4);
writeCRC();
rewind();
outputStream.write(getBuffer());
}
}
@@ -0,0 +1,62 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class MarkRoundingMessage extends Message{
private final long MESSAGE_VERSION_NUMBER = 1;
private final int MESSAGE_SIZE = 21;
private long time;
private long ackNumber;
private long raceId;
private long sourceId;
private RoundingBoatStatus boatStatus;
private RoundingSide roundingSide;
private long markId;
/**
* This message is sent when a boat passes a mark, start line, or finish line
* The purpose of this is to record the time when yachts cross marks
*/
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
RoundingSide roundingSide, int markId){
this.time = System.currentTimeMillis() / 1000L;
this.ackNumber = ackNumber;
this.raceId = raceId;
this.sourceId = sourceId;
this.boatStatus = roundingBoatStatus;
this.roundingSide = roundingSide;
this.markId = markId;
setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize()));
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
putByte((byte) MESSAGE_VERSION_NUMBER);
putInt((int) time, 6);
putInt((int) ackNumber, 2);
putInt((int) raceId, 4);
putInt((int) sourceId, 4);
putByte((byte) boatStatus.getCode());
putByte((byte) roundingSide.getCode());
putByte((byte) markId);
writeCRC();
rewind();
outputStream.write(getBuffer());
}
}
@@ -0,0 +1,20 @@
package seng302.server.messages;
/**
* Types of marks boats can round
*/
public enum MarkType {
UNKNOWN(0),
ROUNDING_MARK(1),
GATE(2);
private long code;
MarkType(long code) {
this.code = code;
}
public long getCode(){
return code;
}
}
@@ -0,0 +1,207 @@
package seng302.server.messages;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.zip.CRC32;
public abstract class Message {
private final int CRC_SIZE = 4;
private Header header;
private ByteBuffer buffer;
private int bufferPosition;
private CRC32 crc;
/**
* @param header Set the header for this message
*/
void setHeader(Header header){
this.header = header;
}
/**
* @return the header specified for this message
*/
Header getHeader(){
return header;
}
/**
* @return the size of the message
*/
public abstract int getSize();
/**
* Send the message as through the outputStream
*/
public abstract void send(SocketChannel outputStream) throws IOException;
/**
* Allocate byte buffer to correct size
*/
void allocateBuffer(){
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
buffer.order(ByteOrder.LITTLE_ENDIAN);
bufferPosition = 0;
}
/**
* Write the set header to the byte buffer
*/
void writeHeaderToBuffer(){
buffer.put(getHeader().getByteBuffer().array());
bufferPosition += Header.getSize();
buffer.position(bufferPosition);
}
/**
* Move the buffer position by n bytes
* @param size Number of bytes to move the buffer by
*/
private void moveBufferPositionBy(int size){
bufferPosition += size;
buffer.position(bufferPosition);
}
/**
* Put an unsigned byte in the buffer
*/
void putUnsignedByte(byte b){
buffer.put(ByteBuffer.allocate(1).put((byte) (b & 0xff)).array());
moveBufferPositionBy(1);
}
/**
* Put an signed byte in the buffer
*/
void putByte(byte b){
buffer.put(ByteBuffer.allocate(1).put(b).array());
moveBufferPositionBy(1);
}
/**
* Place an unsigned integer of the specified length in the buffer
* @param val The integer value to add (Note: This must be long due to java not supporting unsigned integers)
* @param size The size of the int to be added to the buffer
*/
void putUnsignedInt(long val, int size){
if (size <= 1){
putUnsignedByte((byte) val);
}
else if (size < 4){
// Use short
byte[] tmp = Message.intToByteArray(val, size); //ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array();
reverse(tmp);
buffer.put(tmp);
moveBufferPositionBy(size);
}
else{
// Use int
byte[] tmp = Message.intToByteArray(val, size);
reverse(tmp);
moveBufferPositionBy(size);
}
}
/**
* Put a signed int of a specified length in the buffer
* @param val The integer value to add
* @param size The size of the integer to be added to the buffer
*/
void putInt(long val, int size){
if (size < 4){
byte[] tmp = Message.intToByteArray(val, size);
reverse(tmp);
buffer.put(tmp);
}
else{
byte[] tmp = Message.intToByteArray(val, size);
reverse(tmp);
buffer.put(tmp);
}
moveBufferPositionBy(size);
}
/**
* Write an array of bytes to the buffer
* @param bytes to write
*/
void putBytes(byte[] bytes){
buffer.put(bytes);
moveBufferPositionBy(bytes.length);
}
/**
* Write a ByteBuffer of bytes to the buffer
* @param bytes to write
* @param size number of bytes
*/
void putBytes(ByteBuffer bytes, int size){
buffer.put(bytes.array());
moveBufferPositionBy(size);
}
/**
* Calculate the CRC of the buffer and append it to the end of the buffer
*/
void writeCRC(){
crc = new CRC32();
buffer.position(0);
byte[] data = Arrays.copyOfRange(buffer.array(), 0, buffer.array().length-CRC_SIZE);
crc.update(data);
buffer.position(bufferPosition);
putInt((int) crc.getValue(), CRC_SIZE);
}
/**
* @return The current buffer
*/
public ByteBuffer getBuffer(){
return buffer;
}
/**
* Rewind the buffer to the beginning
*/
void rewind(){
buffer.flip();
}
/**
* Convert an integer to an array of bytes
* @param val The value to add
* @param len The width of the integer in the buffer
* @return
*/
public static byte[] intToByteArray(long val, int len){
int index = 0;
byte[] data = new byte[len];
for (int i = 0; i < len; i++){
data[len - index - 1] = (byte) (val & 0xFF);
val >>>= 8;
index++;
}
return data;
}
/**
* Reverse an array of bytes
* @param data The byte[] to reverse
*/
public static void reverse(byte[] data) {
for (int left = 0, right = data.length - 1; left < right; left++, right--) {
byte temp = (byte) (data[left] & 0xff);
data[left] = (byte) (data[right] & 0xff);
data[right] = (byte) (temp & 0xff);
}
}
}
@@ -0,0 +1,34 @@
package seng302.server.messages;
/**
* Enum containing the types of messages
* sent by the server
*/
public enum MessageType {
HEARTBEAT(1),
RACE_STATUS(12),
DISPLAY_TEXT_MESSAGE(20),
XML_MESSAGE(26),
RACE_START_STATUS(27),
YACHT_EVENT_CODE(29),
YACHT_ACTION_CODE(31),
CHATTER_TEXT(36),
BOAT_LOCATION(37),
MARK_ROUNDING(38),
COURSE_WIND(44),
AVERAGE_WIND(47);
private int code;
MessageType(int code){
this.code = code;
}
/**
* Get the message code (From the API Spec)
* @return the message code
*/
int getCode(){
return this.code;
}
}
@@ -0,0 +1,21 @@
package seng302.server.messages;
/**
* The types of race start status messages
*/
public enum RaceStartNotificationType {
SET_RACE_START_TIME(1),
RACE_POSTPONED(2),
RACE_ABANDONED(3),
RACE_TERMINATED(4);
private final long type;
RaceStartNotificationType(long type) {
this.type = type;
}
long getType(){
return type;
}
}
@@ -0,0 +1,59 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class RaceStartStatusMessage extends Message {
private final int MESSAGE_SIZE = 20;
private long version;
private long timeStamp;
private long ackNumber;
private long raceStartTime;
private long raceId;
private RaceStartNotificationType notificationType;
/**
* Message sent to clients with the expected start time of the race
* @param ackNumber Sequence number of message.
* @param raceStartTime Expected race start time
* @param raceId Race ID#
* @param notificationType Type of this notification
*/
public RaceStartStatusMessage(long ackNumber, long raceStartTime, long raceId, RaceStartNotificationType notificationType){
this.version = 1;
this.timeStamp = System.currentTimeMillis() / 1000L;
this.ackNumber = ackNumber;
this.raceStartTime = raceStartTime;
this.notificationType = notificationType;
this.raceId = raceId;
setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize()));
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
putUnsignedByte((byte) version);
putInt((int) timeStamp, 6);
putInt((int) ackNumber, 2);
putInt((int) raceStartTime, 6);
putInt((int) raceId, 4);
putUnsignedByte((byte) notificationType.getType());
writeCRC();
rewind();
outputStream.write(getBuffer());
}
}
@@ -0,0 +1,26 @@
package seng302.server.messages;
/**
* The current status of the race
*/
public enum RaceStatus {
NOTACTIVE(0),
WARNING(1), // Between 3:00 and 1:00 before start
PREPARATORY(2), // Less than 1:00 before start
STARTED(3),
ABANDONED(6),
POSTPONED(7),
TERMINATED(8),
RACE_START_TIME_NOT_SET(9),
PRESTART(10); // More than 3:00 before start
private int code;
RaceStatus(int code){
this.code = code;
}
public int getCode(){
return this.code;
}
}
@@ -0,0 +1,88 @@
package seng302.server.messages;
import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.util.List;
import java.util.zip.CRC32;
public class RaceStatusMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS;
private final int MESSAGE_VERSION = 2; //Always set to 1
private final int MESSAGE_BASE_SIZE = 24;
private long currentTime;
private long raceId;
private RaceStatus raceStatus;
private long expectedStartTime;
private WindDirection raceWindDirection;
private long windSpeed;
private long numBoatsInRace;
private RaceType raceType;
private List<BoatSubMessage> boats;
private CRC32 crc;
/**
* A message containing the current status of the race
* @param raceId The ID of the current race
* @param raceStatus The status of the race
* @param expectedStartTime The expected start time
* @param raceWindDirection The wind direction (north, east, south)
* @param windSpeed The wind speed in mm/sec
* @param numBoatsInRace The number of boats in the race
* @param raceType The race type (Match/fleet)
* @param sourceId The source of this message
* @param boats A list of boat status sub messages
*/
public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection,
long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List<BoatSubMessage> boats){
currentTime = System.currentTimeMillis();
this.raceId = raceId;
this.raceStatus = raceStatus;
this.expectedStartTime = expectedStartTime;
this.raceWindDirection = raceWindDirection;
this.windSpeed = windSpeed;
this.numBoatsInRace = numBoatsInRace;
this.raceType = raceType;
this.boats = boats;
crc = new CRC32();
setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize()));
}
/**
* @return the size of this message in bytes
*/
@Override
public int getSize() {
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
}
/**
* Send this message as a stream of bytes
* @param outputStream The output stream to send the message
*/
@Override
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
putByte((byte) MESSAGE_VERSION);
putInt(currentTime, 6);
putInt((int) raceId, 4);
putByte((byte) raceStatus.getCode());
putInt(expectedStartTime, 6);
putInt((int) raceWindDirection.getCode(), 2);
putInt((int) windSpeed, 2);
putByte((byte) numBoatsInRace);
putByte((byte) raceType.getCode());
for (BoatSubMessage boatSubMessage : boats){
putBytes(boatSubMessage.getByteBuffer(), boatSubMessage.getSize());
}
writeCRC();
rewind();
outputStream.write(getBuffer());
}
}
@@ -0,0 +1,20 @@
package seng302.server.messages;
/**
* Enum containing the types of races
* sent by the server
*/
public enum RaceType {
MATCH_RACE(1),
FLEET_RACE(2);
private long code;
RaceType(long code){
this.code = code;
}
public long getCode(){
return code;
}
}
@@ -0,0 +1,21 @@
package seng302.server.messages;
/**
* The status of a boat rounding a mark
*/
public enum RoundingBoatStatus {
UNKNOWN(0),
RACING(1),
DSQ(2),
WITHDRAWN(3);
private long code;
RoundingBoatStatus(long code) {
this.code = code;
}
public long getCode(){
return code;
}
}
@@ -0,0 +1,20 @@
package seng302.server.messages;
/**
* The side the boat rounded the mark
*/
public enum RoundingSide {
UNKNOWN(0),
PORT(1),
STARBOARD(2);
private long code;
RoundingSide(long code) {
this.code = code;
}
public long getCode(){
return code;
}
}
@@ -0,0 +1,20 @@
package seng302.server.messages;
/**
* Enum containing the supported wind directions
*/
public enum WindDirection {
NORTH(0x0000L),
EAST(0x4000L),
SOUTH(0x8000L);
private long code;
WindDirection(long code) {
this.code = code;
}
public long getCode() {
return code;
}
}
@@ -0,0 +1,69 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.zip.CRC32;
public class XMLMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
private final int MESSAGE_VERSION = 1; //Always set to 1
private final int MESSAGE_SIZE = 14;
// Message fields
private long timeStamp;
private long ack = 0x00; //Unused
private XMLMessageSubType xmlMessageSubType;
private long length;
private long sequence;
private String content;
/**
* XML Message from the AC35 Streaming data spec
* @param content The XML content
* @param type The XML Message Sub Type
*/
public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){
this.content = content;
this.xmlMessageSubType = type;
timeStamp = System.currentTimeMillis() / 1000L;
ack = 0;
length = this.content.length();
sequence = sequenceNum;
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
}
/**
* @return The length of this message
*/
public int getSize(){
return MESSAGE_SIZE + content.length();
}
/**
* Send this message as a stream of bytes
* @param outputStream The output stream to send the message
*/
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
// Write message fields
putUnsignedByte((byte) MESSAGE_VERSION);
putInt((int) ack, 2);
putInt((int) timeStamp, 6);
putByte((byte)xmlMessageSubType.getType());
putInt((int) sequence, 2);
putInt((int) length, 2);
putBytes(content.getBytes());
writeCRC();
rewind();
outputStream.write(getBuffer());
}
}
@@ -0,0 +1,20 @@
package seng302.server.messages;
/**
* Enum containing the types of XML messages
*/
public enum XMLMessageSubType {
REGATTA(5),
RACE(6),
BOAT(7);
private int type;
XMLMessageSubType(int type){
this.type = type;
}
public int getType(){
return this.type;
}
}
@@ -0,0 +1,125 @@
package seng302.server.simulator;
import seng302.server.simulator.mark.Corner;
import seng302.server.simulator.mark.Position;
public class Boat {
private int sourceID;
private double lat;
private double lng;
private double speed; // in mm/sec
private String boatName, shortName, shorterName;
private boolean isFinished;
private long estimatedTimeTillFinish;
private Corner lastPassedCorner, headingCorner;
public Boat(int sourceID, String boatName) {
this.sourceID = sourceID;
this.boatName = boatName;
this.isFinished = false;
estimatedTimeTillFinish = 0;
}
/**
* Moves boat to the heading direction for a given time duration
* @param heading moving direction in degree.
* @param duration moving duration in millisecond.
*/
public void move(double heading, double duration) {
Double distance = speed * duration / 1000000; // convert mm to meter
Position originPos = new Position(lat, lng);
Position newPos = GeoUtility.getGeoCoordinate(originPos, heading, distance);
this.lat = newPos.getLat();
this.lng = newPos.getLng();
}
public String toString() {
return String.format("Boat (%d): lat: %f, lng: %f", sourceID, lat, lng);
}
public int getSourceID() {
return sourceID;
}
public void setSourceID(int sourceID) {
this.sourceID = sourceID;
}
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public double getLng() {
return lng;
}
public void setLng(double lng) {
this.lng = lng;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
public String getBoatName() {
return boatName;
}
public void setBoatName(String boatName) {
this.boatName = boatName;
}
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
public String getShorterName() {
return shorterName;
}
public void setShorterName(String shorterName) {
this.shorterName = shorterName;
}
public Corner getLastPassedCorner() {
return lastPassedCorner;
}
public void setLastPassedCorner(Corner lastPassedCorner) {
this.lastPassedCorner = lastPassedCorner;
}
public Corner getHeadingCorner() {
return headingCorner;
}
public void setHeadingCorner(Corner headingCorner) {
this.headingCorner = headingCorner;
}
public boolean isFinished() {
return isFinished;
}
public void setFinished(boolean finished) {
isFinished = finished;
}
public long getEstimatedTimeTillFinish(){
return (long) (-getSpeed()) + System.currentTimeMillis();
}
}
@@ -0,0 +1,82 @@
package seng302.server.simulator;
import seng302.server.simulator.mark.Position;
public class GeoUtility {
private static double EARTH_RADIUS = 6378.137;
/**
* Calculates the euclidean distance between two markers on the canvas using xy coordinates
*
* @param p1 first geographical position
* @param p2 second geographical position
* @return the distance in meter between two points in meters
*/
public static Double getDistance(Position p1, Position p2) {
double dLat = Math.toRadians(p2.getLat() - p1.getLat());
double dLon = Math.toRadians(p2.getLng() - p1.getLng());
double a = Math.pow(Math.sin(dLat / 2), 2.0)
+ Math.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat()))
* Math.pow(Math.sin(dLon / 2), 2.0);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double d = EARTH_RADIUS * c;
return d * 1000; // distance from km to meter
}
/**
* Calculates the angle between to angular co-ordinates on a sphere.
*
* @param p1 the first geographical position, start point
* @param p2 the second geographical position, end point
* @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.).
* vertical up is 0 deg. horizontal right is 90 deg.
*
* NOTE:
* The final bearing will differ from the initial bearing by varying degrees
* according to distance and latitude (if you were to go from say 35°N,45°E
* ( Baghdad) to 35°N,135°E ( Osaka), you would start on a heading of 60°
* and end up on a heading of 120°
*/
public static Double getBearing(Position p1, Position p2) {
double dLon = Math.toRadians(p2.getLng() - p1.getLng());
double y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat()));
double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(Math.toRadians(p2.getLat()))
- Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math.cos(dLon);
double bearing = Math.toDegrees(Math.atan2(y, x));
return (bearing + 360.0) % 360.0;
}
/**
* Given an existing point in lat/lng, distance in (in meter) and bearing
* (in degrees), calculates the new lat/lng.
*
* @param origin the original position within lat / lng
* @param bearing the bearing in degree, from original position to the new position
* @param distance the distance in meter, from original position to the new position
* @return the new position
*/
public static Position getGeoCoordinate(Position origin, Double bearing, Double distance) {
double b = Math.toRadians(bearing); // bearing to radians
double d = distance / 1000.0; // distance to km
double originLat = Math.toRadians(origin.getLat());
double originLng = Math.toRadians(origin.getLng());
double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS)
+ Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b));
double endLng = originLng
+ Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat),
Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat));
return new Position(Math.toDegrees(endLat), Math.toDegrees(endLng));
}
}
@@ -0,0 +1,141 @@
package seng302.server.simulator;
import seng302.server.simulator.mark.Corner;
import seng302.server.simulator.mark.Mark;
import seng302.server.simulator.mark.Position;
import seng302.server.simulator.parsers.RaceParser;
import java.util.List;
import java.util.Observable;
import java.util.concurrent.ThreadLocalRandom;
public class Simulator extends Observable implements Runnable {
private List<Corner> course;
private List<Boat> boats;
private long lapse;
private boolean isRaceStarted;
/**
* Creates a simulator instance with given time lapse.
* @param lapse time duration in millisecond.
*/
public Simulator(long lapse) {
RaceParser rp = new RaceParser("/server_config/race.xml");
course = rp.getCourse();
boats = rp.getBoats();
this.lapse = lapse;
isRaceStarted = false;
setLegs();
// set start line's coordinate to boats
Double startLat = course.get(0).getCompoundMark().getMark1().getLat();
Double startLng = course.get(0).getCompoundMark().getMark1().getLng();
for (Boat boat : boats) {
boat.setLat(startLat);
boat.setLng(startLng);
boat.setLastPassedCorner(course.get(0));
boat.setHeadingCorner(course.get(1));
boat.setSpeed(ThreadLocalRandom.current().nextInt(40000, 60000 + 1));
}
}
@Override
public void run() {
int numOfFinishedBoats = 0;
while (numOfFinishedBoats < boats.size()) {
// if race has started, then boat should start to move.
if (isRaceStarted) {
for (Boat boat : boats) {
numOfFinishedBoats += moveBoat(boat, lapse);
}
}
//System.out.println(boats.get(0));
setChanged();
notifyObservers(boats);
try {
Thread.sleep(lapse);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("[SERVER] Race simulator has been terminated");
}
/**
* Moves a boat with given time duration.
* @param boat the boat to be moved
* @param duration the moving duration in milliseconds
* @return 1 if the boat has reached the final line, otherwise return 0
*/
private int moveBoat(Boat boat, double duration) {
if (boat.getHeadingCorner() != null) {
boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration);
Position boatPos = new Position(boat.getLat(), boat.getLng());
Position lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getMark1();
double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos);
// if a boat passes its heading mark
while (distanceFromLastMark >= boat.getLastPassedCorner().getDistanceToNextCorner()) {
double compensateDistance = distanceFromLastMark - boat.getLastPassedCorner().getDistanceToNextCorner();
boat.setLastPassedCorner(boat.getHeadingCorner());
boat.setHeadingCorner(boat.getLastPassedCorner().getNextCorner());
// heading corner == null means boat has reached the final mark
if (boat.getHeadingCorner() == null) {
boat.setFinished(true);
return 1;
}
// move compensate distance for the mark just passed
Position pos = GeoUtility.getGeoCoordinate(
boat.getLastPassedCorner().getCompoundMark().getMark1(),
boat.getLastPassedCorner().getBearingToNextCorner(),
compensateDistance);
boat.setLat(pos.getLat());
boat.setLng(pos.getLng());
distanceFromLastMark = GeoUtility.getDistance(new Position(boat.getLat(), boat.getLng()),
boat.getLastPassedCorner().getCompoundMark().getMark1());
}
}
return 0;
}
/**
* Link all the corners in the course list so that every corner knows its next
* corner, as well as the distance and bearing to its next corner. However,
* the last corner's heading is null, which means it is the final line.
*/
private void setLegs() {
// get the bearing from one mark to the next heading mark
for (int i = 0; i < course.size() - 1; i++) {
Mark mark1 = course.get(i).getCompoundMark().getMark1();
Mark mark2 = course.get(i + 1).getCompoundMark().getMark1();
course.get(i).setDistanceToNextCorner(GeoUtility.getDistance(mark1, mark2));
course.get(i).setNextCorner(course.get(i + 1));
course.get(i).setBearingToNextCorner(
GeoUtility.getBearing(course.get(i).getCompoundMark().getMark1(),
course.get(i + 1).getCompoundMark().getMark1()));
}
}
public List<Boat> getBoats(){
return boats;
}
public void setRaceStarted(boolean raceStarted) {
isRaceStarted = raceStarted;
}
}
@@ -0,0 +1,70 @@
package seng302.server.simulator.mark;
public class CompoundMark {
private int markID;
private String name;
private Mark mark1;
private Mark mark2;
public CompoundMark(int markID, String name) {
this.markID = markID;
this.name = name;
}
public void addMark(int seqId, Mark mark) {
if (seqId == 1) {
setMark1(mark);
} else if (seqId == 2) {
setMark2(mark);
}
}
/**
* Prints out compoundMark's info and its marks, good for testing
* @return a string showing its details
*/
@Override
public String toString(){
if (mark2 == null)
return String.format("CompoundMark: %d (%s), [%s]",
markID, name, mark1.toString());
return String.format("CompoundMark: %d (%s), [%s; %s]",
markID, name, mark1.toString(), mark2.toString());
}
public int getMarkID() {
return markID;
}
public void setMarkID(int markID) {
this.markID = markID;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Mark getMark1() {
return mark1;
}
public void setMark1(Mark mark1) {
this.mark1 = mark1;
mark1.setSeqID(1);
}
public Mark getMark2() {
return mark2;
}
public void setMark2(Mark mark2) {
this.mark2 = mark2;
mark2.setSeqID(2);
}
}
@@ -0,0 +1,89 @@
package seng302.server.simulator.mark;
public class Corner {
private int seqID;
private CompoundMark compoundMark;
//private int CompoundMarkID;
private RoundingType roundingType;
private int zoneSize; // size of the zone around a mark in boat-lengths.
// TODO: this shouldn't be used in the future!!!!
private double bearingToNextCorner, distanceToNextCorner;
private Corner nextCorner;
public Corner(int seqID, CompoundMark compoundMark, RoundingType roundingType, int zoneSize) {
this.seqID = seqID;
this.compoundMark = compoundMark;
this.roundingType = roundingType;
this.zoneSize = zoneSize;
}
/**
* Prints out corner's info and its compound mark, good for testing
* @return a string showing its details
*/
@Override
public String toString() {
return String.format("Corner: %d - %s - %d, %s\n",
seqID, roundingType.getType(), zoneSize, compoundMark.toString());
}
public int getSeqID() {
return seqID;
}
public void setSeqID(int seqID) {
this.seqID = seqID;
}
public CompoundMark getCompoundMark() {
return compoundMark;
}
public void setCompoundMark(CompoundMark compoundMark) {
this.compoundMark = compoundMark;
}
public RoundingType getRoundingType() {
return roundingType;
}
public void setRoundingType(RoundingType roundingType) {
this.roundingType = roundingType;
}
public int getZoneSize() {
return zoneSize;
}
public void setZoneSize(int zoneSize) {
this.zoneSize = zoneSize;
}
// TODO: next six setters & getters shouldn't be used in the future.
public double getBearingToNextCorner() {
return bearingToNextCorner;
}
public void setBearingToNextCorner(double bearingToNextCorner) {
this.bearingToNextCorner = bearingToNextCorner;
}
public double getDistanceToNextCorner() {
return distanceToNextCorner;
}
public void setDistanceToNextCorner(double distanceToNextCorner) {
this.distanceToNextCorner = distanceToNextCorner;
}
public Corner getNextCorner() {
return nextCorner;
}
public void setNextCorner(Corner nextCorner) {
this.nextCorner = nextCorner;
}
}
@@ -0,0 +1,53 @@
package seng302.server.simulator.mark;
/**
* An abstract class to represent general marks
* Created by Haoming Yin (hyi25) on 17/3/17.
*/
public class Mark extends Position {
private int seqID;
private String name;
private int sourceID;
public Mark(String name, double lat, double lng, int sourceID) {
super(lat, lng);
this.name = name;
this.sourceID = sourceID;
}
/**
* Prints out mark's info and its geo location, good for testing
* @return a string showing its details
*/
@Override
public String toString() {
return String.format("Mark%d: %s, source: %d, lat: %f, lng: %f", seqID, name, sourceID, lat, lng);
}
public int getSeqID() {
return seqID;
}
public void setSeqID(int seqID) {
this.seqID = seqID;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSourceID() {
return sourceID;
}
public void setSourceID(int sourceID) {
this.sourceID = sourceID;
}
}
@@ -0,0 +1,31 @@
package seng302.server.simulator.mark;
public class Position {
double lat, lng;
public Position(double lat, double lng) {
this.lat = lat;
this.lng = lng;
}
public String toString() {
return String.format("Position at lat:%f lng:%f.", lat, lng);
}
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public double getLng() {
return lng;
}
public void setLng(double lng) {
this.lng = lng;
}
}
@@ -0,0 +1,43 @@
package seng302.server.simulator.mark;
public enum RoundingType {
// the mark should be rounded to port (boat's left)
PORT("Port"),
// the mark should be rounded to starboard (boat's right)
STARBOARD("Stbd"),
// the boat within the compound mark with the SeqID of 1 should be rounded
// to starboard and the boat within the compound mark with the SeqID of 2
// should be rounded to port.
SP("SP"),
// the opposite of SP
PS("PS");
private String type;
RoundingType(String type) {
this.type = type;
}
public String getType() {
return this.type;
}
public static RoundingType typeOf(String type) {
switch (type) {
case "Port":
return PORT;
case "Stbd":
return STARBOARD;
case "SP":
return SP;
case "PS":
return PS;
default:
return null;
}
}
}
@@ -0,0 +1,20 @@
package seng302.server.simulator.parsers;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
/**
* Parses the race xml file to get course details
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public class BoatsParser extends FileParser {
private Document doc;
public BoatsParser(String path) {
super(path);
this.doc = this.parseFile();
}
}
@@ -0,0 +1,118 @@
package seng302.server.simulator.parsers;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import seng302.server.simulator.mark.CompoundMark;
import seng302.server.simulator.mark.Corner;
import seng302.server.simulator.mark.Mark;
import seng302.server.simulator.mark.RoundingType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Parses the race xml file to get course details
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public class CourseParser extends FileParser {
private Document doc;
private Map<Integer, CompoundMark> compoundMarksMap;
public CourseParser(String path) {
super(path);
this.doc = this.parseFile();
}
// TODO: should handle error / invalid file gracefully
protected List<Corner> getCourse() {
compoundMarksMap = getCompoundMarks(doc.getDocumentElement());
List<Corner> corners = new ArrayList<>();
NodeList cMarksSequence = doc.getElementsByTagName("Corner");
for (int i = 0; i < cMarksSequence.getLength(); i++) {
corners.add(getCorner(cMarksSequence.item(i)));
}
return corners;
}
private Corner getCorner(Node node) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) node;
Integer seqId = Integer.valueOf(e.getAttribute("SeqID"));
Integer cMarkId = Integer.valueOf(e.getAttribute("CompoundMarkID"));
CompoundMark cMark = compoundMarksMap.get(cMarkId);
RoundingType roundingType = RoundingType.typeOf(e.getAttribute("Rounding"));
Integer zoneSize = Integer.valueOf(e.getAttribute("ZoneSize"));
return new Corner(seqId, cMark, roundingType, zoneSize);
}
return null;
}
private Map<Integer, CompoundMark> getCompoundMarks(Node node) {
Map<Integer, CompoundMark> compoundMarksMap = new HashMap<>();
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
NodeList cMarks = element.getElementsByTagName("CompoundMark");
// loop through all compound marks who are the children of course node
for (int i = 0; i < cMarks.getLength(); i++) {
CompoundMark cMark = getCompoundMark(cMarks.item(i));
if (cMark != null)
compoundMarksMap.put(cMark.getMarkID(), cMark);
}
return compoundMarksMap;
}
return null;
}
private CompoundMark getCompoundMark(Node node) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) node;
Integer markID = Integer.valueOf(e.getAttribute("CompoundMarkID"));
String name = e.getAttribute("Name");
CompoundMark cMark = new CompoundMark(markID, name);
NodeList marks = e.getElementsByTagName("Mark");
for (int i = 0; i < marks.getLength(); i++) {
Mark mark = getMark(marks.item(i));
if (mark != null)
cMark.addMark(mark.getSeqID(), mark);
}
return cMark;
}
System.out.println("Failed to create compound mark.");
return null;
}
private Mark getMark(Node node) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) node;
Integer seqId = Integer.valueOf(e.getAttribute("SeqID"));
String name = e.getAttribute("Name");
Double lat = Double.valueOf(e.getAttribute("TargetLat"));
Double lng = Double.valueOf(e.getAttribute("TargetLng"));
Integer sourceId = Integer.valueOf(e.getAttribute("SourceID"));
Mark mark = new Mark(name, lat, lng, sourceId);
mark.setSeqID(seqId);
return mark;
}
System.out.println("Failed to create mark.");
return null;
}
}
@@ -0,0 +1,52 @@
package seng302.server.simulator.parsers;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;
import java.io.StringReader;
/**
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public abstract class FileParser {
private String filePath;
public FileParser() {}
public FileParser(String path) {
this.filePath = path;
}
protected Document parseFile() {
try {
InputStream is = getClass().getResourceAsStream(this.filePath);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(is);
// optional, in order to recover info from broken line.
doc.getDocumentElement().normalize();
return doc;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
protected Document parseFile(String xmlString) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xmlString)));
// optional, in order to recover info from broken line.
doc.getDocumentElement().normalize();
return doc;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
@@ -0,0 +1,66 @@
package seng302.server.simulator.parsers;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import seng302.server.simulator.Boat;
import seng302.server.simulator.mark.Corner;
import java.util.ArrayList;
import java.util.List;
/**
* Parses the race xml file to get course details
* Created by Haoming Yin (hyi25) on 16/3/2017
*/
public class RaceParser extends FileParser {
private Document doc;
private String path;
public RaceParser(String path) {
super(path);
this.path = path;
this.doc = this.parseFile();
}
/**
* Parses race.xml file and returns a list of corner which is the race course.
* @return a list of ordered corner to represent the course.
*/
public List<Corner> getCourse() {
CourseParser cp = new CourseParser(path);
return cp.getCourse();
}
/**
* Parses race.xml file and return a list of boats which will compete in the
* race.
* @return a list of boats that are going to compete in the race.
*/
public List<Boat> getBoats() {
NodeList yachts = doc.getDocumentElement().getElementsByTagName("Yacht");
List<Boat> boats = new ArrayList<>();
for (int i = 0; i < yachts.getLength(); i++) {
boats.add(getBoat(yachts.item(i)));
}
return boats;
}
/**
* Parses a single boat from the given node
* @param node a node within a boat tag
* @return a boat instance parsed from the given node
*/
private Boat getBoat(Node node) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) node;
Integer sourceId = Integer.valueOf(e.getAttribute("SourceID"));
return new Boat(sourceId, "Test Boat");
}
return null;
}
}
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" ?>
<configurations>
<race-name>AC35</race-name>
<race-size>6</race-size>
<time-scale>10.0</time-scale>
<wind-direction>135</wind-direction>
</configurations>
+80
View File
@@ -0,0 +1,80 @@
<?xml version="1.0" ?>
<markers>
<marks>
<gate>
<name type="start-line">Start</name>
<mark>
<name>Start1</name>
<latitude>57.6703330</latitude>
<longitude>11.8278330</longitude>
<id>122</id>
</mark>
<mark>
<name>Start2</name>
<latitude>57.6706330</latitude>
<longitude>11.8281330</longitude>
<id>123</id>
</mark>
</gate>
<mark>
<name>Mid Mark</name>
<latitude>57.6675700</latitude>
<longitude>11.8359880</longitude>
<id>131</id>
</mark>
<gate>
<name>Leeward Gate</name>
<mark>
<name>Leeward Gate1</name>
<latitude>57.6708220</latitude>
<longitude>11.8433900</longitude>
<id>124</id>
</mark>
<mark>
<name>Leeward Gate2</name>
<latitude>57.6711220</latitude>
<longitude>11.8436900</longitude>
<id>125</id>
</mark>
</gate>
<gate>
<name>Windward Gate</name>
<mark>
<name>Windward Gate1</name>
<latitude>57.6650170</latitude>
<longitude>11.8279170</longitude>
<id>126</id>
</mark>
<mark>
<name>Windward Gate2</name>
<latitude>57.6653170</latitude>
<longitude>11.8282170</longitude>
<id>127</id>
</mark>
</gate>
<gate type="finish-line">
<name>Finish</name>
<mark>
<name>Finish1</name>
<latitude>57.6715240</latitude>
<longitude>11.8444950</longitude>
<id>128</id>
</mark>
<mark>
<name>Finish2</name>
<latitude>57.6718240</latitude>
<longitude>11.8447950</longitude>
<id>129</id>
</mark>
</gate>
</marks>
<order>
<one>Start</one>
<two>Mid Mark</two>
<three>Leeward Gate</three>
<four>Windward Gate</four>
<five>Leeward Gate</five>
<six>Finish</six>
</order>
</markers>
+72
View File
@@ -0,0 +1,72 @@
<?xml version="1.0" ?>
<course>
<marks>
<gate>
<name type="start-line">Start</name>
<mark>
<name>Start1</name>
<latitude>32.296577</latitude>
<longitude>-64.854304</longitude>
</mark>
<mark>
<name>Start2</name>
<latitude>32.293771</latitude>
<longitude>-64.855242</longitude>
</mark>
</gate>
<mark>
<name>Mid Mark</name>
<latitude>32.293039</latitude>
<longitude>-64.843983</longitude>
</mark>
<gate>
<name>Leeward Gate</name>
<mark>
<name>Leeward Gate1</name>
<latitude>32.284680</latitude>
<longitude>-64.850045</longitude>
</mark>
<mark>
<name>Leeward Gate2</name>
<latitude>32.280164</latitude>
<longitude>-64.847591</longitude>
</mark>
</gate>
<gate>
<name>Windward Gate</name>
<mark>
<name>Windward Gate1</name>
<latitude>32.309693</latitude>
<longitude>-64.835249</longitude>
</mark>
<mark>
<name>Windward Gate2</name>
<latitude>32.308046</latitude>
<longitude>-64.831785</longitude>
</mark>
</gate>
<gate type="finish-line">
<name>Finish</name>
<mark>
<name>Finish1</name>
<latitude>32.317379</latitude>
<longitude>-64.839291</longitude>
</mark>
<mark>
<name>Finish2</name>
<latitude>32.317257</latitude>
<longitude>-64.836260</longitude>
</mark>
</gate>
</marks>
<order>
<one>Start</one>
<two>Mid Mark</two>
<three>Leeward Gate</three>
<four>Windward Gate</four>
<five>Leeward Gate</five>
<six>Finish</six>
</order>
</course>
+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0" ?>
<teams>
<team>
<name>Oracle Team USA</name>
<alias>USA</alias>
<velocity>0.0</velocity>
<id>102</id>
</team>
<team>
<name>Artemis Racing</name>
<alias>ART</alias>
<velocity>0.0</velocity>
<id>101</id>
</team>
<team>
<name>Emirates Team New Zealand</name>
<alias>NZL</alias>
<velocity>0.0</velocity>
<id>103</id>
</team>
<team>
<name>Land Rover BAR</name>
<alias>BAR</alias>
<velocity>0.0</velocity>
<id>104</id>
</team>
<team>
<name>SoftBank Team Japan</name>
<alias>JAP</alias>
<velocity>0.0</velocity>
<id>105</id>
</team>
<team>
<name>Groupama Team France</name>
<alias>FRC</alias>
<velocity>0.0</velocity>
<id>106</id>
</team>
</teams>
+171
View File
@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2015-08-28T17:32:59+0100</Modified>
<Version>12</Version>
<Snapshot>219</Snapshot>
<Settings>
<RaceBoatType Type="AC45"/>
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="53.796"/>
<ZoneLimits Limit1="200" Limit2="100" Limit3="53.796" Limit4="0" Limit5="-100"/>
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="3" Y="25" X="0"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="14">
<Vertices>
<Vtx Seq="1" Y="0" X="-1"/>
<Vtx Seq="2" Y="0.75" X="-1"/>
<Vtx Seq="3" Y="0.75" X="-0.25"/>
<Vtx Seq="4" Y="3.5" X="-0.25"/>
<Vtx Seq="5" Y="4.5" X="-1"/>
<Vtx Seq="6" Y="6.5" X="-1"/>
<Vtx Seq="7" Y="7" X="-0.5"/>
<Vtx Seq="8" Y="7" X="0.5"/>
<Vtx Seq="9" Y="6.5" X="1"/>
<Vtx Seq="10" Y="4.5" X="1"/>
<Vtx Seq="11" Y="3.5" X="0.25"/>
<Vtx Seq="12" Y="0.75" X="0.25"/>
<Vtx Seq="13" Y="0.75" X="1"/>
<Vtx Seq="14" Y="0" X="1"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="15">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46"/>
<Vtx Seq="2" Y="13.449" X="-3.46"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="13.449" X="3.46"/>
<Vtx Seq="5" Y="0" X="3.46"/>
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752"/>
<Vtx Seq="2" Y="0" X="-2.813"/>
<Vtx Seq="3" Y="0" X="-3.34"/>
<Vtx Seq="4" Y="5.351" X="-3.46"/>
<Vtx Seq="5" Y="10.544" X="-3.387"/>
<Vtx Seq="6" Y="13.449" X="-3.075"/>
<Vtx Seq="7" Y="10.851" X="-2.793"/>
<Vtx Seq="8" Y="6.669" X="-2.699"/>
<Vtx Seq="9" Y="6.669" X="2.699"/>
<Vtx Seq="10" Y="10.851" X="2.793"/>
<Vtx Seq="11" Y="13.449" X="3.075"/>
<Vtx Seq="12" Y="10.544" X="3.387"/>
<Vtx Seq="13" Y="5.351" X="3.46"/>
<Vtx Seq="14" Y="0" X="3.34"/>
<Vtx Seq="15" Y="0" X="2.813"/>
<Vtx Seq="16" Y="1.769" X="2.752"/>
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2"/>
<Vtx Seq="2" Y="11.377" X="-0.2"/>
<Vtx Seq="3" Y="14.019" X="0"/>
<Vtx Seq="4" Y="11.377" X="0.2"/>
<Vtx Seq="5" Y="6.669" X="0.2"/>
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699"/>
<Vtx Seq="2" Y="6.438" X="-2.699"/>
<Vtx Seq="3" Y="6.438" X="2.699"/>
<Vtx Seq="4" Y="2" X="2.699"/>
</Trampoline>
</BoatShape>
<BoatShape ShapeID="18">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.04"/>
<Vtx Seq="2" Y="0.11" X="-1.18"/>
<Vtx Seq="3" Y="0.42" X="-1.28"/>
<Vtx Seq="4" Y="3.74" X="-1.29"/>
<Vtx Seq="5" Y="5.36" X="-1.21"/>
<Vtx Seq="6" Y="6.29" X="-1.08"/>
<Vtx Seq="7" Y="7.15" X="-0.84"/>
<Vtx Seq="8" Y="7.63" X="-0.62"/>
<Vtx Seq="9" Y="7.94" X="-0.34"/>
<Vtx Seq="10" Y="8.06" X="0"/>
<Vtx Seq="11" Y="7.94" X="0.34"/>
<Vtx Seq="12" Y="7.63" X="0.62"/>
<Vtx Seq="13" Y="7.15" X="0.84"/>
<Vtx Seq="14" Y="6.29" X="1.08"/>
<Vtx Seq="15" Y="5.36" X="1.21"/>
<Vtx Seq="16" Y="3.74" X="1.29"/>
<Vtx Seq="17" Y="0.42" X="1.28"/>
<Vtx Seq="18" Y="0.11" X="1.18"/>
<Vtx Seq="19" Y="0" X="1.04"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="24">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.5"/>
<Vtx Seq="2" Y="7" X="-2.5"/>
<Vtx Seq="3" Y="12.6" X="-2.2"/>
<Vtx Seq="4" Y="12.6" X="2.2"/>
<Vtx Seq="5" Y="7" X="2.5"/>
<Vtx Seq="6" Y="0" X="2.5"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="34">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.16"/>
<Vtx Seq="2" Y="5.51" X="-1.16"/>
<Vtx Seq="3" Y="5.846" X="-0.84"/>
<Vtx Seq="4" Y="5.846" X="0.84"/>
<Vtx Seq="5" Y="5.51" X="1.16"/>
<Vtx Seq="6" Y="0" X="1.16"/>
</Vertices>
</BoatShape>
<BoatShape ShapeID="35">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.461"/>
<Vtx Seq="2" Y="6" X="-1.461"/>
<Vtx Seq="3" Y="7" X="-1.44"/>
<Vtx Seq="4" Y="8" X="-1.38"/>
<Vtx Seq="5" Y="9" X="-1.17"/>
<Vtx Seq="6" Y="10" X="-0.76"/>
<Vtx Seq="7" Y="10.6" X="-0.34"/>
<Vtx Seq="8" Y="10.61" X="0"/>
<Vtx Seq="9" Y="10.6" X="0.34"/>
<Vtx Seq="10" Y="10" X="0.76"/>
<Vtx Seq="11" Y="9" X="1.17"/>
<Vtx Seq="12" Y="8" X="1.38"/>
<Vtx Seq="13" Y="7" X="1.44"/>
<Vtx Seq="14" Y="6" X="1.461"/>
<Vtx Seq="15" Y="0" X="1.461"/>
</Vertices>
</BoatShape>
</BoatShapes>
<Boats>
<Boat Type="Yacht" SourceID="101" ShapeID="15" StoweName="USA" ShortName="ORACLE" ShorterName="USA" BoatName="ORACLE TEAM USA" HullNum="AC4515" Skipper="SPITHILL" Helmsman="SPITHILL" Country="USA" PeliID="101" RadioIP="172.20.2.101">
<GPSposition Z="1.78" Y="-0.331" X="-0.006"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="102" ShapeID="15" StoweName="SWE" ShortName="ARTEMIS" ShorterName="SWE" BoatName="ARTEMIS RACING" HullNum="AC4517" Skipper="OUTTERIDGE" Helmsman="OUTTERIDGE" Country="SWE" PeliID="102" RadioIP="172.20.2.102">
<GPSposition Z="1.727" Y="-0.359" X="-0.0121"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="103" ShapeID="15" StoweName="NZL" ShortName="ETNZ" ShorterName="NZL" BoatName="EMIRATES TEAM NZ" HullNum="AC4503" Skipper="ASHBY" Helmsman="BURLING" Country="NZL" PeliID="103" RadioIP="172.20.2.103">
<GPSposition Z="1.881" Y="-0.291" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="104" ShapeID="15" StoweName="JPN" ShortName="JAPAN" ShorterName="JPN" BoatName="SOFTBANK TEAM JAPAN" HullNum="AC4504" Skipper="BARKER" Helmsman="BARKER" Country="JPN" PeliID="104" RadioIP="172.20.2.104">
<GPSposition Z="1.805" Y="-0.322" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="105" ShapeID="15" StoweName="FRA" ShortName="FRANCE" ShorterName="FRA" BoatName="GROUPAMA TEAM FRANCE" HullNum="AC4505" Skipper="CAMMAS" Helmsman="CAMMAS" Country="FRA" PeliID="105" RadioIP="172.20.2.105">
<GPSposition Z="1.863" Y="-0.3" X="-0.003"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
<Boat Type="Yacht" SourceID="106" ShapeID="15" StoweName="GBR" ShortName="GBR" ShorterName="GBR" BoatName="LAND ROVER BAR" HullNum="AC4516" Skipper="ANSLIE" Helmsman="ANSLIE" Country="GBR" PeliID="106" RadioIP="172.20.2.106">
<GPSposition Z="1.734" Y="-0.352" X="0"/>
<MastTop Z="21.496" Y="3.7" X="0"/>
<FlagPosition Z="0" Y="6.2" X="0"/>
</Boat>
</Boats>
</BoatConfig>
+85
View File
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<Race>
<CreationTimeDate>2015-08-29T13:12:40+02:00</CreationTimeDate>
<RaceStartTime Start="2015-08-29T13:10:00+02:00" Postpone="False" />
<RaceID>15082901</RaceID>
<RaceType>Fleet</RaceType>
<Participants>
<Yacht SourceID="101" />
<Yacht SourceID="102" />
<Yacht SourceID="103" />
<Yacht SourceID="104" />
<Yacht SourceID="105" />
<Yacht SourceID="106" />
</Participants>
<Course>
<CompoundMark CompoundMarkID="1" Name="Mark0">
<Mark SeqID="1" Name="Start Line 1" TargetLat="57.6703330" TargetLng="11.8278330" SourceID="122" />
<Mark SeqID="2" Name="Start Line 2" TargetLat="57.6703330" TargetLng="11.8278330" SourceID="123" />
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="Mark1">
<Mark SeqID="1" Name="Mark1" TargetLat="57.6675700" TargetLng="11.8359880" SourceID="131" />
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="Mark2">
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="Mark3">
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
</CompoundMark>
<CompoundMark CompoundMarkID="5" Name="Mark2">
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
</CompoundMark>
<CompoundMark CompoundMarkID="6" Name="Mark3">
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
</CompoundMark>
<CompoundMark CompoundMarkID="7" Name="Mark2">
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
</CompoundMark>
<CompoundMark CompoundMarkID="8" Name="Mark3">
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
</CompoundMark>
<CompoundMark CompoundMarkID="9" Name="Mark2">
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
</CompoundMark>
<CompoundMark CompoundMarkID="10" Name="Mark3">
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
</CompoundMark>
<CompoundMark CompoundMarkID="11" Name="Mark4">
<Mark SeqID="1" Name="Finish Line 1" TargetLat="57.6715240" TargetLng="11.8444950" SourceID="128" />
<Mark SeqID="2" Name="Finish Line 2" TargetLat="57.6715240" TargetLng="11.8444950" SourceID="129" />
</CompoundMark>
</Course>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="PS" ZoneSize="3" />
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3" />
<Corner SeqID="3" CompoundMarkID="3" Rounding="SP" ZoneSize="3" />
<Corner SeqID="4" CompoundMarkID="4" Rounding="PS" ZoneSize="3" />
<Corner SeqID="5" CompoundMarkID="5" Rounding="SP" ZoneSize="3" />
<Corner SeqID="6" CompoundMarkID="6" Rounding="PS" ZoneSize="3" />
<Corner SeqID="7" CompoundMarkID="7" Rounding="SP" ZoneSize="3" />
<Corner SeqID="8" CompoundMarkID="8" Rounding="PS" ZoneSize="3" />
<Corner SeqID="9" CompoundMarkID="9" Rounding="SP" ZoneSize="3" />
<Corner SeqID="10" CompoundMarkID="10" Rounding="PS" ZoneSize="3" />
<Corner SeqID="11" CompoundMarkID="11" Rounding="PS" ZoneSize="3" />
</CompoundMarkSequence>
<CourseLimit>
<Limit SeqID="1" Lat="57.6739450" Lon="11.8417100" />
<Limit SeqID="2" Lat="57.6709520" Lon="11.8485010" />
<Limit SeqID="3" Lat="57.6690260" Lon="11.8472790" />
<Limit SeqID="4" Lat="57.6693140" Lon="11.8457610" />
<Limit SeqID="5" Lat="57.6665370" Lon="11.8432910" />
<Limit SeqID="6" Lat="57.6641400" Lon="11.8385840" />
<Limit SeqID="7" Lat="57.6629430" Lon="11.8332030" />
<Limit SeqID="8" Lat="57.6629480" Lon="11.8249660" />
<Limit SeqID="9" Lat="57.6686890" Lon="11.8250920" />
<Limit SeqID="10" Lat="57.6708220" Lon="11.8321340" />
</CourseLimit>
</Race>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<RegattaConfig>
<RegattaID>24</RegattaID>
<RegattaName>Gothenburg World Series 2015</RegattaName>
<CourseName>Gothenburg</CourseName>
<CentralLatitude>57.6679590</CentralLatitude>
<CentralLongitude>11.8503233</CentralLongitude>
<CentralAltitude>6.95</CentralAltitude>
<UtcOffset>2</UtcOffset>
<MagneticVariation>3.2</MagneticVariation>
<ShorelineName>gothenburg_shoreline</ShorelineName>
</RegattaConfig>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="canvasPane" prefHeight="960.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.CanvasController" />
+55
View File
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="raceResults" maxHeight="1080.0" maxWidth="1920.0" minHeight="1080.0" minWidth="1920.0" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<GridPane layoutX="444.0" layoutY="256.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="620.1734008789062" minWidth="10.0" prefWidth="493.2829895019531" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="862.0581665039062" minWidth="10.0" prefWidth="786.7170104980469" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="348.0" minHeight="10.0" prefHeight="112.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="348.0" minHeight="10.0" prefHeight="99.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="348.0" minHeight="7.0" prefHeight="88.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="348.0" minHeight="0.0" prefHeight="278.3376770019531" vgrow="SOMETIMES" />
<RowConstraints maxHeight="653.0" minHeight="0.0" prefHeight="98.66232299804688" vgrow="SOMETIMES" />
<RowConstraints maxHeight="812.0" minHeight="10.0" prefHeight="418.4577941894531" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Race Results:" wrappingWidth="616.5260620117188" GridPane.rowIndex="2">
<font>
<Font size="45.0" />
</font>
<GridPane.margin>
<Insets left="50.0" />
</GridPane.margin>
</Text>
<VBox fx:id="resultsVBox" prefHeight="44.0" prefWidth="571.0" GridPane.rowIndex="3">
<opaqueInsets>
<Insets />
</opaqueInsets>
<GridPane.margin>
<Insets left="50.0" />
</GridPane.margin>
<padding>
<Insets top="60.0" />
</padding></VBox>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Finish!" wrappingWidth="623.9530334472656" GridPane.rowIndex="1">
<font>
<Font size="75.0" />
</font>
<GridPane.margin>
<Insets left="50.0" />
</GridPane.margin>
</Text>
</children>
</GridPane>
</children>
</AnchorPane>
+64
View File
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.canvas.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="contentPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller">
<children>
<GridPane nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints percentHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="52.0" minHeight="52.0" prefHeight="52.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="0.0" percentHeight="8.0" prefHeight="0.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="28.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="55.0" minHeight="55.0" percentHeight="5.0" prefHeight="55.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="0.0" minHeight="0.0" percentHeight="23.0" prefHeight="0.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="93.0" minHeight="72.0" prefHeight="72.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="283.0" minHeight="262.0" prefHeight="283.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label alignment="CENTER" text="Welcome to Race Vision" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<font>
<Font size="40.0" />
</font>
</Label>
<Label text="Your live AC35 livestream" GridPane.halignment="CENTER" GridPane.rowIndex="1">
<font>
<Font size="20.0" />
</font>
</Label>
<Label text="Livestream Status:" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="BOTTOM">
<font>
<Font size="28.0" />
</font>
</Label>
<Label fx:id="timeTillLive" text="0:00 minutes" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="4">
<font>
<Font size="27.0" />
</font>
</Label>
<Button fx:id="streamButton" mnemonicParsing="false" onAction="#startStream" text="Click to stream" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
<Button fx:id="switchToRaceViewButton" disable="true" mnemonicParsing="false" onAction="#switchToRaceView" text="Watch Race" GridPane.halignment="CENTER" GridPane.rowIndex="7" GridPane.valignment="TOP" />
<TableView fx:id="teamList" maxWidth="500.0" prefHeight="200.0" prefWidth="210.0" GridPane.halignment="CENTER" GridPane.rowIndex="5">
<columns>
<TableColumn fx:id="posCol" editable="false" maxWidth="74.0" minWidth="74.0" prefWidth="74.0" resizable="false" sortable="false" text="Position" />
<TableColumn fx:id="boatNameCol" editable="false" maxWidth="171.0" minWidth="171.0" prefWidth="171.0" resizable="false" sortable="false" text="Boat Name" />
<TableColumn fx:id="shortNameCol" editable="false" maxWidth="107.0" minWidth="107.0" prefWidth="107.0" resizable="false" sortable="false" text="Short Name" />
<TableColumn fx:id="countryCol" editable="false" maxWidth="147.0" minWidth="147.0" prefWidth="147.0" resizable="false" sortable="false" text="Country" />
</columns>
<GridPane.margin>
<Insets />
</GridPane.margin>
</TableView>
<Label fx:id="realTime" text="Local time" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="BOTTOM" />
</children>
</GridPane>
</children>
</AnchorPane>
+68
View File
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<GridPane prefHeight="1080.0" prefWidth="1920.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.RaceViewController">
<columnConstraints>
<ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="500.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints />
<RowConstraints />
</rowConstraints>
<children>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowSpan="3">
<children>
<Label layoutX="11.0" layoutY="259.0" text="Team Position" />
<Label layoutX="13.0" layoutY="432.0" text="Settings" />
<Label layoutX="11.0" layoutY="14.0" text="Timer" />
<Label layoutX="11.0" layoutY="88.0" text="Wind direction" />
<Circle fx:id="windBackgroundCircle" blendMode="DARKEN" fill="#76baf8" layoutX="110.0" layoutY="166.0" radius="35.0" stroke="#686868" strokeType="INSIDE" strokeWidth="3.0" />
<Text fx:id="windArrowText" fill="#686868" layoutX="86.0" layoutY="186.0" strokeType="OUTSIDE" strokeWidth="0.0" text="↑">
<font>
<Font name="AdobeArabic-Regular" size="55.0" />
</font>
</Text>
<Text fx:id="windDirectionText" fill="#a8a7a7" layoutX="171.0" layoutY="214.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0°" textAlignment="RIGHT">
<font>
<Font name="System Bold" size="13.0" />
</font>
</Text>
<CheckBox fx:id="toggleFps" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" text="Show FPS" />
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" />
<Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0">
<children>
<Text fx:id="timerLabel" layoutX="-26.0" layoutY="34.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="246.0">
<font>
<Font size="25.0" />
</font>
</Text>
</children>
</Pane>
<Slider fx:id="annotationSlider" blockIncrement="1.0" layoutX="38.0" layoutY="527.0" majorTickUnit="1.0" max="3.0" minorTickCount="0" prefHeight="51.0" prefWidth="170.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" />
<Label layoutX="10.0" layoutY="499.0" text="Annotations" />
</children>
</AnchorPane>
<AnchorPane fx:id="contentAnchorPane" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: skyblue;" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP">
<children>
<fx:include fx:id="includedCanvas" source="CanvasView.fxml" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children></AnchorPane>
</children>
</GridPane>
-15
View File
@@ -1,15 +0,0 @@
package seng302;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
/**
* Unit test for simple App.
*/
public class AppTest {
@Test
public void testApp() {
assertTrue(true);
}
}
+35 -34
View File
@@ -1,34 +1,35 @@
package seng302; //package seng302;
//
import org.junit.Test; //import org.junit.Test;
//import seng302.models.Boat;
import static org.junit.Assert.assertEquals; //
//import static org.junit.Assert.assertEquals;
/** //
* Unit test for the Team class. ///**
*/ // * Unit test for the Team class.
public class BoatTest { // */
//public class BoatTest {
@Test //
public void testBoatCreation() { // @Test
Boat boat1 = new Boat("Team 1"); // public void testBoatCreation() {
assertEquals(boat1.getTeamName(), "Team 1"); // Boat boat1 = new Boat("Team 1");
assertEquals(boat1.getVelocity(), (double) 10.0, 1e-15); // assertEquals(boat1.getTeamName(), "Team 1");
} // assertEquals(boat1.getVelocity(), (double) 10.0, 1e-15);
// }
@Test //
public void testChangeTeamName() { // @Test
Boat boat1 = new Boat("Team 1"); // public void testChangeTeamName() {
boat1.setTeamName("Team 2"); // Boat boat1 = new Boat("Team 1");
assertEquals(boat1.getTeamName(), "Team 2"); // boat1.setTeamName("Team 2");
} // assertEquals(boat1.getTeamName(), "Team 2");
// }
@Test //
public void testSetVelocity() { // @Test
Boat boat1 = new Boat("Team 1", 29.0); // public void testSetVelocity() {
assertEquals(boat1.getVelocity(), (double) 29.0, 1e-15); // Boat boat1 = new Boat("Team 1", 29.0, "", 100);
// assertEquals(boat1.getVelocity(), (double) 29.0, 1e-15);
boat1.setVelocity(12.0); //
assertEquals(boat1.getVelocity(), (double)12.0, 1e-15); // boat1.setVelocity(12.0);
} // assertEquals(boat1.getVelocity(), (double)12.0, 1e-15);
} // }
//}
+24
View File
@@ -0,0 +1,24 @@
package seng302;
import javafx.scene.paint.Color;
import org.junit.Assert;
import org.junit.Test;
import seng302.models.Colors;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
/**
* Created by ryan_ on 16/03/2017.
*/
public class ColorsTest {
@Test
public void testNextColor() {
Color expectedColors[] = {Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.PURPLE};
for (int i = 0; i<6; i++)
{
Assert.assertEquals(expectedColors[i], Colors.getColor());
}
}
}
+15 -17
View File
@@ -1,6 +1,9 @@
package seng302; package seng302;
import org.junit.Test; import org.junit.Test;
import seng302.models.Event;
import seng302.models.Yacht;
import seng302.models.mark.SingleMark;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -12,29 +15,24 @@ public class EventTest {
@Test @Test
public void getTimeString() throws Exception { public void getTimeString() throws Exception {
Leg leg = new Leg(35, 100, "Start"); Yacht boat = new Yacht("testBoat");
Boat boat = new Boat("testBoat"); Event event = new Event(1231242.2, boat, new SingleMark("mark1"), new SingleMark("mark2"), 0);
Event event = new Event(1231242, boat, leg);
assertEquals("20:31:242", event.getTimeString()); assertEquals("20:31:242", event.getTimeString());
} }
/**
* ensure all boats are added as they pass the marker
*/
@Test @Test
public void boatOrderTest() throws Exception { public void testBoatHeading() throws Exception {
Leg leg = new Leg(35, 100, "1"); Yacht boat = new Yacht("testBoat");
Event event = new Event(1231242.2, boat, new SingleMark("mark1", 142.5, 122.1, 1), new SingleMark("mark2", 121.9,99.2, 2), 0);
Boat boat1 = new Boat("testBoat"); assertEquals(event.getBoatHeading(), 228.0266137055349, 1e-15);
Boat boat2 = new Boat("testBoat2"); }
Event event1 = new Event(1231242, boat1, leg); @Test
Event event2 = new Event(1231242, boat2, leg); public void testDistanceBetweenMarks() throws Exception {
Yacht boat = new Yacht("testBoat");
Event event = new Event(1231242.2, boat, new SingleMark("mark1", 142.5, 122.1, 1), new SingleMark("mark2", 121.9,99.2, 2), 0);
event1.boatPassedMarker(); assertEquals(event.getDistanceBetweenMarks(), 339059.653830461, 1e-15);
event2.boatPassedMarker();
assertEquals(event1.getLeg().getMarker().getBoats()[0].getTeamName(), "testBoat");
assertEquals(event2.getLeg().getMarker().getBoats()[1].getTeamName(), "testBoat2");
} }
} }
-52
View File
@@ -1,52 +0,0 @@
package seng302;
import org.junit.Test;
import java.io.FileNotFoundException;
import static org.junit.Assert.assertEquals;
/**
* Unit test for FileParser class
* Created by Haoming on 5/03/17.
*/
public class FileParserTest {
/**
* test if it fails from reading non existed file
*/
@Test(expected = FileNotFoundException.class)
public void readNonExistedFile() throws Exception {
FileParser fileParser = new FileParser("test/java/seng302/non-existed.json");
}
/**
* test a valid json file with valid content.
*/
@Test
public void readValidFile() throws Exception {
FileParser fileParser = new FileParser("src/test/java/seng302/valid.json");
assertEquals("AC35", fileParser.getRaceName());
assertEquals("Oracle Team USA", fileParser.getTeams().get(0).get("team-name"));
assertEquals(20.9, fileParser.getTeams().get(0).get("velocity"));
assertEquals(2, fileParser.getRaceSize());
assertEquals(6, fileParser.getTotalNumberOfTeams());
}
/**
* test an invalid json file within wrong type value and misnamed
* variable name.
*/
@Test
public void readInvalidFile() throws Exception {
FileParser fileParser = new FileParser("src/test/java/seng302/invalid.json");
assertEquals(null, fileParser.getRaceName());
assertEquals(null, fileParser.getTeams());
//assertEquals(-1, fileParser.getTimeScale());
assertEquals(null, fileParser.getTeams());
}
}
+8 -6
View File
@@ -1,6 +1,8 @@
package seng302; package seng302;
import org.junit.Test; import org.junit.Test;
import seng302.models.Leg;
import seng302.models.mark.SingleMark;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -15,25 +17,25 @@ public class LegTest {
*/ */
@Test @Test
public void testLegCreationUsingMarkerLabel() { public void testLegCreationUsingMarkerLabel() {
Leg leg = new Leg(010, 100, "Marker"); Leg leg = new Leg(010, 100, "SingleMark");
assertEquals(leg.getHeading(), 010); assertEquals(leg.getHeading(), 010);
assertEquals(leg.getDistance(), 100); assertEquals(leg.getDistance(), 100);
assertEquals(leg.getMarkerLabel(), "Marker"); assertEquals(leg.getMarkerLabel(), "SingleMark");
assertEquals(leg.getIsFinishingLeg(), false); assertEquals(leg.getIsFinishingLeg(), false);
} }
/** /**
* Test creation of the leg by providing a * Test creation of the leg by providing a
* Marker object * SingleMark object
*/ */
@Test @Test
public void testLegCreation() { public void testLegCreation() {
Leg leg = new Leg(010, 100, new Marker("Marker")); Leg leg = new Leg(010, 100, new SingleMark("SingleMark"));
assertEquals(leg.getHeading(), 010); assertEquals(leg.getHeading(), 010);
assertEquals(leg.getDistance(), 100); assertEquals(leg.getDistance(), 100);
assertEquals(leg.getMarkerLabel(), "Marker"); assertEquals(leg.getMarkerLabel(), "SingleMark");
assertEquals(leg.getIsFinishingLeg(), false); assertEquals(leg.getIsFinishingLeg(), false);
} }
@@ -43,7 +45,7 @@ public class LegTest {
*/ */
@Test @Test
public void testSetFinishLeg() { public void testSetFinishLeg() {
Leg leg = new Leg(010, 100, "Marker"); Leg leg = new Leg(010, 100, "SingleMark");
leg.setFinishingLeg(true); leg.setFinishingLeg(true);
assertEquals(leg.getIsFinishingLeg(), true); assertEquals(leg.getIsFinishingLeg(), true);
+6 -4
View File
@@ -1,6 +1,8 @@
package seng302; package seng302;
import org.junit.Test; import org.junit.Test;
import seng302.models.Race;
import seng302.models.Yacht;
import java.lang.reflect.Array; import java.lang.reflect.Array;
@@ -15,8 +17,8 @@ public class RaceTest {
*/ */
@Test @Test
public void testAddingBoatsToRace() { public void testAddingBoatsToRace() {
Boat boat1 = new Boat("Team 1"); Yacht boat1 = new Yacht("Team 1");
Boat boat2 = new Boat("Team 2"); Yacht boat2 = new Yacht("Team 2");
Race race = new Race(); Race race = new Race();
race.addBoat(boat1); race.addBoat(boat1);
@@ -27,8 +29,8 @@ public class RaceTest {
@Test @Test
public void testGetShuffledBoats(){ public void testGetShuffledBoats(){
Boat boat1 = new Boat("Team 1"); Yacht boat1 = new Yacht("Team 1");
Boat boat2 = new Boat("Team 2"); Yacht boat2 = new Yacht("Team 2");
Race race = new Race(); Race race = new Race();
race.addBoat(boat1); race.addBoat(boat1);
+25
View File
@@ -0,0 +1,25 @@
package seng302;
import org.junit.Test;
import seng302.controllers.RaceViewController;
import static org.junit.Assert.assertTrue;
public class TestRaceTimer {
@Test
public void testPositiveTimeString(){
RaceViewController controller = new RaceViewController();
String result = controller.convertTimeToMinutesSeconds(61);
assertTrue(result.equals("01:01"));
}
@Test
public void testNegativeTimeString(){
RaceViewController controller = new RaceViewController();
String result = controller.convertTimeToMinutesSeconds(-61);
assertTrue(result.equals("-01:01"));
}
}
-9
View File
@@ -1,9 +0,0 @@
{
"time-scale": "abc",
"race-name": 123,
"teams-with-wrong-name": [
"team1",
"team2",
"team3"
]
}
@@ -0,0 +1,95 @@
// TODO: 4/05/17 cir27 - Currently the CI cannot build these tests. Unsure of exact issue but believe it may be a driver issue. Find a way to make tests work.
//package seng302.models;
//
//public class BoatGroupTest {
//
//}
package seng302.models;
/*
* Created by cir27 on 4/05/17.
public class BoatGroupTest {
BoatGroup boatGroup;
@Before
public void setUp () {
Yacht b = new Yacht("TEST", 0.0, "T" ,0);
boatGroup = new BoatGroup(b, Color.BLACK);
}
@Test
public void setDestinationFirstUseForcesLocationUpdate () {
boatGroup.setDestination(10, 10, 90, 0);
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
Assert.assertTrue(10 == bp.getLayoutX());
Assert.assertTrue(10 == bp.getLayoutY());
}
@Test
public void setDestinationFutureUseDoesntForce () {
for (int i = 0; i < 60; i++) {
boatGroup.setDestination(200, 200, 90, 0);
}
boatGroup.setDestination(210, 210, 90, 0);
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
Assert.assertTrue(200 == bp.getLayoutX());
Assert.assertTrue(200 == bp.getLayoutY());
}
@Test
public void setDestinationUnrealisticMovementForceUpdate () {
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
double xLocation = bp.getLayoutX();
double yLocation = bp.getLayoutY();
boatGroup.setDestination(xLocation + 500, yLocation + 500, 90, 0);
Assert.assertTrue(xLocation + 500 == bp.getLayoutX());
Assert.assertTrue(yLocation + 500 == bp.getLayoutY());
}
@Test
public void setDestinationUnrealisticNegativeForceUpdate () {
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
double xLocation = bp.getLayoutX();
double yLocation = bp.getLayoutY();
boatGroup.setDestination(xLocation - 500, yLocation - 500, 90, 0);
Assert.assertTrue(xLocation - 500 == bp.getLayoutX());
Assert.assertTrue(yLocation - 500 == bp.getLayoutY());
}
@Test
public void updatePositionGeneratesExpectedMovement () {
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
double xLocation = bp.getLayoutX();
double yLocation = bp.getLayoutY();
int movement = 10;
double delay = RaceObject.getExpectedUpdateInterval();
double defaultTimePeriod = 1000 / 60;
double expectedMovement = movement / delay * defaultTimePeriod;
for (int i = 0; i < 60; i++) {
boatGroup.setDestination(xLocation, yLocation, 90, 0);
}
boatGroup.setDestination(xLocation + 10, yLocation + 10, 90, 0);
boatGroup.updatePosition(1000/60);
Assert.assertEquals(expectedMovement, bp.getLayoutX() - xLocation, 0.0);
}
@Test
public void correctRaceID () {
Assert.assertTrue(boatGroup.hasRaceId(0));
}
@Test
public void incorrectRaceID () {
Assert.assertTrue(!boatGroup.hasRaceId(2));
}
@Test
public void nothingOnWrongId () {
Polygon bp = (Polygon) boatGroup.getChildren().get(2);
double originalX = bp.getLayoutX();
double originalY = bp.getLayoutY();
boatGroup.setDestination(10, 10, 90, 12);
Assert.assertTrue(originalX == bp.getLayoutX());
Assert.assertTrue(originalY == bp.getLayoutY());
}
}*/
@@ -0,0 +1,90 @@
// TODO: 4/05/17 cir27 - Currently the CI cannot build these tests. Unsure of exact issue but believe it may be a driver issue. Find a way to make tests work.
//
//package seng302.models;
//
//public class MarkGroupTest {
//
//}
package seng302.models;
/*
import javafx.scene.shape.Circle;
import seng302.*;
import javafx.geometry.Point2D;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import seng302.models.mark.*;
/**
* Created by cir27 on 4/05/17.
*/
//public class MarkGroupTest {
// private MarkGroup gateMG;
// private MarkGroup singleMG;
//
// @Before
// public void setUp () {
// Mark single = new SingleMark("SM", 0, 0 , 0);
// Mark gate = new GateMark(
// "GM",
// MarkType.OPEN_GATE,
// new SingleMark("GM1", 0, 0, 1),
// new SingleMark("GM2", 0, 0, 2),
// 0,
// 0);
// gateMG = new MarkGroup(gate, new Point2D(10, 10), new Point2D(20, 20));
// singleMG = new MarkGroup(single, new Point2D(0, 0));
// }
//
// @Test
// public void hasIDSingle () {
// Assert.assertTrue(singleMG.hasRaceId(0));
// Assert.assertTrue(!singleMG.hasRaceId(100,12));
// }
//
// @Test
// public void hasIdGate () {
// Assert.assertTrue(gateMG.hasRaceId(1));
// Assert.assertTrue(gateMG.hasRaceId(2));
// Assert.assertTrue(!gateMG.hasRaceId(100,12));
// }
//
// @Test
// public void nothingOnWrongId () {
// double originalX = singleMG.getChildren().get(0).getLayoutX();
// double originalY = singleMG.getChildren().get(0).getLayoutY();
// singleMG.setDestination(10, 10, 0, 4);
// singleMG.updatePosition(400);
// Assert.assertTrue(originalX == singleMG.getChildren().get(0).getLayoutX());
// Assert.assertTrue(originalY == singleMG.getChildren().get(0).getLayoutY());
// }
//
// @Test
// public void correctMovementCorrectIdSingle () {
// double originalX = singleMG.getChildren().get(0).getLayoutX();
// double originalY = singleMG.getChildren().get(0).getLayoutY();
// long timeinterval = 1000/60;
// double expectedChange = 10 / 200 * timeinterval;
// singleMG.setDestination(originalX + 10, originalY + 10, 0, 0);
// singleMG.updatePosition(timeinterval);
// Assert.assertTrue(originalX + expectedChange == singleMG.getChildren().get(0).getLayoutX());
// Assert.assertTrue(originalY + expectedChange == singleMG.getChildren().get(0).getLayoutY());
// }
//
// @Test
// public void correctMovementCorrectIDGate () {
// double originalX1 = gateMG.getChildren().get(0).getLayoutX();
// double originalY1 = gateMG.getChildren().get(0).getLayoutY();
// double originalX2 = gateMG.getChildren().get(1).getLayoutX();
// double originalY2 = gateMG.getChildren().get(1).getLayoutY();
// long timeinterval = 1000/60;
// double expectedChange = 10 / 200 * timeinterval;
// gateMG.setDestination(originalX1 + 10, originalY1 + 10, 0, 1);
// gateMG.setDestination(originalX2 + 10, originalY2 + 10, 0, 2);
// gateMG.updatePosition(timeinterval);
// Assert.assertTrue(originalX1 + expectedChange == gateMG.getChildren().get(0).getLayoutX());
// Assert.assertTrue(originalY1 + expectedChange == gateMG.getChildren().get(0).getLayoutY());
// Assert.assertTrue(originalX2 + expectedChange == gateMG.getChildren().get(1).getLayoutX());
// Assert.assertTrue(originalY2 + expectedChange == gateMG.getChildren().get(1).getLayoutY());
// }
//}
@@ -0,0 +1,44 @@
package seng302.models.mark;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created by Haoming on 17/3/17.
*/
public class MarkTest {
private SingleMark singleMark1;
private SingleMark singleMark2;
private GateMark gateMark;
@Before
public void setUp() throws Exception {
this.singleMark1 = new SingleMark("testMark_SM1", 12.23234, -34.342, 1);
this.singleMark2 = new SingleMark("testMark_SM2", 12.23239, -34.352, 2);
this.gateMark = new GateMark("testMark_GM", MarkType.OPEN_GATE, singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude());
}
@Test
public void getName() throws Exception {
assertEquals("testMark_SM1", this.singleMark1.getName());
assertEquals("testMark_GM", this.gateMark.getName());
}
@Test
public void getMarkType() throws Exception {
assertTrue(this.singleMark2.getMarkType() == MarkType.SINGLE_MARK);
assertTrue(this.gateMark.getMarkType() == MarkType.OPEN_GATE);
}
@Test
public void getMarkContent() throws Exception {
assertEquals(12.23234, this.singleMark1.getLatitude(), 1e-10);
assertEquals(-34.342, this.singleMark1.getLongitude(), 1e-10);
assertEquals("testMark_SM1", this.gateMark.getSingleMark1().getName());
assertEquals(-34.352, this.gateMark.getSingleMark2().getLongitude(), 1e-10);
}
}

Some files were not shown because too many files have changed in this diff Show More