Compare commits

..

171 Commits

Author SHA1 Message Date
Kusal Ekanayake 762829e5ff Merge branch 'develop' 2017-05-25 15:56:10 +12:00
Kusal Ekanayake 835f79b113 Swapped to the local stream as default 2017-05-25 15:56:02 +12:00
Kusal Ekanayake e0854bc68c Merge remote-tracking branch 'origin/master' 2017-05-25 14:55:35 +12:00
Kusal Ekanayake cec7014856 Set axis ons parkline style 2017-05-25 14:54:34 +12:00
Kusal Ekanayake f1a9da83fc Quick fixes before merge 2017-05-25 14:22:01 +12:00
Kusal Ekanayake 6e903bfbed Merge branch 'develop' into 38b_LayLines
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/models/Yacht.java
#	src/main/java/seng302/models/stream/StreamParser.java
2017-05-25 14:16:55 +12:00
Michael Rausch 23d62f552e Merged develop onto this branch
Tidied code, removed print statements

#story[956] #pair[wmu16, mra106]
2017-05-25 14:06:33 +12:00
Kusal Ekanayake 53f6a6b8c5 Implimented a new way to determine positions
#story[952]
2017-05-25 14:06:33 +12:00
Michael Rausch af81bf5891 Merge remote-tracking branch 'origin/develop' into 38b_LayLines
# Conflicts:
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/Event.java
#	src/main/java/seng302/models/mark/Mark.java
#	src/main/java/seng302/models/stream/StreamParser.java
2017-05-25 13:56:08 +12:00
Michael Rausch e72ac1def8 Laylines are now togglable. Still not completely fool proof
Laylines appear only for the selected boat. The dissapear if the boat is not seleccted. Laylines are the colour of the boat

Laylines do not yet automatically update for a selected boat when it passes a mark

There is still a polling interval of a second in which if you select a boat after it has just passed a mark it still thinks the next mark is that mark and laylines are displayed incorrectly

#story[956] #pair[wmu16, mra106]
2017-05-25 13:48:48 +12:00
Calum adbb9ffe3b Removed completed TODO request. 2017-05-25 10:31:37 +12:00
Calum a6d9c66fc9 Removed unused import statements and class variables. Removed non error print statements. 2017-05-25 10:20:39 +12:00
William Muir 99588c7ff8 Laylines now working upon selecting a boat. Still need to make their visibility togglable
Currently just paints upon clicking

tags: #story[956]
2017-05-25 09:54:00 +12:00
William Muir 68c3e3e999 Merged develop onto this branchk
tags: #story[956]
2017-05-25 09:21:47 +12:00
William Muir 67b5650288 Merge branch 'develop' into 38b_LayLines
# Conflicts:
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/Yacht.java
2017-05-25 09:19:42 +12:00
William Muir ffe70a8313 Trying to get laylines to display on proper respective sides of the gate
Having a great time here, Not quite working. There was an issue where the laylines are crossed and I think its tied to the fact that sometimes the two seperate points of the gate mark appear to have the same x y coordinate so the line function doesnt work? idk

tags: #story[956] #pair[wmu16]
2017-05-25 00:22:15 +12:00
Haoming Yin 331e0fc6ab Merge branch 'Story34_Sparklines' into 'develop'
Story34 sparklines



See merge request !38
2017-05-24 22:24:50 +12:00
Kusal Ekanayake b3fd735f5c Merge branch 'develop' into Story34_Sparklines
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
2017-05-24 21:50:15 +12:00
William Muir 3cbbdb070f Basic implementation for mapping windspeed to draw a polar on a gate complete
Created functionality to grab the closest windspeed value to map to VMG values based off the current wind speed in the Polar Table

Created new RaceXML mark object which contains ALL marks for purposes of sequencing

Displaying correct (?) polars for one point only on a gate

Created functionality to receive leg data for each boat and then map that to the next gate. This may only work for the current race due to a slight fudge factor

Created functionality to receive wind speed

tags: #story[956] #pair[wmu16, mra106]
2017-05-24 20:31:07 +12:00
Haoming Yin d2e55bf964 Removed the FPS background, and lowered the update frequency so it doesn't flip too fast.
#story[923]
2017-05-24 19:33:24 +12:00
Kusal Ekanayake 05cdadac79 Merge remote-tracking branch 'origin/Story34_Sparklines' into Story34_Sparklines 2017-05-24 18:57:09 +12:00
Kusal Ekanayake e6aed88188 Merge with develop.
#story[952]
2017-05-24 18:56:56 +12:00
Kusal Ekanayake d032314ddb Merge branch 'develop' into Story34_Sparklines
# Conflicts:
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/Yacht.java
2017-05-24 18:56:04 +12:00
Peter Galloway 6301fd2fb7 Merge branch 'issue#5_fix_pre-race_boats_on_leaderboard' into 'develop'
Issue#5 fix pre race boats on leaderboard

Updated leaderboard so it only shows competing boats and pre-race no longer shows only one boat

See merge request !36
2017-05-24 18:52:37 +12:00
Kusal Ekanayake 34c3899ec4 Removed unused css for charts 2017-05-24 18:51:52 +12:00
Kusal Ekanayake 46f5fc5172 Merge with develop.
#story[952]
2017-05-24 18:47:27 +12:00
Kusal Ekanayake 81c021b59a Merge branch 'develop' into Story34_Sparklines
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/resources/views/RaceView.fxml
2017-05-24 18:45:01 +12:00
Kusal Ekanayake 8ab57e4e61 Cleaned up code for review.
#story[952]
2017-05-24 18:43:50 +12:00
Zhi You Tan 13ff179840 Merge remote-tracking branch 'origin/develop' into issue#5_fix_pre-race_boats_on_leaderboard 2017-05-24 16:18:33 +12:00
Michael Rausch 76c0d34760 Merge branch '37-display_map_on_canvas' into 'develop'
37 display map on canvas

Merge request for story #928

See merge request !37
2017-05-24 15:05:39 +12:00
Michael Rausch 71637d7286 Removed dead code from canvas controller and canvas map
Tags: #story[928]
2017-05-24 15:04:12 +12:00
William Muir 1cac7cc189 Calculation of upwind downwind leg given a boat, wind and next mark now works. created GeometryUtils class
Can accurately calculate if a boat is going upwind or downward using a line function for the wind vector from the gate and the boat position from the gate.

Requires knowledge of the next mark which requires the boat to have passed a mark. This could be fixed by extracting the leg number from the race status packet and mapping these to gates in an initalisation step

tags: #story[956]
2017-05-24 14:57:22 +12:00
Haoming Yin c42942430f Fixed bug that grid panel pushes annotation panel up out of window.
#story[928]
2017-05-24 14:57:06 +12:00
Kusal Ekanayake 7abb36c362 The axis is labeled correctly and is scaled accordingly.
The sparkline should work from the start of a race now also. We now have the boat in first place listed at the top along with the axis labeling and marking matching the position (eg, 1st at top. 6th at bottom). Boats picked up on the visualiser park way through the race are able to added and drawn onto the sparkline.

#story[952]
2017-05-24 14:42:41 +12:00
Haoming Yin 3fd0c0a2dd Merge remote-tracking branch 'origin/37-display_map_on_canvas' into 37-display_map_on_canvas
# Conflicts:
#	src/main/java/seng302/App.java
2017-05-24 13:55:42 +12:00
Haoming Yin 397f7d003a Fixed the size of race canvas and race view so that canvas won't be stretched
- canvas view is set to 1280 * 960

#story[928]
2017-05-24 13:54:05 +12:00
Haoming Yin f85d3bf5fe Plugged Canvas map to canvas view controller to display map
- rebase on the latest develop status
- optimised the scaling factor the map to fit the canvas view
- a new image containing map image is displayed under race canvas

#story[928]
2017-05-24 13:52:49 +12:00
Haoming Yin 8f93956ff1 Added methods to calculate optimal map size given a geo boundary.
- From zoom level 20 to 1, once find a size that contains the whole boundary, then the size will be used to retrieve map image from google

#story[928]
2017-05-24 00:49:03 +12:00
Haoming Yin 4fe4ac1079 Added get map size (width and height) method in canvasMap with given boundary
#story[928]
2017-05-24 00:49:03 +12:00
Haoming Yin 8a2f0a9f45 Added unit tests for Mercator projection class.
- changed its methods to static
- add some documentation for its methods

#story[928]
2017-05-24 00:49:03 +12:00
Haoming Yin 5cc865f0af Created Mercator projection to convert between Geo location and planar projection point.
- MapGeo and MapPoint encapsulate geo location and planar projection point into classes.

#story[928]
2017-05-24 00:49:03 +12:00
Haoming Yin 189ba93e64 Created a canvas map class to fetch map image from google
- also added Bound class to encapsulate map boundary.
- created TestMapView and its controller just for testing.

#story[928]
2017-05-24 00:49:03 +12:00
William Muir 89464e033e Initial work on calculating when a gate is upwind or downwind
Need to know if a gate is upwind or downwind to use the appropriate polar table

Currently calculate the angle between the next mark and the vector of the current mark to the wind, if this angle is less than 90 degrees than the next mark should be down wind

Pretty poor implementation currently, just prototype

Doesn't appear to be working as intended currently. Just a prototype for how we could implement further

tags: #story[956]
2017-05-24 00:08:55 +12:00
Zhi You Tan 6d7c36e31f Updated code to check if boat is in the race before displaying in the leaderboard
#story[923]
2017-05-23 20:00:23 +12:00
Michael Rausch ca8ea03870 Added method in mark group to draw lay lines, also added next mark to Yacht class.
#story[956]
2017-05-23 18:54:42 +12:00
Kusal Ekanayake 3f57adb9cf Merge branch 'develop' into Story34_Sparklines
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/resources/views/RaceView.fxml
2017-05-23 14:06:24 +12:00
Zhi You Tan 2686dac62e Fixed Yacht position initialise. TO-DO: check if the boat is participating before showing up on leaderboard. 2017-05-23 11:53:23 +12:00
Zhi You Tan ffd40fef6d Merge remote-tracking branch 'origin/develop' into issue#5_fix_pre-race_boats_on_leaderboard
# Conflicts:
#	src/main/java/seng302/controllers/RaceController.java
#	src/main/java/seng302/controllers/StartScreenController.java
2017-05-23 11:31:18 +12:00
William Muir e1b8e19966 Initial work on finding next marks for boats as from mark rounding messages
Marks were very difficult to extend, need large refactor

Marks now have a compound ID as well as their sourceID. They need this compound ID to be identifiable by the
mark rounding messages.

tags: #story[956]  #pair[wmu16, mra106]
2017-05-22 18:54:07 +12:00
William Muir dec742cf54 Merge remote-tracking branch 'origin/develop' into 38b_LayLines 2017-05-22 18:15:58 +12:00
Zhi You Tan 6f1b0b06c3 Fixed est time to next mark and time from last mark annotation update after commit e0750f53 in BoatGroup.java
#story[924] #story[927]
2017-05-22 16:17:52 +12:00
Michael Rausch 408d70c420 Merge branch 'issue#10_unifying_marks' into 'develop'
Issue#10 unifying marks

Marks work properly, reading boat position packets and moving.

First and last marks are colored green and red respectively.

See merge request !35
2017-05-22 16:07:50 +12:00
Alistair McIntyre e51c966969 Made sure only boats in participant list are replaced. Also removed mark coloring as it wasn't as accurate as was necessary.
#story[923]
2017-05-22 16:05:24 +12:00
William Muir 08eacacfd4 Polar table is now parsed form the stored file in resources on startup in main
story[956]
2017-05-22 15:45:16 +12:00
William Muir 87150b3c72 Merge remote-tracking branch 'origin/develop' into 38b_LayLines 2017-05-22 15:40:11 +12:00
William Muir e385ac5c09 Initial work on static parser class for polar files
story[956]
2017-05-22 15:39:53 +12:00
Alistair McIntyre c30629542b Merged develop into issue10 branch. Fixed merge issues and completed manual testing before merge request.
#story[923]
2017-05-22 15:27:23 +12:00
Alistair McIntyre 3f9fa24c69 Merge branch 'develop' into issue#10_unifying_marks
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/controllers/CanvasController.java
2017-05-22 15:13:48 +12:00
Alistair McIntyre 78573fa837 Made start/finish lines a different color.
#story[923]
2017-05-22 15:10:05 +12:00
Michael Rausch d4837cacda Merge branch 'Issue#4_boat_movement' into 'develop'
Issue#4 boat movement

This merge will break several things:
1 boat wakes and trails will be the most effective and will have to be manually re-implemented in the re-engineered boatgroup
2 boat annotations in Raceviewcontroller have been effected
3 boat selection (on click) in Raceviewcontroller has been effected

See merge request !34
2017-05-22 15:03:11 +12:00
Peter Galloway 0367805f0f fixed bug from merge where est time to next mark was null on some boats #story[923] 2017-05-22 15:01:04 +12:00
Peter Galloway e0750f5341 Merge branch 'develop' into Issue#4_boat_movement
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/Race.java
2017-05-22 14:56:08 +12:00
Calum 80528c9c42 fixed bug in canvas controller
#bug
2017-05-22 14:30:31 +12:00
alistairjmcintyre be633c0e60 Marks display correctly on the canvas, no double ups or anything like that left.
#[issue10]
2017-05-22 13:22:55 +12:00
cir27 97f1ccb6c1 Fixed a bug where an error was caused when attempting to flip the race around the prime meridian.
All x values are now flipped horizontally if the difference between the smallest lon, e.g. -179.5 and the largest e.g. 179.5 is greater than 180.

#story[923] #bug
2017-05-22 05:41:57 +12:00
cir27 68a243725b Fixed a bug where an error was caused when attempting to flip the race around the prime meridian.
All x values are now flipped horizontally if the difference between the smallest lon, e.g. -179.5 and the largest e.g. 179.5 is greater than 180.

#story[923] #bug
2017-05-22 05:39:36 +12:00
Kusal Ekanayake 8f81060a18 Managed to make the sparklines coordinate with their colour.
However the symbols had to be removed. The sparkline can also be intialised at any time now but this might need to be tested further to ensure that it works.

#story[952]
2017-05-21 19:49:11 +12:00
Kusal Ekanayake 07c76f12e1 Merge branch 'develop' into Story34_Sparklines 2017-05-21 17:37:41 +12:00
cir27 c6ab96a86f Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop 2017-05-21 16:05:37 +12:00
Kusal Ekanayake 059c0de1fa Quickfix for null pointer in boatObject
#pair[ptg19,kre39]
2017-05-21 15:58:17 +12:00
Kusal Ekanayake 8e147bd1bd Making the boat sparkline initialise after the race screen is shown.
#story[952]
2017-05-21 14:56:05 +12:00
Michael Rausch 4d3cfe71f7 Merge branch 'develop' into Issue#4_boat_movement
Conflicts:
	src/main/java/seng302/models/BoatGroup.java
	src/main/java/seng302/models/Wake.java
2017-05-19 21:40:16 +12:00
Michael Rausch 5adb7c3762 Fixed failing ColorsTest
#story[923]
2017-05-19 20:57:40 +12:00
Michael Rausch 937b309b07 Added boat trails to the boat group, fixed annotations
- Set colours for the annotations
- Added boat trails back into the boat group
- Re-added time until next mark and time since last mark rounding

#story[923]
2017-05-19 20:55:02 +12:00
Kusal Ekanayake 48d58ea660 Sparklines now update when a boat passes a mark.
There are issue of initialising the sparkline before the race starts and when the race is being viewed when it starts. It works great though when you join a race halfway.
#story[952]
2017-05-19 18:08:26 +12:00
Michael Rausch aaf2e6a3f0 Added boat wakes back to the visualiser
#story[923]
2017-05-19 16:57:29 +12:00
Kusal Ekanayake 422dcd4501 Sparklines now update with boat position change.
Need to make it work for any amount of boats in the race and to work when the race first starts as it currently only shows one boat.
#story[952]
2017-05-19 15:15:05 +12:00
Kusal Ekanayake 951a726309 The line chart now has series which display on it.
Need to get the right data to display on the linechart when it updates.

#story[952]
2017-05-18 20:56:42 +12:00
Alistair McIntyre b692ddcbe6 Marks and gates correctly displaying from XML messages and mark(boat) position packets. Will need a little polishing but marks and gates are moving around.
#story[923] #pair[ajm412, ptg19]
2017-05-18 18:39:23 +12:00
Alistair McIntyre 5d6b356602 all marks displaying except currently gate marks are displaying as a single dot
#story[923] #pair[ajm412, ptg19]
2017-05-18 18:02:25 +12:00
Alistair McIntyre 08057edb28 Merge branch 'Issue#4_boat_movement' into issue#10_unifying_marks
# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/mark/MarkGroup.java
2017-05-18 17:04:00 +12:00
Kusal Ekanayake 390aabc78f Adding race yacht series to the sparkline.
Next step is to make the series of the boats update when new position information is received.

#story[952]
2017-05-18 14:44:10 +12:00
William Muir a2123df0c5 Fixed Boat selection and lost annotations (Estimated and Leg Time)
Trails and Wakes, however,  are still not re implemented yet.

story[923]
2017-05-18 14:41:08 +12:00
Kusal Ekanayake b87008f590 Merge branch 'develop' into Story34_Sparklines 2017-05-18 13:22:51 +12:00
Kusal Ekanayake ecc0e722b5 Started sparkline prototype
#story[952]
2017-05-18 13:19:59 +12:00
Zhi You Tan 6f9a8e5581 Merge remote-tracking branch 'origin/develop' into issue#5_fix_pre-race_boats_on_leaderboard 2017-05-18 12:20:02 +12:00
Zhi You Tan b17bba3629 Fixed leaderboard on start screen and race view to show all boats correctly during pre-race.
#story[923]
2017-05-18 12:18:51 +12:00
Michael Rausch cf4d7e03f5 Merge branch 'issue#1_wakes_3.0' into 'develop'
Issue#1 wakes 3.0

Changed methods in the wake class to fix issues caused by unexpected velocities making the wakes go crazy.
Improved performance.
Made wakes look different.

See merge request !33
2017-05-18 11:45:28 +12:00
Michael Rausch 73eeeb0ef9 Removed extra print stmt and changed default server back to official 2017-05-18 11:45:06 +12:00
Peter Galloway 0f79353936 fixes from merge #story[923] 2017-05-17 20:15:47 +12:00
Peter Galloway 38b44fa92b Merge branch 'develop' into Issue#4_boat_movement
# Conflicts:
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/controllers/RaceController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/mark/MarkGroup.java
#	src/main/java/seng302/models/stream/StreamParser.java
#	src/test/java/seng302/models/stream/StreamReceiverTest.java
2017-05-17 20:05:40 +12:00
alistairjmcintyre 3fd13ddc0a Adjusted XMLParser to use model Mark objects rather than the simple datatype that existed in the XMLParser previously. Began attempting to implement them into the canvas controller but have issues
#[issue10]
2017-05-17 19:46:05 +12:00
Peter Galloway 2e375978bd cleaned up code for merging back to develop #story[923] 2017-05-17 19:18:31 +12:00
Calum 45db731a60 Fixed a merge issue.
#story[923] #bug #refactor
2017-05-17 18:06:36 +12:00
Peter Galloway 95e353c14e fixed initialization bug #story[923] 2017-05-17 17:55:21 +12:00
Calum 8a3a41294a Fixed error causing build failure.
#story[923] #bug #refactor
2017-05-17 17:41:45 +12:00
Calum f41858e2c7 Tidied variable names. 2017-05-17 17:35:59 +12:00
Peter Galloway aaa3dc93f1 boat movement working reliably now, still need to fix one bug at initialization #story[923] 2017-05-17 17:34:47 +12:00
Calum e5eab0a6c8 Merge remote-tracking branch 'origin/develop' into issue#1_wakes_3.0
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/models/BoatGroup.java
2017-05-17 17:27:34 +12:00
Calum 7c39368126 Removed unnecessary constant 2017-05-17 17:23:10 +12:00
Calum ade926e2f2 StreamParser class now only shares thread safe classes.
#story[923] #bug
2017-05-17 16:44:05 +12:00
Calum c63c8e4d73 Stripped out excess code in Wake class. 2017-05-17 16:33:45 +12:00
Michael Rausch 3c418b2aa4 Changed boat location message to milliseconds 2017-05-17 14:57:31 +12:00
Michael Rausch d34a158c34 Merge branch '38a_Select_Boats' into 'develop'
38a select boats

Selection of boats now possible by selecting a boat from a drop down menu to focus that boat singularly or clicking a boat on the canvas to toggle that boats annotations.

Changed the annotations slider to remove the low annotation option. Only now includes None, Important and All. I think this makes much more sense.

Refactor of the race view controller class to combine all timelines for polling the Stream Parser to update race view data such as tables into one timeline for polling. In the future this should be improved further from polling to an observer model.

See merge request !31
2017-05-17 14:38:23 +12:00
Michael Rausch 6e3d037021 Merge branch 'develop' into 38a_Select_Boats 2017-05-17 14:34:10 +12:00
Zhi You Tan 7d160eaf54 Removed unused imports from controller.java 2017-05-17 13:16:00 +12:00
Peter Galloway 4fc99edbd6 Merge branch 'issue#14_split_start_screen_view&controller' into 'develop'
Split Start Screen out from Main View view and controller.

The start screen has its own view and controller now. Returned main view to its original function.

(This solves issue#14)

See merge request !32
2017-05-17 13:07:48 +12:00
Zhi You Tan 4da8c1645e Split Start Screen out from Main View view and controller.
#story[923]
2017-05-17 12:56:13 +12:00
William Muir 1c01aab1e7 Merge on to develop
story[955]
2017-05-17 10:34:00 +12:00
William Muir 6a85b0800f Merge on to develop
story[955]
2017-05-17 10:06:50 +12:00
William Muir 1acb0fbac4 Merge on to develop
story[955]
2017-05-17 10:04:28 +12:00
William Muir 2b294702a9 Merge remote-tracking branch 'origin/develop' into 38a_Select_Boats
# Conflicts:
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/models/BoatGroup.java
2017-05-17 09:53:18 +12:00
William Muir afd97d6e05 Reformating of code to google style.
#story[955]
2017-05-17 00:34:36 +12:00
William Muir 2d5a7a8a49 Large tidying of RaceViewController class. Fixing updating for combo boxes
#story[955]
2017-05-16 15:06:01 +12:00
Peter Galloway 9e3036e134 Stripped down the boatgroup to the basic movement components and then tried to adjust how position updates are being dealt with to make everything more logically understandable. I made some progress in terms of understanding but the position update is still not as reliable as I would like. I will be explaining to other team members how this part of the code is working so the time I have spent is not completely wasted #story[923] 2017-05-15 23:17:36 +12:00
Haoming Yin 8dec458ba9 Added methods to calculate optimal map size given a geo boundary.
- From zoom level 20 to 1, once find a size that contains the whole boundary, then the size will be used to retrieve map image from google

#story[928]
2017-05-15 19:57:23 +12:00
Michael Rausch da07d885da Merge branch 'Story47CourseLimits' into 'develop'
Story47 course limits

The idea was to have the course canvas update whenever we receive a new xml packet for the race data. Specifically for a few seconds after the race where the course boundaries change, going from a boundary which contains the start line to cutting off the start line as soon as the boats start.

See merge request !30
2017-05-15 18:43:49 +12:00
Michael Rausch a9de005e1a Removed a extraneous print stmt 2017-05-15 18:41:44 +12:00
Michael Rausch e03e121da4 Merge branch 'develop' into Story47CourseLimits
Conflicts:
	src/main/java/seng302/App.java
2017-05-15 18:37:31 +12:00
Peter Galloway fc3ca70e5d Merge branch 'issue#10_unifying_marks' into Issue#4_boat_movement
# Conflicts:
#	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/stream/StreamPacket.java
#	src/main/java/seng302/models/stream/StreamParser.java
2017-05-15 18:09:47 +12:00
Kusal Ekanayake 9c7144c918 Removed dud print statement
#story[889]
2017-05-15 17:55:06 +12:00
Peter Galloway ed8d70c3b3 commented out buffered code and cleaned up some other areas to try and make the boatgroup clear enough to modify #story[923] 2017-05-15 17:53:11 +12:00
Kusal Ekanayake fa501460cb Cleaned up code for merge.
#story[889]
2017-05-15 17:21:56 +12:00
Kusal Ekanayake 110143ae6e Added a dynamically updating course limits file to the model.
This will make manual testing of the course limits easier. When the race starts, the limits encapsulate the start line but soon after the race starts the course limits close off around the start line (this is for the model). This means the model acts in a very similar way to the actual live stream.

#story[889]
2017-05-15 17:09:12 +12:00
Haoming Yin eda3d76077 Added get map size (width and height) method in canvasMap with given boundary
#story[928]
2017-05-15 17:06:28 +12:00
William Muir 51f090324a CSS on annotation selection and minor bug fixes. Changed annotation slider to only have None, important and All
#story[955]
2017-05-15 16:44:28 +12:00
Michael Rausch 335540ff4a Merge branch 'story36_elapsed_time' into 'develop'
Story36 elapsed time

Implemented time elapsed since last mark on annotation

See merge request !29
2017-05-15 16:33:21 +12:00
Zhi You Tan 5fa47ff65b Implemented elapsed time since last mark on annotation.
#story[927]
2017-05-15 16:31:04 +12:00
Zhi You Tan 3a1c1a5e43 Merge remote-tracking branch 'origin/develop' into story36_elapsed_time
# Conflicts:
#	src/main/java/seng302/models/BoatGroup.java
2017-05-15 15:54:44 +12:00
Michael Rausch 4c7f530458 Merge branch 'story33_add_estimated_time_annotation' into 'develop'
Story33 add estimated time annotation

Added estimated time to next mark to annotation

See merge request !28
2017-05-15 15:50:46 +12:00
Zhi You Tan 2e914a7704 Fixed estimate time to next mark to match acceptance criteria which is countdown in minutes and seconds.
#story[924]
2017-05-15 15:49:21 +12:00
Calum 8fbb9d6d4e Added/improved documentation
#chore
2017-05-15 15:36:18 +12:00
Calum 23bc643c91 Removed some unused functions and imports caused by code refactor.
#chore
2017-05-15 15:20:21 +12:00
Calum c4fe116267 MarkGroups refactored to be independent of BoatGroups as their functionality has diverged.
#issue[10] #refactor
2017-05-15 15:17:54 +12:00
Kusal Ekanayake 081d7e3dcb Fixed the parsing of bytes being off by one byte in the stream parser fr the race status packet.
#story[924] #pair[kre39,zyt10]
2017-05-15 14:52:57 +12:00
William Muir 764ae37ce4 Gave the boatgroups a selection attribute, allowing them to be highlighted upon clicking
Boats can be clicked on canvas or from selection drop down on the side

#story[955]
2017-05-15 14:09:09 +12:00
Zhi You Tan e62a609b6b Prepared StreamParser.java, BoatGroup.java, Yacht.java for leg timer annotation (story 36)
#story[927]
2017-05-15 13:47:30 +12:00
Haoming Yin 4b1a4aae87 Added unit tests for Mercator projection class.
- changed its methods to static
- add some documentation for its methods

#story[928]
2017-05-15 13:23:04 +12:00
Peter Galloway ccda5f2a2e changing branch name #story[923] 2017-05-15 12:40:11 +12:00
Haoming Yin 3fd8b1b855 Created Mercator projection to convert between Geo location and planar projection point.
- MapGeo and MapPoint encapsulate geo location and planar projection point into classes.

#story[928]
2017-05-15 12:24:36 +12:00
Kusal Ekanayake 94d1982670 Shifted the canvas to the back of the window when updated.
This is to preserve the way the boats are still shown on the front when the course i reloaded.
As the main live stream has been down, this still needs to be tested.
2017-05-15 10:46:28 +12:00
Zhi You Tan 39efafc75f Fixed Annotation.java, ImportantAnnotationController.java, RaceViewController.java to include estimate time to next mark after merge
#story[924]
2017-05-15 10:36:20 +12:00
Kusal Ekanayake afe0c9f1a6 Merge branch 'develop' into Story47CourseLimits
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-05-15 10:33:22 +12:00
Zhi You Tan 04b105d74b Merge branch 'develop' into story33_add_estimated_time_annotation
# Conflicts:
#	src/main/java/seng302/controllers/RaceViewController.java
2017-05-15 10:19:58 +12:00
Zhi You Tan 1ab6351d48 Fixed estimated time to next mark (annotation) to update correctly
#story[924]
2017-05-15 10:11:19 +12:00
Michael Rausch 9c348df5a5 Merge branch 'merge_request_test' into 'master'
Added .codeclimate.yml for static code analysis



See merge request !26
2017-05-15 01:28:57 +12:00
Peter Galloway 256ec046fc adjusted givePointsXY to try and use the marks from the XML parser, stopped partway as the marks from the XML parser and the standard marks need to be merged before this can be fixed properly #story[923] 2017-05-14 22:12:16 +12:00
Peter Galloway 85d4d63287 Merge branch 'story35_create_annotation_view' into 'develop'
Story35 create annotation view

## Addresses issue #9 and story 35

# Change Log
* Styled application using CSS (Issue #9)
* User can select to only show important annotations using the annotation slider
* Added view to select important annotations
* Important annotations are updated in real-time

# Testing
* Unit tests in test/java/seng302/visualizer/annotations/*
* Controllers have been tested manually

# Acceptance Criteria

* There is a mechanism to chose the ‘important’ annotations while viewing a race.

* Any number of annotations (from zero to the number of annotations) can be chosen.

* If the partial annotations toggle is active, choosing an annotation makes that annotation immediately visible for all boats, and deselecting an annotation should likewise remove it from all boats.

* When the partial annotations toggle becomes active, all annotations chosen as ‘important’, and only those annotations, should be shown.

See merge request !27
2017-05-14 22:00:02 +12:00
Michael Rausch 213d36ed56 Merge remote-tracking branch 'origin/develop' into story35_create_annotation_view 2017-05-14 21:05:48 +12:00
Michael Rausch ff6bfc9516 Important annotations are displayed correctly when updated.
- Important annotations can be selected, the live view updates in real time
- Change local time text colour to white
- Split the important annotations state into a separate class

Tags: #story[926]
2017-05-14 20:27:25 +12:00
Peter Galloway ae5678482b changed package "parsers" to be called "stream" #story[923] 2017-05-14 20:26:49 +12:00
Peter Galloway 63514cfafb deleted duplicated packet type class #story[923] 2017-05-14 20:20:14 +12:00
Peter Galloway 03ca60f2e1 deleted a whole bunch of legacy code, primarily the old controllers and old parsers #story[923] 2017-05-14 20:12:35 +12:00
Calum 89ef6e5277 Wake calculation now changed to be based off of the separation between wakes.
This allows wakes to auto correct their position better and stops the system reliance on "realistic data".
Wakes have several options for behaviour until the ideal settings are decided upon.
Note that MarkGroup position updating is currently disabled.

#implement #refactor #issue[1] #story[923]
2017-05-14 17:24:15 +12:00
Zhi You Tan 47880d09bc Implemented estimated time to next mark. Added checkbox for estimated time to next mark. To be fix: change from long to human readable time, update time every second
#story[923]
2017-05-12 20:04:09 +12:00
Zhi You Tan 5472765b95 Removed unused methods in RaceViewController.java
#story[923]
2017-05-12 18:24:28 +12:00
Zhi You Tan 14d975dce4 Fixed a bug where clicking "watch race" before race starts will cause boats to travel in unexpected ways.
#story[923]
2017-05-12 17:41:47 +12:00
Michael Rausch a23bdd0c53 Added functionality to select important annotations and ui changes
- Added CSS for the race view & welcome screen
- Added view to select important annotations
- Annotations are enabled/disabled when the user changes the important annotations

Tags: #story[926]
2017-05-11 21:44:56 +12:00
cir27 b0d8c3db0a Merge remote-tracking branch 'origin/develop' into develop 2017-05-11 16:40:50 +12:00
Kusal Ekanayake 4a75c062ce Making the course limits change when a new xml packet is received..
#story[889]
2017-05-11 15:49:49 +12:00
Michael Rausch fe90a3bf13 Merge branch 'master' into story35_create_annotation_view 2017-05-11 13:43:02 +12:00
Michael Rausch 711f6f4c45 Merge branch 'master' into develop 2017-05-11 13:29:51 +12:00
Haoming Yin 8fa7829a3c Created a canvas map class to fetch map image from google
- also added Bound class to encapsulate map boundary.
- created TestMapView and its controller just for testing.

#story[928]
2017-05-10 20:52:46 +12:00
Peter Galloway 6d7697a0eb commented out the problematic buffering functionality #story[923] 2017-05-10 13:58:10 +12:00
Michael Rausch cdd80af27b Added .codeclimate.yml for static code analysis 2017-05-10 11:56:18 +12:00
Zhi You Tan 2cb09b81f8 Updated .mailmap for everyone 2017-05-04 16:53:16 +12:00
Peter Galloway b529d621e9 changed course size to 720x720 2017-05-04 16:42:23 +12:00
Zhi You Tan ac3f3bfd55 Added a check if race started, then start screen switches to race view automatically.
#story[572]
2017-05-04 14:32:06 +12:00
cir27 3a72409fb8 Merge remote-tracking branch 'origin/develop' into develop 2017-05-02 21:50:30 +12:00
cir27 fd092bb7e1 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	src/main/java/seng302/App.java
2017-05-02 16:41:49 +12:00
cir27 9c60521d00 Merge branch 'develop' of C:\Users\CJIRWIN\Documents\team-13 with conflicts. 2017-05-02 16:41:40 +12:00
74 changed files with 3892 additions and 3383 deletions
+17
View File
@@ -0,0 +1,17 @@
engines:
pmd:
enabled: true
channel: "beta"
fixme:
enabled: true
config:
strings:
- FIXME
- TODO
- BUG
- FIX
ratings:
paths:
- "**.java"
+5 -3
View File
@@ -16,10 +16,12 @@
# 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@michaelrausch.net> Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz>
Michael Rausch <mra106@uclive.ac.nz> <michael@michaelrausch.net>
Kusal Ekanayake <kre39@uclive.ac.nz> kre39 <kre39@uclive.ac.nz> Kusal Ekanayake <kre39@uclive.ac.nz> kre39 <kre39@uclive.ac.nz>
Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com> Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz> Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz>
Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz> Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz>
Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com>
Alistair McIntyre <ajm412@uclive.ac.nz> alistairjmcintyre <alistairjmcintyre@gmail.com>
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
+16 -12
View File
@@ -5,27 +5,33 @@ import javafx.fxml.FXMLLoader;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import seng302.models.parsers.StreamParser; import seng302.models.PolarTable;
import seng302.models.parsers.StreamReceiver; import seng302.models.stream.StreamParser;
import seng302.models.stream.StreamReceiver;
import seng302.server.ServerThread; import seng302.server.ServerThread;
public class App extends Application public class App extends Application {
{
@Override @Override
public void start(Stage primaryStage) throws Exception { public void start(Stage primaryStage) throws Exception {
PolarTable.parsePolarFile(getClass().getResource("/config/acc_polars.csv").getFile());
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml")); Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
primaryStage.setTitle("RaceVision"); primaryStage.setTitle("RaceVision");
primaryStage.setScene(new Scene(root)); primaryStage.setScene(new Scene(root, 1530, 960));
primaryStage.setMaximized(true); primaryStage.setMaxWidth(1530);
primaryStage.setMaxHeight(960);
// primaryStage.setMaximized(true);
primaryStage.show(); primaryStage.show();
primaryStage.setOnCloseRequest(e -> { primaryStage.setOnCloseRequest(e -> {
StreamParser.appClose(); StreamParser.appClose();
StreamReceiver.noMoreBytes(); StreamReceiver.noMoreBytes();
System.out.println("[CLIENT] Exiting program");
System.exit(0); System.exit(0);
}); });
} }
public static void main(String[] args) { public static void main(String[] args) {
@@ -39,15 +45,15 @@ public class App extends Application
e.printStackTrace(); e.printStackTrace();
} }
if (args.length == 1 && args[0].equals("-standalone")){ if (args.length == 1 && args[0].equals("-standalone")) {
return; return;
} }
if (args.length == 3 && args[0].equals("-server")){ if (args.length == 3 && args[0].equals("-server")) {
sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream"); sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream");
} else if(args.length == 2 && args[0].equals("-server")){ } else if (args.length == 2 && args[0].equals("-server")) {
switch (args[1]) { switch (args[1]) {
case "internal": case "internal":
sr = new StreamReceiver("localhost", 4949, "RaceStream"); sr = new StreamReceiver("localhost", 4949, "RaceStream");
@@ -71,8 +77,6 @@ public class App extends Application
launch(args); launch(args);
} }
} }
+63
View File
@@ -0,0 +1,63 @@
package seng302;
import javafx.geometry.Point2D;
/**
* A Class for performing geometric calculations on the canvas
* Created by wmu16 on 24/05/17.
*/
public final class GeometryUtils {
/**
* Performs the line function on two points of a line and a test point to test which side of the line that point is
* on. If the return value is
* return 1, then the point is on one side of the line,
* return -1 then the point is on the other side of the line
* return 0 then the point is exactly on the line.
* @param linePoint1 One point of the line
* @param linePoint2 Second point of the line
* @param testPoint The point to test with this line
* @return A return value indicating which side of the line the point is on
*/
public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) {
Double x = testPoint.getX();
Double y = testPoint.getY();
Double x1 = linePoint1.getX();
Double y1 = linePoint1.getY();
Double x2 = linePoint2.getX();
Double y2 = linePoint2.getY();
Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function
if (result > 0) {
return 1;
}
else if (result < 0) {
return -1;
}
else {
return 0;
}
}
/**
* Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin
* point
* @param originPoint The point with which to use as the base for our vector addition
* @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!)
* @param vectorLength The length out on this angle from the origin point to create the new point
* @return a Point2D
*/
public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) {
Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg));
Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg));
return new Point2D(endPointX, endPointY);
}
}
@@ -1,31 +1,34 @@
package seng302.controllers; package seng302.controllers;
import javafx.animation.*; import javafx.animation.AnimationTimer;
import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleDoubleProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.Group; import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext; import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Font; import javafx.scene.text.Font;
import javafx.stage.Stage; import seng302.models.BoatGroup;
import seng302.models.*; import seng302.models.Colors;
import seng302.models.Yacht;
import seng302.models.map.Boundary;
import seng302.models.map.CanvasMap;
import seng302.models.mark.*; import seng302.models.mark.*;
import seng302.models.parsers.StreamParser; import seng302.models.stream.StreamParser;
import seng302.models.parsers.StreamReceiver; import seng302.models.stream.XMLParser;
import seng302.models.parsers.packets.BoatPositionPacket; import seng302.models.stream.XMLParser.RaceXMLObject.Limit;
import seng302.models.parsers.XMLParser; import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
import seng302.models.parsers.XMLParser.RaceXMLObject.CompoundMark; import seng302.models.stream.packets.BoatPositionPacket;
import seng302.models.parsers.XMLParser.RaceXMLObject.Limit; import seng302.server.simulator.GeoUtility;
import seng302.models.mark.Mark; import seng302.server.simulator.mark.Position;
import java.text.DecimalFormat; import java.util.ArrayList;
import java.util.*; import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
/** /**
@@ -41,15 +44,14 @@ public class CanvasController {
private ResizableCanvas canvas; private ResizableCanvas canvas;
private Group group; private Group group;
private GraphicsContext gc; private GraphicsContext gc;
private ImageView mapImage;
private final int MARK_SIZE = 10;
private final int BUFFER_SIZE = 50; private final int BUFFER_SIZE = 50;
private final int CANVAS_WIDTH = 1000; private final int PANEL_WIDTH = 1260; // it should be 1280 but, minors 40 to cancel the bias.
private final int CANVAS_HEIGHT = 1000; private final int PANEL_HEIGHT = 960;
private final int LHS_BUFFER = BUFFER_SIZE; private final int CANVAS_WIDTH = 720;
private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2; private final int CANVAS_HEIGHT = 720;
private final int TOP_BUFFER = BUFFER_SIZE; private boolean horizontalInversion = false;
private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2;
private double distanceScaleFactor; private double distanceScaleFactor;
private ScaleDirection scaleDirection; private ScaleDirection scaleDirection;
@@ -59,16 +61,17 @@ public class CanvasController {
private Mark maxLonPoint; private Mark maxLonPoint;
private double referencePointX; private double referencePointX;
private double referencePointY; private double referencePointY;
private double metersToPixels; private double metersPerPixelX;
private List<RaceObject> raceObjects = new ArrayList<>(); private double metersPerPixelY;
private List<Mark> raceMarks = new ArrayList<>();
private List<MarkGroup> markGroups = new ArrayList<>();
private List<BoatGroup> boatGroups = new ArrayList<>();
//FRAME RATE //FRAME RATE
private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps private Double frameRate = 60.0;
private final long[] frameTimes = new long[30]; private final long[] frameTimes = new long[30];
private int frameTimeIndex = 0; private int frameTimeIndex = 0;
private boolean arrayFilled = false; private boolean arrayFilled = false;
private DecimalFormat decimalFormat2dp = new DecimalFormat("0.00");
public AnimationTimer timer; public AnimationTimer timer;
@@ -86,29 +89,33 @@ public class CanvasController {
canvas = new ResizableCanvas(); canvas = new ResizableCanvas();
group = new Group(); group = new Group();
// create image view for map, bind panel size to image
mapImage = new ImageView();
canvasPane.getChildren().add(mapImage);
mapImage.fitWidthProperty().bind(canvasPane.widthProperty());
mapImage.fitHeightProperty().bind(canvasPane.heightProperty());
canvasPane.getChildren().add(canvas); canvasPane.getChildren().add(canvas);
canvasPane.getChildren().add(group); canvasPane.getChildren().add(group);
// Bind canvas size to stack pane size. // Bind canvas size to stack pane size.
canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH)); canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH));
canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT)); canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT));
//group.minWidth(CANVAS_WIDTH);
//group.minHeight(CANVAS_HEIGHT);
} }
public void initializeCanvas (){ public void initializeCanvas (){
gc = canvas.getGraphicsContext2D(); gc = canvas.getGraphicsContext2D();
gc.save(); gc.setGlobalAlpha(0.5);
gc.setFill(Color.SKYBLUE);
gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT);
gc.restore();
fitMarksToCanvas(); fitMarksToCanvas();
drawGoogleMap();
// TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml // TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml
drawBoats(); initializeBoats();
initializeMarks();
timer = new AnimationTimer() { timer = new AnimationTimer() {
private int UPDATE_FPM_PERIOD = 50; // update FPM label every 50 frames
private int updateFPMCounter = 100;
@Override @Override
public void handle(long now) { public void handle(long now) {
@@ -123,13 +130,14 @@ public class CanvasController {
if (arrayFilled) { if (arrayFilled) {
elapsedNanos = now - oldFrameTime ; elapsedNanos = now - oldFrameTime ;
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ; long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
Double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ; frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
drawFps(frameRate.intValue()); if (updateFPMCounter++ > UPDATE_FPM_PERIOD) {
updateFPMCounter = 0;
drawFps(frameRate.intValue());
}
raceViewController.updateSparkLine();
} }
updateGroups();
// TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay.
elapsedNanos = 1000 / 60;
updateRaceObjects();
if (StreamParser.isRaceFinished()) { if (StreamParser.isRaceFinished()) {
this.stop(); this.stop();
} }
@@ -137,6 +145,30 @@ public class CanvasController {
}; };
} }
/**
* First find the top right and bottom left points' geo locations, then retrieve
* map from google to display on image view. - Haoming 22/5/2017
*/
private void drawGoogleMap() {
findMetersPerPixel();
Point2D topLeftPoint = findScaledXY(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
// distance from top left extreme to panel origin (top left corner)
double distanceFromTopLeftToOrigin = Math.sqrt(Math.pow(topLeftPoint.getX() * metersPerPixelX, 2) + Math.pow(topLeftPoint.getY() * metersPerPixelY, 2));
// angle from top left extreme to panel origin
double bearingFromTopLeftToOrigin = Math.toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY()));
// the top left extreme
Position topLeftPos = new Position(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
Position originPos = GeoUtility.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin);
// distance from origin corner to bottom right corner of the panel
double distanceFromOriginToBottomRight = Math.sqrt(Math.pow(PANEL_HEIGHT* metersPerPixelY, 2) + Math.pow(PANEL_WIDTH * metersPerPixelX, 2));
double bearingFromOriginToBottomRight = Math.toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT));
Position bottomRightPos = GeoUtility.getGeoCoordinate(originPos, bearingFromOriginToBottomRight, distanceFromOriginToBottomRight);
Boundary boundary = new Boundary(originPos.getLat(), bottomRightPos.getLng(), bottomRightPos.getLat(), originPos.getLng());
CanvasMap canvasMap = new CanvasMap(boundary);
mapImage.setImage(canvasMap.getMapImage());
}
/** /**
* Adds border marks to the canvas, taken from the XML file * Adds border marks to the canvas, taken from the XML file
@@ -154,119 +186,129 @@ public class CanvasController {
double[] yBoundaryPoints = new double[courseLimits.size()]; double[] yBoundaryPoints = new double[courseLimits.size()];
for (int i = 0; i < courseLimits.size() - 1; i++) { for (int i = 0; i < courseLimits.size() - 1; i++) {
Limit thisPoint1 = courseLimits.get(i); Limit thisPoint1 = courseLimits.get(i);
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID()); SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID(), thisPoint1.getSeqID());
Limit thisPoint2 = courseLimits.get(i+1); Limit thisPoint2 = courseLimits.get(i+1);
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID()); SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID(), thisPoint2.getSeqID());
Point2D borderPoint1 = findScaledXY(thisMark1); Point2D borderPoint1 = findScaledXY(thisMark1);
Point2D borderPoint2 = findScaledXY(thisMark2); Point2D borderPoint2 = findScaledXY(thisMark2);
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(), gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
borderPoint2.getX(), borderPoint2.getY()); borderPoint2.getX(), borderPoint2.getY());
xBoundaryPoints[i] = borderPoint1.getX(); xBoundaryPoints[i] = borderPoint1.getX();
yBoundaryPoints[i] = borderPoint1.getY(); yBoundaryPoints[i] = borderPoint1.getY();
} }
Limit thisPoint1 = courseLimits.get(courseLimits.size()-1); Limit thisPoint1 = courseLimits.get(courseLimits.size()-1);
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID()); SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID(), thisPoint1.getSeqID());
Limit thisPoint2 = courseLimits.get(0); Limit thisPoint2 = courseLimits.get(0);
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID()); SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID(), thisPoint2.getSeqID());
Point2D borderPoint1 = findScaledXY(thisMark1); Point2D borderPoint1 = findScaledXY(thisMark1);
Point2D borderPoint2 = findScaledXY(thisMark2); Point2D borderPoint2 = findScaledXY(thisMark2);
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(), gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
borderPoint2.getX(), borderPoint2.getY()); borderPoint2.getX(), borderPoint2.getY());
xBoundaryPoints[courseLimits.size()-1] = borderPoint1.getX(); xBoundaryPoints[courseLimits.size()-1] = borderPoint1.getX();
yBoundaryPoints[courseLimits.size()-1] = borderPoint1.getY(); yBoundaryPoints[courseLimits.size()-1] = borderPoint1.getY();
gc.setFill(Color.LIGHTBLUE); // gc.setFill(Color.LIGHTBLUE);
gc.fillPolygon(xBoundaryPoints,yBoundaryPoints,yBoundaryPoints.length); // gc.fillPolygon(xBoundaryPoints,yBoundaryPoints,yBoundaryPoints.length);
} }
private void updateGroups(){
for (BoatGroup boatGroup : boatGroups) {
// some raceObjects will have multiple ID's (for instance gate marks)
//checking if the current "ID" has any updates associated with it
if (StreamParser.boatLocations.containsKey(boatGroup.getRaceId())) {
if (boatGroup.isStopped()) {
updateBoatGroup(boatGroup);
}
}
boatGroup.move();
}
for (MarkGroup markGroup : markGroups) {
for (Long id : markGroup.getRaceIds()) {
if (StreamParser.markLocations.containsKey(id)) {
updateMarkGroup(id, markGroup);
}
}
}
checkForCourseChanges();
}
private void checkForCourseChanges() {
if (StreamParser.isNewRaceXmlReceived()){
gc.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
drawGoogleMap();
addRaceBorder();
}
}
private void updateBoatGroup(BoatGroup boatGroup) {
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId());
// giving the movementQueue a 5 packet buffer to account for slightly out of order packets
if (movementQueue.size() > 0){
try {
BoatPositionPacket positionPacket = movementQueue.take();
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
double heading = 360.0 / 0xffff * positionPacket.getHeading();
boatGroup.setDestination(p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), positionPacket.getTimeValid(), frameRate, boatGroup.getRaceId());
} catch (InterruptedException e){
e.printStackTrace();
}
// }
}
}
void updateMarkGroup (long raceId, MarkGroup markGroup) {
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.markLocations.get(raceId);
if (movementQueue.size() > 0){
try {
BoatPositionPacket positionPacket = movementQueue.take();
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
/** /**
* Adds the course marks to the canvas, taken from the XMl file * Draws all the boats.
*
* 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() { private void initializeBoats() {
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML(); Map<Integer, Yacht> boats = StreamParser.getBoats();
ArrayList<CompoundMark> compoundMarks = raceXMLObject.getCompoundMarks(); Group boatAnnotations = new Group();
RaceObject markGroup;
for (CompoundMark compoundMark : compoundMarks) { ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML().getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) {
participantIDs.add(p.getsourceID());
}
//If the compound mark has 2 marks then its a gate mark for (Yacht boat : boats.values()) {
if (compoundMark.getMarks().size() == 2) { if (participantIDs.contains(boat.getSourceID())) {
CompoundMark.Mark mark1 = compoundMark.getMarks().get(0); boat.setColour(Colors.getColor());
CompoundMark.Mark mark2 = compoundMark.getMarks().get(1); BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
SingleMark singleMark1 = new SingleMark(mark1.getMarkName(), mark1.getTargetLat(), mark1.getTargetLng(), mark1.getSourceID()); boatGroups.add(boatGroup);
SingleMark singleMark2 = new SingleMark(mark1.getMarkName(), mark2.getTargetLat(), mark2.getTargetLng(), mark2.getSourceID()); boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations());
GateMark thisGateMark = new GateMark(compoundMark.getcMarkName(), }
(compoundMark.getMarkID().equals(1)) ? MarkType.OPEN_GATE : MarkType.CLOSED_GATE, }
singleMark1, group.getChildren().add(boatAnnotations);
singleMark2, group.getChildren().addAll(boatGroups);
singleMark1.getLatitude(), }
singleMark1.getLongitude());
markGroup = new MarkGroup(thisGateMark, private void initializeMarks() {
findScaledXY(thisGateMark.getSingleMark1()), List<Mark> allMarks = StreamParser.getXmlObject().getRaceXML().getNonDupCompoundMarks();
findScaledXY(thisGateMark.getSingleMark2())); for (Mark mark : allMarks) {
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
SingleMark sMark = (SingleMark) mark;
raceObjects.add(markGroup); MarkGroup markGroup = new MarkGroup(sMark, findScaledXY(sMark));
raceMarks.add(thisGateMark); markGroups.add(markGroup);
//Otherwise its a single mark
} else { } else {
CompoundMark.Mark singleMark = compoundMark.getMarks().get(0); GateMark gMark = (GateMark) mark;
Mark thisSingleMark = new SingleMark(singleMark.getMarkName(),
singleMark.getTargetLat(),
singleMark.getTargetLng(),
singleMark.getSourceID());
markGroup = new MarkGroup(thisSingleMark, findScaledXY(thisSingleMark));
raceObjects.add(markGroup);
raceMarks.add(thisSingleMark);
MarkGroup markGroup = new MarkGroup(gMark, findScaledXY(gMark.getSingleMark1()), findScaledXY(gMark.getSingleMark2())); //should be 2 objects in the list.
markGroups.add(markGroup);
} }
} }
} group.getChildren().addAll(markGroups);
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 { class ResizableCanvas extends Canvas {
@@ -294,63 +336,37 @@ public class CanvasController {
public double prefWidth(double height) { public double prefWidth(double height) {
return getWidth(); return getWidth();
} }
@Override @Override
public double prefHeight(double width) { public double prefHeight(double width) {
return getHeight(); return getHeight();
} }
} }
private void drawFps(int fps){ private void drawFps(int fps){
if (raceViewController.isDisplayFps()){ if (raceViewController.isDisplayFps()){
gc.clearRect(5,5,50,20); gc.clearRect(5, 5, 60, 30);
gc.setFill(Color.SKYBLUE); gc.setFont(new Font(16));
gc.fillRect(4,4,51,21); gc.setLineWidth(4);
gc.setFill(Color.BLACK); gc.setGlobalAlpha(0.75);
gc.setFont(new Font(14));
gc.setLineWidth(3);
gc.fillText(fps + " FPS", 5, 20); gc.fillText(fps + " FPS", 5, 20);
gc.setGlobalAlpha(0.5);
} else { } else {
gc.clearRect(5,5,50,20); gc.clearRect(5,5,60,30);
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. * Calculates x and y location for every marker that fits it to the canvas the race will be drawn on.
*/ */
private void fitMarksToCanvas() { private void fitMarksToCanvas() {
//Check is called once to avoid unnecessarily change the course limits once the race is running
StreamParser.isNewRaceXmlReceived();
findMinMaxPoint(); findMinMaxPoint();
double minLonToMaxLon = scaleRaceExtremities(); double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon); calculateReferencePointLocation(minLonToMaxLon);
givePointsXY(); //givePointsXY();
addRaceBorder(); addRaceBorder();
findMetersToPixels();
} }
@@ -366,23 +382,18 @@ public class CanvasController {
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat)); sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
Limit minLatMark = sortedPoints.get(0); Limit minLatMark = sortedPoints.get(0);
Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1); Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1);
minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(), minLatMark.getLng(), minLatMark.getSeqID()); minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(), minLatMark.getLng(), minLatMark.getSeqID(), minLatMark.getSeqID());
maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.getSeqID()); maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.getSeqID(), minLatMark.getSeqID());
sortedPoints.sort(Comparator.comparingDouble(Limit::getLng)); sortedPoints.sort(Comparator.comparingDouble(Limit::getLng));
//If the course is on a point on the earth where longitudes wrap around. //If the course is on a point on the earth where longitudes wrap around.
Limit minLonMark = sortedPoints.get(0); Limit minLonMark = sortedPoints.get(0);
Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1); Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1);
SingleMark thisMinLon = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID()); minLonPoint = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID(), minLonMark.getSeqID());
SingleMark thisMaxLon = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID()); maxLonPoint = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID(), minLonMark.getSeqID());
// TODO: 30/03/17 cir27 - Correctly account for longitude wrapping around. if (maxLonPoint.getLongitude() - minLonPoint.getLongitude() > 180) {
if (thisMaxLon.getLongitude() - thisMinLon.getLongitude() > 180) { horizontalInversion = true;
SingleMark temp = thisMinLon;
thisMinLon = thisMaxLon;
thisMaxLon = temp;
} }
minLonPoint = thisMinLon;
maxLonPoint = thisMaxLon;
} }
/** /**
@@ -397,24 +408,28 @@ public class CanvasController {
if (scaleDirection == ScaleDirection.HORIZONTAL) { if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint)); referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
referencePointX = LHS_BUFFER + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint); referencePointX = BUFFER_SIZE + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint)); referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint));
referencePointY = CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER); referencePointY = CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint); referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2; referencePointY = referencePointY / 2;
referencePointY += TOP_BUFFER; referencePointY += BUFFER_SIZE;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint); referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
} else { } else {
referencePointY = CANVAS_HEIGHT - BOT_BUFFER; referencePointY = CANVAS_HEIGHT - BUFFER_SIZE;
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint)); referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
referencePointX = LHS_BUFFER; referencePointX = BUFFER_SIZE;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint); referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
referencePointX += ((CANVAS_WIDTH - (LHS_BUFFER + RHS_BUFFER)) - (minLonToMaxLon * distanceScaleFactor)) / 2; referencePointX += ((CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
}
if(horizontalInversion) {
referencePointX = CANVAS_WIDTH - BUFFER_SIZE - (referencePointX - BUFFER_SIZE);
} }
} }
/** /**
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns it to distanceScaleFactor * 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. * Returns the max horizontal distance of the map.
@@ -431,10 +446,10 @@ public class CanvasController {
horiAngle = horiAngle - (Math.PI / 2); horiAngle = horiAngle - (Math.PI / 2);
double horiDistance = Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint); double horiDistance = Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint);
double vertScale = (CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER)) / vertDistance; double vertScale = (CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE)) / vertDistance;
if ((horiDistance * vertScale) > (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER))) { if ((horiDistance * vertScale) > (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE))) {
distanceScaleFactor = (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER)) / horiDistance; distanceScaleFactor = (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL; scaleDirection = ScaleDirection.HORIZONTAL;
} else { } else {
distanceScaleFactor = vertScale; distanceScaleFactor = vertScale;
@@ -443,43 +458,18 @@ public class CanvasController {
return horiDistance; 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) { private Point2D findScaledXY (Mark unscaled) {
return findScaledXY (minLatPoint.getLatitude(), minLatPoint.getLongitude(), return findScaledXY (unscaled.getLatitude(), unscaled.getLongitude());
unscaled.getLatitude(), unscaled.getLongitude());
} }
private Point2D findScaledXY (double latA, double lonA, double latB, double lonB) { public Point2D findScaledXY (double unscaledLat, double unscaledLon) {
double distanceFromReference; double distanceFromReference;
double angleFromReference; double angleFromReference;
int xAxisLocation = (int) referencePointX; int xAxisLocation = (int) referencePointX;
int yAxisLocation = (int) referencePointY; int yAxisLocation = (int) referencePointY;
angleFromReference = Mark.calculateHeadingRad(latA, lonA, latB, lonB); angleFromReference = Mark.calculateHeadingRad(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, unscaledLon);
distanceFromReference = Mark.calculateDistance(latA, lonA, latB, lonB); distanceFromReference = Mark.calculateDistance(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat, unscaledLon);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) { if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
@@ -496,44 +486,38 @@ public class CanvasController {
xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
} }
if(horizontalInversion) {
xAxisLocation = CANVAS_WIDTH - BUFFER_SIZE - (xAxisLocation - BUFFER_SIZE);
}
return new Point2D(xAxisLocation, yAxisLocation); return new Point2D(xAxisLocation, yAxisLocation);
} }
/** /**
* Find the number of meters per pixel. * Find the number of meters per pixel.
*/ */
private void findMetersToPixels () { private void findMetersPerPixel () {
Double angularDistance; Point2D p1, p2;
Double angle; Mark m1, m2;
Double straightLineDistance; double theta, distance, dx, dy, dHorizontal, dVertical;
if (scaleDirection == ScaleDirection.HORIZONTAL) { m1 = new SingleMark("m1", maxLatPoint.getLatitude(), minLonPoint.getLongitude(), 1, 0);
angularDistance = Mark.calculateDistance(minLonPoint, maxLonPoint); m2 = new SingleMark("m2", minLatPoint.getLatitude(), maxLonPoint.getLongitude(), 2, 0);
angle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint); p1 = findScaledXY(m1);
if (angle > Math.PI / 2) { p2 = findScaledXY(m2);
straightLineDistance = Math.cos(angle - Math.PI) * angularDistance; theta = Mark.calculateHeadingRad(m1, m2);
} else { distance = Mark.calculateDistance(m1, m2);
straightLineDistance = Math.cos(angle) * angularDistance; dHorizontal = Math.abs(Math.sin(theta) * distance);
} dVertical = Math.abs(Math.cos(theta) * distance);
metersToPixels = (CANVAS_WIDTH - RHS_BUFFER - LHS_BUFFER) / straightLineDistance; dx = Math.abs(p1.getX() - p2.getX());
} else { dy = Math.abs(p1.getY() - p2.getY());
angularDistance = Mark.calculateDistance(minLatPoint, maxLatPoint); metersPerPixelX = dHorizontal / dx;
angle = Mark.calculateHeadingRad(minLatPoint, maxLatPoint); metersPerPixelY = dVertical / dy;
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) { List<BoatGroup> getBoatGroups() {
return findScaledXY(minLatPoint.getLatitude(), minLatPoint.getLongitude(), latitude, longitude); return boatGroups;
} }
List<RaceObject> getRaceObjects() { List<MarkGroup> getMarkGroups() {
return raceObjects; return markGroups;
} }
} }
+12 -137
View File
@@ -1,164 +1,39 @@
package seng302.controllers; package seng302.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; 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.AnchorPane;
import javafx.scene.layout.Pane; 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.io.IOException;
import java.net.URL; import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.Timer; import seng302.models.stream.StreamParser;
import java.util.TimerTask;
public class Controller implements Initializable { public class Controller implements Initializable {
@FXML @FXML
private AnchorPane contentPane; 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 {
private void setContentPane(String jfxUrl){
try{
contentPane.getChildren().removeAll(); contentPane.getChildren().removeAll();
contentPane.getChildren().clear(); contentPane.getChildren().clear();
contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
} contentPane.getChildren()
catch(javafx.fxml.LoadException e){ .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
} catch (javafx.fxml.LoadException e) {
System.err.println(e.getCause()); System.err.println(e.getCause());
} } catch (IOException e) {
catch(IOException e){
System.err.println(e); System.err.println(e);
} }
} }
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
//DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
//format.setTimeZone(TimeZone.getTimeZone("GMT-8")); setContentPane("/views/StartScreenView.fxml");
//realTime.setText(format.format(new Date())); StreamParser.boatLocations.clear();
}
/**
* 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);
} }
} }
@@ -1,82 +0,0 @@
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;
}
}
@@ -1,37 +0,0 @@
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--;
}
}
}
@@ -1,35 +1,60 @@
package seng302.controllers; 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.KeyFrame;
import javafx.animation.Timeline; import javafx.animation.Timeline;
import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections;
import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.geometry.Point2D;
import javafx.geometry.Side;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Slider; import javafx.scene.control.Slider;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Line;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration; import javafx.util.Duration;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import seng302.GeometryUtils;
import seng302.controllers.annotations.Annotation;
import seng302.controllers.annotations.ImportantAnnotationController;
import seng302.controllers.annotations.ImportantAnnotationDelegate;
import seng302.controllers.annotations.ImportantAnnotationsState;
import seng302.models.*; import seng302.models.*;
import seng302.models.parsers.ConfigParser; import seng302.models.mark.GateMark;
import seng302.models.parsers.StreamParser; import seng302.models.mark.Mark;
import seng302.models.mark.MarkGroup;
import seng302.models.mark.SingleMark;
import seng302.models.stream.StreamParser;
import seng302.models.stream.XMLParser;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
import java.util.stream.Collectors;
/** /**
* Created by ptg19 on 29/03/17. * Created by ptg19 on 29/03/17.
*/ */
public class RaceViewController extends Thread{ public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
@FXML
private LineChart raceSparkLine;
@FXML
private NumberAxis sparklineYAxis;
@FXML @FXML
private VBox positionVbox; private VBox positionVbox;
@FXML @FXML
@@ -43,64 +68,105 @@ public class RaceViewController extends Thread{
@FXML @FXML
private Slider annotationSlider; private Slider annotationSlider;
@FXML @FXML
private Button selectAnnotationBtn;
@FXML
private ComboBox boatSelectionComboBox;
@FXML
private CanvasController includedCanvasController; private CanvasController includedCanvasController;
private ArrayList<Yacht> startingBoats = new ArrayList<>(); private static ArrayList<Yacht> startingBoats = new ArrayList<>();
private boolean displayFps; private boolean displayFps;
private Timeline timerTimeline; private Timeline timerTimeline;
private Map<Yacht, TimelineInfo> timelineInfos = new HashMap<>();
private ArrayList<Yacht> boatOrder = new ArrayList<>();
private Race race;
private Stage stage; private Stage stage;
private static HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>();
private static ArrayList<Yacht> racingBoats = new ArrayList<>();
private ImportantAnnotationsState importantAnnotations;
private Yacht selectedBoat;
public void initialize() { public void initialize() {
// Load a default important annotation state
importantAnnotations = new ImportantAnnotationsState();
RaceController raceController = new RaceController(); //Formatting the y axis of the sparkline
raceController.initializeRace(); raceSparkLine.getYAxis().setRotate(180);
race = raceController.getRace(); raceSparkLine.getYAxis().setTickLabelRotation(180);
for (Yacht boat : race.getBoats()) { raceSparkLine.getYAxis().setTranslateX(-5);
startingBoats.add(boat); raceSparkLine.getYAxis().setAutoRanging(false);
} sparklineYAxis.setTickMarkVisible(false);
// try{ startingBoats = new ArrayList<>(StreamParser.getBoats().values());
// initializeTimelines();
// }
// catch (Exception e){
// e.printStackTrace();
// }
includedCanvasController.setup(this); includedCanvasController.setup(this);
includedCanvasController.initializeCanvas(); includedCanvasController.initializeCanvas();
initializeTimer(); initializeUpdateTimer();
initializeSettings(); initialiseFPSCheckBox();
initialiseWindDirection(); initialiseAnnotationSlider();
initialisePositionVBox(); initialiseBoatSelectionComboBox();
//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(); includedCanvasController.timer.start();
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
} }
/**
* The important annotations have been changed, update this view
*
* @param importantAnnotationsState The current state of the selected annotations
*/
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
this.importantAnnotations = importantAnnotationsState;
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
}
private void initializeSettings() {
/**
* Loads the "select annotations" view in a new window
*/
private void loadSelectAnnotationView() {
try {
FXMLLoader fxmlLoader = new FXMLLoader();
Stage stage = new Stage();
// Set controller
ImportantAnnotationController controller = new ImportantAnnotationController(this,
stage);
fxmlLoader.setController(controller);
// Load FXML and set CSS
fxmlLoader
.setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 469, 298);
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
stage.initStyle(StageStyle.UNDECORATED);
stage.setScene(scene);
stage.show();
controller.loadState(importantAnnotations);
} catch (IOException e) {
e.printStackTrace();
}
}
private void initialiseFPSCheckBox() {
displayFps = true; displayFps = true;
toggleFps.selectedProperty().addListener(
(observable, oldValue, newValue) -> displayFps = !displayFps);
}
toggleFps.selectedProperty().addListener(new ChangeListener<Boolean>() { private void initialiseAnnotationSlider() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
displayFps = !displayFps;
}
});
//SLIFER STUFF BELOW
annotationSlider.setLabelFormatter(new StringConverter<Double>() { annotationSlider.setLabelFormatter(new StringConverter<Double>() {
@Override @Override
public String toString(Double n) { public String toString(Double n) {
if (n == 0) return "None"; if (n == 0) {
if (n == 1) return "Low"; return "None";
if (n == 2) return "Medium"; }
if (n == 3) return "All"; if (n == 1) {
return "Important";
}
if (n == 2) {
return "All";
}
return "All"; return "All";
} }
@@ -110,169 +176,349 @@ public class RaceViewController extends Thread{
switch (s) { switch (s) {
case "None": case "None":
return 0d; return 0d;
case "Low": case "Important":
return 1d; return 1d;
case "Medium":
return 2d;
case "All": case "All":
return 3d; return 2d;
default: default:
return 3d; return 2d;
} }
} }
}); });
annotationSlider.valueProperty().addListener((obs, oldval, newVal) -> annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
setAnnotations((int)annotationSlider.getValue())); setAnnotations((int) annotationSlider.getValue()));
annotationSlider.setValue(3); annotationSlider.setValue(2);
} }
private void initializeTimer(){
/**
* Used to add any new boats into the race that may have started late or not have had data received yet
*/
void updateSparkLine(){
// Collect the racing boats that aren't already in the chart
ArrayList<Yacht> sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceID())
&& yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
// Obtain the qualifying boats to set the max on the Y axis
racingBoats = startingBoats.stream().filter(yacht ->
yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
sparklineYAxis.setUpperBound(racingBoats.size() + 1);
// Create a new data series for new boats
sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> {
Series<String, Double> yachtData = new Series<>();
yachtData.setName(yacht.getBoatName());
yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
sparkLineData.put(yacht.getSourceID(), yachtData);
});
// Lambda function to sort the series in order of leg (later legs shown more to the right)
List<XYChart.Series<String, Double>> positions = new ArrayList<>(sparkLineData.values());
Collections.sort(positions, (o1, o2) -> {
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
if (leg2 < leg1){
return 1;
} else {
return -1;
}
});
// Adds the new data series to the sparkline (and set the colour of the series)
raceSparkLine.setCreateSymbols(false);
positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> {
raceSparkLine.getData().add(spark);
spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
});
}
/**
* Updates the yachts sparkline of the desired boat and using the new leg number
* @param yacht The yacht to be updated on the sparkline
* @param legNumber the leg number that the position will be assigned to
*/
public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){
XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
}
/**
* gets the rgb string of the boats colour to use for the chart via css
* @param boatName boat passed in to get the boats colour
* @return the colour as an rgb string
*/
private String getBoatColorAsRGB(String boatName){
Color color = Color.WHITE;
for (Yacht yacht: startingBoats){
if (Objects.equals(yacht.getBoatName(), boatName)){
color = yacht.getColour();
}
}
if (color == null){
return String.format( "#%02X%02X%02X",255,255,255);
}
return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ),
(int)( color.getBlue() * 255 ) );
}
/**
* Initalises a timer which updates elements of the RaceView such as wind direction, boat
* orderings etc.. which are dependent on the info from the stream parser constantly.
* Updates of each of these attributes are called ONCE EACH SECOND
*/
private void initializeUpdateTimer() {
timerTimeline = new Timeline(); timerTimeline = new Timeline();
timerTimeline.setCycleCount(Timeline.INDEFINITE); timerTimeline.setCycleCount(Timeline.INDEFINITE);
// Run timer update every second // Run timer update every second
timerTimeline.getKeyFrames().add( timerTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1), new KeyFrame(Duration.seconds(1),
event -> { event -> {
if (StreamParser.isRaceFinished()) { updateRaceTime();
timerLabel.setFill(Color.RED); updateWindDirection();
timerLabel.setText("Race Finished!"); updateOrder();
} else { updateBoatSelectionComboBox();
timerLabel.setText(currentTimer()); })
}
})
); );
// Start the timer // Start the timer
timerTimeline.playFromStart(); 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 * Iterates over all corners until ones SeqID matches with the boats current leg number.
* Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark
* Returns null if no next mark found.
* @param bg The BoatGroup to find the next mark of
* @return The next Mark or null if none found
*/ */
private void initializeTimelines() { private Mark getNextMark(BoatGroup bg) {
HashMap<Yacht, List> boat_events = race.getEvents(); Integer legNumber = bg.getBoat().getLegNumber();
for (Yacht boat : boat_events.keySet()) {
startingBoats.add(boat); List<XMLParser.RaceXMLObject.Corner> markSequence = StreamParser.getXmlObject().getRaceXML().getCompoundMarkSequence();
// // x, y are the real time coordinates
// DoubleProperty x = new SimpleDoubleProperty(); if (legNumber == 0) {
// DoubleProperty y = new SimpleDoubleProperty(); return null;
// } else if (legNumber == markSequence.size() - 1) {
// List<KeyFrame> keyFrames = new ArrayList<>(); return null;
// 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();
for (XMLParser.RaceXMLObject.Corner corner : markSequence) {
if (legNumber + 2 == corner.getSeqID()) {
Integer thisCompoundMarkID = corner.getCompoundMarkID();
for (Mark mark : StreamParser.getXmlObject().getRaceXML().getAllCompoundMarks()) {
if (mark.getCompoundMarkID() == thisCompoundMarkID) {
return mark;
}
}
}
}
return null;
} }
private void setRaceDuration(){
Double maxDuration = 0.0;
Timeline maxTimeline = null;
for (TimelineInfo timelineInfo : timelineInfos.values()) { /**
* Updates the wind direction arrow and text as from info from the StreamParser
*/
private void updateWindDirection() {
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
windArrowText.setRotate(StreamParser.getWindDirection());
}
Timeline timeline = timelineInfo.getTimeline();
if (timeline.getTotalDuration().toMillis() >= maxDuration) {
maxDuration = timeline.getTotalDuration().toMillis();
maxTimeline = timeline;
}
// Timelines are paused by default /**
timeline.play(); * Updates the clock for the race
timeline.pause(); */
private void updateRaceTime() {
if (StreamParser.isRaceFinished()) {
timerLabel.setFill(Color.RED);
timerLabel.setText("Race Finished!");
} else {
timerLabel.setText(getTimeSinceStartOfRace());
}
}
/**
* Grabs the boats currently in the race as from the StreamParser and sets them to be selectable
* in the boat selection combo box
*/
private void updateBoatSelectionComboBox() {
ObservableList<Yacht> observableBoats = FXCollections
.observableArrayList(StreamParser.getBoatsPos().values());
boatSelectionComboBox.setItems(observableBoats);
}
/**
* Updates the order of the boats as from the StreamParser and sets them in the boat order
* section
*/
private void updateOrder() {
positionVbox.getChildren().clear();
positionVbox.getChildren().removeAll();
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// list of racing boat id
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
.getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) {
participantIDs.add(p.getsourceID());
} }
maxTimeline.setOnFinished(event -> { if (StreamParser.isRaceStarted()) {
race.setRaceFinished(); for (Yacht boat : StreamParser.getBoatsPos().values()) {
loadRaceResultView(); if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
if (boat.getBoatStatus() == 3) { // 3 is finish status
Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " (Finished)");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
positionVbox.getChildren().add(textToAdd);
} else {
Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " ");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
textToAdd.setStyle("");
positionVbox.getChildren().add(textToAdd);
}
}
}
} else {
for (Yacht boat : StreamParser.getBoats().values()) {
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " ");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
textToAdd.setStyle("");
positionVbox.getChildren().add(textToAdd);
}
}
}
}
private void updateLaylines(BoatGroup bg) {
Mark nextMark = getNextMark(bg);
Boolean isUpwind = null;
// Can only calc leg direction if there is a next mark and it is a gate mark
if (nextMark != null) {
if (nextMark instanceof GateMark) {
if (bg.isUpwindLeg(includedCanvasController, nextMark)) {
isUpwind = true;
} else {
isUpwind = false;
}
for(MarkGroup mg : includedCanvasController.getMarkGroups()) {
mg.removeLaylines();
if (mg.getMainMark().getId() == nextMark.getId()) {
SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1();
SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2();
Point2D markPoint1 = includedCanvasController.findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude());
Point2D markPoint2 = includedCanvasController.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
HashMap<Double, Double> angleAndSpeed;
if (isUpwind) {
angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed());
} else {
angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed());
}
Double resultingAngle = angleAndSpeed.keySet().iterator().next();
Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
Integer lineFuncResult = GeometryUtils.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
Line rightLayline = new Line();
Line leftLayline = new Line();
if (lineFuncResult == 1) {
rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
} else if (lineFuncResult == -1) {
rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
}
leftLayline.setStrokeWidth(0.5);
leftLayline.setStroke(bg.getBoat().getColour());
rightLayline.setStrokeWidth(0.5);
rightLayline.setStroke(bg.getBoat().getColour());
bg.setLaylines(leftLayline, rightLayline);
mg.addLaylines(leftLayline, rightLayline);
}
}
}
}
}
private Point2D getPointRotation(Point2D ref, Double distance, Double angle){
Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle);
Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle);
return new Point2D(newX, newY);
}
public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle);
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
return line;
}
public Line makeRightLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle - layLineAngle);
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
return line;
}
/**
* Initialised the combo box with any boats currently in the race and adds the required listener
* for the combobox to take action upon selection
*/
private void initialiseBoatSelectionComboBox() {
updateBoatSelectionComboBox();
boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
//This listener is fired whenever the combo box changes. This means when the values are updated
//We dont want to set the selected value if the values are updated but nothing clicked (null)
if (newValue != null && newValue != selectedBoat) {
Yacht thisYacht = (Yacht) newValue;
setSelectedBoat(thisYacht);
}
}); });
} }
/**
* 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 * Display the list of boats in the order they finished the race
*/ */
private void loadRaceResultView() { private void loadRaceResultView() {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml")); FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
loader.setController(new RaceResultController(race));
try { try {
contentAnchorPane.getChildren().removeAll(); contentAnchorPane.getChildren().removeAll();
@@ -286,40 +532,6 @@ public class RaceViewController extends Thread{
} }
} }
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 * Convert seconds to a string of the format mm:ss
@@ -334,7 +546,7 @@ public class RaceViewController extends Thread{
return String.format("%02d:%02d", time / 60, time % 60); return String.format("%02d:%02d", time / 60, time % 60);
} }
private String currentTimer() { private String getTimeSinceStartOfRace() {
String timerString = "0:00"; String timerString = "0:00";
if (StreamParser.getTimeSinceStart() > 0) { if (StreamParser.getTimeSinceStart() > 0) {
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60); String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
@@ -354,84 +566,120 @@ public class RaceViewController extends Thread{
return timerString; return timerString;
} }
public void stopTimer() {
timerTimeline.stop();
}
public void startTimer() {
timerTimeline.play();
}
public boolean isDisplayFps() { boolean isDisplayFps() {
return displayFps; return displayFps;
} }
public Race getRace() { /**
return race; * Display the important annotations for a specific BoatGroup
} * @param bg The boat group to set the annotations for
*/
private void setBoatGroupImportantAnnotations(BoatGroup bg) {
if (importantAnnotations.getAnnotationState(Annotation.NAME)) {
bg.setTeamNameObjectVisible(true);
} else {
bg.setTeamNameObjectVisible(false);
}
public Map<Yacht, TimelineInfo> getTimelineInfos() { if (importantAnnotations.getAnnotationState(Annotation.SPEED)) {
return timelineInfos; bg.setVelocityObjectVisible(true);
} } else {
bg.setVelocityObjectVisible(false);
}
public ArrayList<Yacht> getStartingBoats(){ if (importantAnnotations.getAnnotationState(Annotation.TRACK)) {
return startingBoats; bg.setLineGroupVisible(true);
} } else {
bg.setLineGroupVisible(false);
}
if (importantAnnotations.getAnnotationState(Annotation.WAKE)) {
bg.setWakeVisible(true);
} else {
bg.setWakeVisible(false);
}
//TODO fix boat annotations with new boatgroup
if (importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK)) {
bg.setEstTimeToNextMarkObjectVisible(true);
} else {
bg.setEstTimeToNextMarkObjectVisible(false);
}
if (importantAnnotations.getAnnotationState(Annotation.LEGTIME)) {
bg.setLegTimeObjectVisible(true);
} else {
bg.setLegTimeObjectVisible(false);
}
}
private void setAnnotations(Integer annotationLevel) { private void setAnnotations(Integer annotationLevel) {
switch (annotationLevel) { switch (annotationLevel) {
// No Annotations
case 0: case 0:
for (RaceObject ro : includedCanvasController.getRaceObjects()) { for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
if(ro instanceof BoatGroup) { bg.setTeamNameObjectVisible(false);
BoatGroup bg = (BoatGroup) ro; bg.setVelocityObjectVisible(false);
bg.setTeamNameObjectVisible(false); bg.setEstTimeToNextMarkObjectVisible(false);
bg.setVelocityObjectVisible(false); bg.setLegTimeObjectVisible(false);
bg.setLineGroupVisible(false); bg.setLineGroupVisible(false);
bg.setWakeVisible(false); bg.setWakeVisible(false);
}
} }
break; break;
// Important Annotations
case 1: case 1:
for (RaceObject ro : includedCanvasController.getRaceObjects()) { for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
if(ro instanceof BoatGroup) { setBoatGroupImportantAnnotations(bg);
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(true);
bg.setVelocityObjectVisible(false);
bg.setLineGroupVisible(false);
bg.setWakeVisible(false);
}
} }
break; break;
// All Annotations
case 2: case 2:
for (RaceObject ro : includedCanvasController.getRaceObjects()) { for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
if(ro instanceof BoatGroup) { bg.setTeamNameObjectVisible(true);
BoatGroup bg = (BoatGroup) ro; bg.setVelocityObjectVisible(true);
bg.setTeamNameObjectVisible(true); bg.setEstTimeToNextMarkObjectVisible(true);
bg.setVelocityObjectVisible(false); bg.setLegTimeObjectVisible(true);
bg.setLineGroupVisible(true); bg.setLineGroupVisible(true);
bg.setWakeVisible(false); bg.setWakeVisible(true);
}
}
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; break;
} }
} }
void setStage (Stage stage) {
/**
* Sets all the annotations of the selected boat to be visible and all others to be hidden
*
* @param yacht The yacht for which we want to view all annotations
*/
private void setSelectedBoat(Yacht yacht) {
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
//We need to iterate over all race groups to get the matching boat group belonging to this boat if we
//are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
if (bg.getBoat().getHullID().equals(yacht.getHullID())) {
updateLaylines(bg);
bg.setIsSelected(true);
selectedBoat = yacht;
} else {
bg.setIsSelected(false);
}
}
}
void setStage(Stage stage) {
this.stage = stage; this.stage = stage;
} }
Stage getStage () { Stage getStage() {
return stage; return stage;
} }
/**
* Used for when the boat attempts to add data to the sparkline (first checks if the sparkline contains info on it)
* @param yachtId
* @return
*/
public static boolean sparkLineStatus(Integer yachtId) {
return sparkLineData.containsKey(yachtId);
}
} }
@@ -0,0 +1,188 @@
package seng302.controllers;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
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.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import seng302.models.Yacht;
import seng302.models.stream.StreamParser;
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
public class StartScreenController implements Initializable {
@FXML
private GridPane gridPane;
@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 boolean switchedToRaceView = false;
private void setContentPane(String jfxUrl) {
try {
// get the main controller anchor pane (MainView.fxml)
AnchorPane contentPane = (AnchorPane) gridPane.getParent();
contentPane.getChildren().removeAll();
contentPane.getChildren().clear();
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren()
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
} catch (javafx.fxml.LoadException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString());
}
/**
* Running a timer to update the livestream status on welcome screen. Update interval is 1
* second.
*/
public void startStream() {
if (StreamParser.isStreamStatus()) {
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.isRaceStarted()) {
if (!switchedToRaceView) {
switchToRaceView();
}
timer.cancel();
}
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() {
StreamParser.boatLocations.clear();
switchedToRaceView = true;
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")
);
// check if the boat is racing
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
.getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) {
participantIDs.add(p.getsourceID());
}
// add boats to the start screen list
if (StreamParser.isRaceStarted()) { // if race is started, use StreamParser.getBoatsPos()
for (Yacht boat : StreamParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) {
data.add(boat);
}
}
} else { // else use StreamParser.getBoats()
for (Yacht boat : StreamParser.getBoats().values()) {
if (participantIDs.contains(boat.getSourceID())) {
data.add(boat);
}
}
}
teamList.refresh();
}
}
@@ -0,0 +1,13 @@
package seng302.controllers.annotations;
/**
* Annotations the user can select as important
*/
public enum Annotation {
SPEED,
WAKE,
TRACK,
NAME,
ESTTIMETONEXTMARK,
LEGTIME
}
@@ -0,0 +1,145 @@
package seng302.controllers.annotations;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import seng302.controllers.RaceViewController;
import seng302.controllers.annotations.Annotation;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
public class ImportantAnnotationController implements Initializable {
/*
* JavaFX Outlets
*/
@FXML
private CheckBox boatWakeSelect;
@FXML
private CheckBox boatSpeedSelect;
@FXML
private CheckBox boatTrackSelect;
@FXML
private CheckBox boatNameSelect;
@FXML
private CheckBox boatEstTimeToNextMarkSelect;
@FXML
private CheckBox boatElapsedTimeSelect;
@FXML
private AnchorPane annotationSelectWindow;
@FXML
private Button closeButton;
private ImportantAnnotationDelegate delegate;
private ImportantAnnotationsState importantAnnotationsState;
private Stage stage;
public ImportantAnnotationController(ImportantAnnotationDelegate delegate, Stage stage) {
this.delegate = delegate;
importantAnnotationsState = new ImportantAnnotationsState();
this.stage = stage;
}
/**
* Sets whether or not an annotation is considered important, then
* sends an update to the delegate
*
* @param annotation The annotation
* @param isSet True if annotation is important
*/
private void setAnnotation(Annotation annotation, Boolean isSet) {
importantAnnotationsState.setAnnotationState(annotation, isSet);
sendUpdate();
}
/**
* Sends an update to the delegate when the important
* annotations have changed
*/
private void sendUpdate() {
this.delegate.importantAnnotationsChanged(importantAnnotationsState);
}
/**
* Load the current state of the 'important annotations'
*
* @param currentState hashmap containing the states of each annotation
*/
public void loadState(ImportantAnnotationsState currentState) {
this.importantAnnotationsState = currentState;
// Initialise checkboxes
for (Annotation annotation : importantAnnotationsState.getAnnotations()) {
switch (annotation) {
case WAKE:
boatWakeSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
break;
case SPEED:
boatSpeedSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
break;
case TRACK:
boatTrackSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
break;
case NAME:
boatNameSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
break;
case ESTTIMETONEXTMARK:
boatEstTimeToNextMarkSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
break;
case LEGTIME:
boatElapsedTimeSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
default:
break;
}
}
}
/**
* View did load
*
* @param location .
* @param resources .
*/
@Override
public void initialize(URL location, ResourceBundle resources) {
boatWakeSelect
.setOnAction(event -> setAnnotation(Annotation.WAKE, boatWakeSelect.isSelected()));
boatSpeedSelect
.setOnAction(event -> setAnnotation(Annotation.SPEED, boatSpeedSelect.isSelected()));
boatTrackSelect
.setOnAction(event -> setAnnotation(Annotation.TRACK, boatTrackSelect.isSelected()));
boatNameSelect
.setOnAction(event -> setAnnotation(Annotation.NAME, boatNameSelect.isSelected()));
boatEstTimeToNextMarkSelect.setOnAction(event -> setAnnotation(Annotation.ESTTIMETONEXTMARK,
boatEstTimeToNextMarkSelect.isSelected()));
boatElapsedTimeSelect.setOnAction(
event -> setAnnotation(Annotation.LEGTIME, boatElapsedTimeSelect.isSelected()));
closeButton.setOnAction(event -> stage.close());
}
}
@@ -0,0 +1,16 @@
package seng302.controllers.annotations;
/**
* An ImportantAnnotationDelegate handles updating the important annotations
* displayed to the user on behalf of the ImportantAnnotationController
*/
public interface ImportantAnnotationDelegate {
/**
* The important annotations have been changed, update the
* annotations displayed to the user
* @param importantAnnotationsState The current state of the selected annotations
*/
void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState);
}
@@ -0,0 +1,52 @@
package seng302.controllers.annotations;
import java.util.HashMap;
import java.util.Map;
public class ImportantAnnotationsState {
public static final Boolean DEFAULT_ANNOTATION_STATE = true;
private Map<Annotation, Boolean> currentState;
/**
* Stores the users preference for the annotations
* they consider to be important
*/
public ImportantAnnotationsState(){
this.currentState = new HashMap<>();
initialiseState();
}
/**
* Set each annotation to the default annotation state
*/
private void initialiseState(){
for (Annotation annotation : getAnnotations()){
currentState.put(annotation, DEFAULT_ANNOTATION_STATE);
}
}
/**
* Sets the state (visibility) of an annotation
* @param annotation The annotation to set
* @param visible Whether or not the annotation should be visible
*/
public void setAnnotationState(Annotation annotation, Boolean visible){
this.currentState.put(annotation, visible);
}
/**
* Returns the state (visibility) of a specific annotation
* @param annotation The annotation to check
* @return True if visible, else false
*/
public Boolean getAnnotationState(Annotation annotation){
return this.currentState.containsKey(annotation) && this.currentState.get(annotation);
}
/**
* @return Return an array containing all defined annotations
*/
public Annotation[] getAnnotations(){
return Annotation.class.getEnumConstants();
}
}
+352 -201
View File
@@ -1,88 +1,137 @@
package seng302.models; package seng302.models;
import java.util.ArrayList;
import javafx.event.EventHandler;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.CacheHint;
import javafx.scene.Group; import javafx.scene.Group;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Line; import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon; import javafx.scene.shape.Polygon;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import javafx.scene.transform.Rotate; import javafx.scene.transform.Rotate;
import javafx.stage.Stage; import seng302.GeometryUtils;
import seng302.controllers.CanvasController;
import seng302.models.mark.GateMark;
import seng302.models.mark.Mark;
import seng302.models.mark.SingleMark;
import seng302.models.stream.StreamParser;
import java.util.ArrayList; import java.text.DateFormat;
import java.util.List; import java.text.SimpleDateFormat;
/** /**
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 dimensional boat. * BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
* 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 * dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
* annotate the boat teams name and the boats velocity. The boat will update it's position onscreen everytime * a wake object and two text labels to annotate the boat teams name and the boats velocity. The
* UpdatePosition is called unless the window is minimized in which case it attempts to store animations and apply them * boat will update it's position onscreen everytime UpdatePosition is called unless the window is
* when the window is maximised. * minimized in which case it attempts to store animations and apply them when the window is
* maximised.
*/ */
public class BoatGroup extends RaceObject{ public class BoatGroup extends Group {
//Constants for drawing //Constants for drawing
private static final double TEAMNAME_X_OFFSET = 10d; private static final double TEAMNAME_X_OFFSET = 10d;
private static final double TEAMNAME_Y_OFFSET = -15d; private static final double TEAMNAME_Y_OFFSET = -29d;
private static final double VELOCITY_X_OFFSET = 10d; private static final double VELOCITY_X_OFFSET = 10d;
private static final double VELOCITY_Y_OFFSET = -5d; private static final double VELOCITY_Y_OFFSET = -17d;
private static final double ESTTIMETONEXTMARK_X_OFFSET = 10d;
private static final double ESTTIMETONEXTMARK_Y_OFFSET = -5d;
private static final double LEGTIME_X_OFFSET = 10d;
private static final double LEGTIME_Y_OFFSET = 7d;
private static final double BOAT_HEIGHT = 15d; private static final double BOAT_HEIGHT = 15d;
private static final double BOAT_WIDTH = 10d; private static final double BOAT_WIDTH = 10d;
//Variables for boat logic. //Variables for boat logic.
private Point2D lastPoint; private boolean isStopped = true;
private int wakeGenerationDelay = 10; private double xIncrement;
private double distanceTravelled; private double yIncrement;
private long lastTimeValid = 0;
private Double lastRotation = 0.0;
private long framesToMove;
//Graphical objects //Graphical objects
private Yacht boat; private Yacht boat;
private Group lineGroup = new Group(); private Group lineGroup = new Group();
private Polygon boatPoly; private Polygon boatPoly;
private Text teamNameObject; private Text teamNameObject;
private Text velocityObject; private Text velocityObject;
private Text estTimeToNextMarkObject;
private Text legTimeObject;
private Wake wake; private Wake wake;
//Handles boat moving when connecting to a stream private Line leftLayLine;
private boolean setToInitialLocation = false; private Line rightLayline;
private Double distanceTravelled = 0.0;
private Point2D lastPoint;
private boolean destinationSet; private boolean destinationSet;
//Variables for handling minimization private Color textColor = Color.RED;
private Stage stage;
private boolean isMaximized= true; private Boolean isSelected = true; //All boats are initalised as selected
private List<Line> lineStorage = new ArrayList<>();
private int setCallCount = 5;
/** /**
* Creates a BoatGroup with the default triangular boat polygon. * 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 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 color The colour of the boat polygon and the trailing line.
*/ */
public BoatGroup (Yacht boat, Color color){ public BoatGroup(Yacht boat, Color color) {
this.boat = boat; this.boat = boat;
initChildren(color); initChildren(color);
this.textColor = color;
} }
/** /**
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be at point (0,0). * Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which * at point (0,0).
* BoatGroup to update. *
* @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 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. * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/ */
public BoatGroup (Yacht boat, Color color, double... points) public BoatGroup(Yacht boat, Color color, double... points) {
{
this.boat = boat; this.boat = boat;
initChildren(color, points); initChildren(color, points);
} }
/** /**
* Creates the javafx objects that will be the in the group by default. * Return a text object with caching and a color applied
* @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. * @param defaultText The default text to display
* @param fill The text fill color
* @return The text object
*/ */
private void initChildren (Color color, double... points) { private Text getTextObject(String defaultText, Color fill) {
Text text = new Text(defaultText);
text.setFill(fill);
text.setCacheHint(CacheHint.SPEED);
text.setCache(true);
return text;
}
/**
* 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) {
textColor = color;
destinationSet = false;
boatPoly = new Polygon(points); boatPoly = new Polygon(points);
boatPoly.setFill(color); boatPoly.setFill(color);
boatPoly.setOnMouseEntered(event -> boatPoly.setFill(Color.FLORALWHITE));
boatPoly.setOnMouseExited(event -> boatPoly.setFill(color));
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
boatPoly.setCache(true);
boatPoly.setCacheHint(CacheHint.SPEED);
teamNameObject = new Text(boat.getShortName()); teamNameObject = getTextObject(boat.getShortName(), textColor);
velocityObject = new Text(String.valueOf(boat.getVelocity())); velocityObject = getTextObject(boat.getVelocity().toString(), textColor);
teamNameObject.setX(TEAMNAME_X_OFFSET); teamNameObject.setX(TEAMNAME_X_OFFSET);
teamNameObject.setY(TEAMNAME_Y_OFFSET); teamNameObject.setY(TEAMNAME_Y_OFFSET);
@@ -91,194 +140,287 @@ public class BoatGroup extends RaceObject{
velocityObject.setX(VELOCITY_X_OFFSET); velocityObject.setX(VELOCITY_X_OFFSET);
velocityObject.setY(VELOCITY_Y_OFFSET); velocityObject.setY(VELOCITY_Y_OFFSET);
velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); velocityObject.relocate(velocityObject.getX(), velocityObject.getY());
destinationSet = false;
updateLastMarkRoundingTime();
updateTimeTillNextMark();
if (estTimeToNextMarkObject != null) {
estTimeToNextMarkObject.setX(ESTTIMETONEXTMARK_X_OFFSET);
estTimeToNextMarkObject.setY(ESTTIMETONEXTMARK_Y_OFFSET);
estTimeToNextMarkObject
.relocate(estTimeToNextMarkObject.getX(), estTimeToNextMarkObject.getY());
}
if (legTimeObject != null) {
legTimeObject.setX(LEGTIME_X_OFFSET);
legTimeObject.setY(LEGTIME_Y_OFFSET);
legTimeObject.relocate(legTimeObject.getX(), legTimeObject.getY());
}
leftLayLine = new Line();
rightLayline = new Line();
wake = new Wake(0, -BOAT_HEIGHT); wake = new Wake(0, -BOAT_HEIGHT);
super.getChildren().addAll(teamNameObject, velocityObject, boatPoly); super.getChildren()
.addAll(teamNameObject, velocityObject, boatPoly, estTimeToNextMarkObject,
legTimeObject, leftLayLine, rightLayline);
} }
/** /**
* Creates the javafx objects that will be the in the group by default. * 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 color The colour of the boat polygon and the trailing line.
*/ */
private void initChildren (Color color) { private void initChildren(Color color) {
initChildren(color, initChildren(color,
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2, -BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
0.0, -BOAT_HEIGHT / 2, 0.0, -BOAT_HEIGHT / 2,
BOAT_WIDTH / 2, BOAT_HEIGHT / 2); BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
} }
/** /**
* Moves the boat and its children annotations from its current coordinates by specified amounts. * 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 dx The amount to move the X coordinate by
* @param dy The amount to move the Y coordinate by * @param dy The amount to move the Y coordinate by
*/ */
public void moveGroupBy(double dx, double dy, double rotation) { private void moveGroupBy(double dx, double dy) {
boatPoly.setLayoutX(boatPoly.getLayoutX() + dx); boatPoly.setLayoutX(boatPoly.getLayoutX() + dx);
boatPoly.setLayoutY(boatPoly.getLayoutY() + dy); boatPoly.setLayoutY(boatPoly.getLayoutY() + dy);
teamNameObject.setLayoutX(teamNameObject.getLayoutX() + dx); teamNameObject.setLayoutX(teamNameObject.getLayoutX() + dx);
teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy); teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy);
velocityObject.setLayoutX(velocityObject.getLayoutX() + dx); velocityObject.setLayoutX(velocityObject.getLayoutX() + dx);
velocityObject.setLayoutY(velocityObject.getLayoutY() + dy); velocityObject.setLayoutY(velocityObject.getLayoutY() + dy);
estTimeToNextMarkObject.setLayoutX(estTimeToNextMarkObject.getLayoutX() + dx);
estTimeToNextMarkObject.setLayoutY(estTimeToNextMarkObject.getLayoutY() + dy);
legTimeObject.setLayoutX(legTimeObject.getLayoutX() + dx);
legTimeObject.setLayoutY(legTimeObject.getLayoutY() + dy);
wake.setLayoutX(wake.getLayoutX() + dx); wake.setLayoutX(wake.getLayoutX() + dx);
wake.setLayoutY(wake.getLayoutY() + dy); wake.setLayoutY(wake.getLayoutY() + dy);
rotateTo(rotation + currentRotation);
} }
/** /**
* Moves the boat and its children annotations to coordinates specified * Moves the boat and its children annotations to coordinates specified
*
* @param x The X coordinate to move the boat to * @param x The X coordinate to move the boat to
* @param y The Y 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) { private void moveTo(double x, double y, double rotation) {
rotateTo(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.setLayoutX(x);
boatPoly.setLayoutY(y); boatPoly.setLayoutY(y);
teamNameObject.setLayoutX(x); teamNameObject.setLayoutX(x);
teamNameObject.setLayoutY(y); teamNameObject.setLayoutY(y);
velocityObject.setLayoutX(x); velocityObject.setLayoutX(x);
velocityObject.setLayoutY(y); velocityObject.setLayoutY(y);
estTimeToNextMarkObject.setLayoutX(x);
estTimeToNextMarkObject.setLayoutY(y);
legTimeObject.setLayoutX(x);
legTimeObject.setLayoutY(y);
wake.setLayoutX(x); wake.setLayoutX(x);
wake.setLayoutY(y); wake.setLayoutY(y);
wake.rotate(currentRotation); wake.rotate(rotation);
}
private void rotateTo(double rotation) {
boatPoly.getTransforms().setAll(new Rotate(rotation));
} }
/** /**
* Updates the position of all graphics in the BoatGroup based off of the given time interval. * Updates the time until next mark label, will create a label if one doesn't exist
* @param timeInterval The interval, in milliseconds, the boat should update it's position based on.
*/ */
public void updatePosition (long timeInterval) { private void updateTimeTillNextMark() {
//Calculate the movement of the boat. if (estTimeToNextMarkObject == null) {
if (isMaximized) { estTimeToNextMarkObject = getTextObject("Next mark: -", textColor);
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);
} }
if (boat.getEstimateTimeAtNextMark() != null) {
DateFormat format = new SimpleDateFormat("mm:ss");
String timeToNextMark = format
.format(boat.getEstimateTimeAtNextMark() - StreamParser.getCurrentTimeLong());
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
} else {
estTimeToNextMarkObject.setText("Next mark: -");
}
}
/**
* Updates the time since last mark rounding, will create a label if one doesn't exist
*/
private void updateLastMarkRoundingTime() {
if (legTimeObject == null) {
legTimeObject = getTextObject("Last mark: -", textColor);
}
if (boat.getMarkRoundingTime() != null) {
DateFormat format = new SimpleDateFormat("mm:ss");
String elapsedTime = format
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundingTime());
legTimeObject.setText("Last mark: " + elapsedTime);
} else {
legTimeObject.setText("Last mark: -");
}
}
public void move() {
double dx = xIncrement * framesToMove;
double dy = yIncrement * framesToMove;
distanceTravelled += Math.abs(dx) + Math.abs(dy);
moveGroupBy(xIncrement, yIncrement);
framesToMove = framesToMove - 1;
if (framesToMove <= 0) {
isStopped = true;
}
if (distanceTravelled > 70) {
distanceTravelled = 0d;
if (lastPoint != null) {
Line l = new Line(
lastPoint.getX(),
lastPoint.getY(),
boatPoly.getLayoutX(),
boatPoly.getLayoutY()
);
l.getStrokeDashArray().setAll(3d, 7d);
l.setStroke(boat.getColour());
l.setCache(true);
l.setCacheHint(CacheHint.SPEED);
lineGroup.getChildren().add(l);
}
if (destinationSet) {
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
}
}
wake.updatePosition(1000 / 60);
}
/**
* Calculates the rotational velocity required to reach the rotationalGoal from the
* currentRotation.
*/
protected Double calculateRotationalVelocity(Double rotationalGoal) {
Double rotationalVelocity = 0.0;
if (Math.abs(rotationalGoal - lastRotation) > 180) {
if (rotationalGoal - lastRotation >= 0.0) {
rotationalVelocity = ((rotationalGoal - lastRotation) - 360) / 200;
} else {
rotationalVelocity = (360 + (rotationalGoal - lastRotation)) / 200;
}
} else {
rotationalVelocity = (rotationalGoal - lastRotation) / 200;
}
//Sometimes the rotation is too large to be realistic. In that case just do it instantly.
if (Math.abs(rotationalVelocity) > 1) {
rotationalVelocity = 0.0;
}
return rotationalVelocity;
} }
/** /**
* Sets the destination of the boat and the headng it should have once it reaches * 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 newXValue The X co-ordinate the boat needs to move to.
* @param newYValue The Y 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 rotation Rotation to move graphics to.
* @param raceIds RaceID of the object to move. * @param timeValid the time the position values are valid for
*/ */
public void setDestination (double newXValue, double newYValue, double rotation, double groundSpeed, int... raceIds) { public void setDestination(double newXValue, double newYValue, double rotation,
if (hasRaceId(raceIds)) { double groundSpeed, long timeValid, double frameRate, long id) {
if (setToInitialLocation) { if (lastTimeValid == 0) {
destinationSet = true; lastTimeValid = timeValid - 200;
boat.setVelocity(groundSpeed); moveTo(newXValue, newYValue, rotation);
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. framesToMove = Math.round((frameRate / (1000.0f / (timeValid - lastTimeValid))));
if (!isMaximized) { double dx = newXValue - boatPoly.getLayoutX();
setToInitialLocation = false; double dy = newYValue - boatPoly.getLayoutY();
wakeGenerationDelay = 2;
if(setCallCount-- == 0) { xIncrement = dx / framesToMove;
setCallCount = 5; yIncrement = dy / framesToMove;
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; destinationSet = true;
if (hasRaceId(raceIDs)) { Double rotationalVelocity = calculateRotationalVelocity(rotation);
double rotation = Math.abs(
Math.toDegrees( updateTimeTillNextMark();
Math.atan( updateLastMarkRoundingTime();
(newYValue - boatPoly.getLayoutY()) / (newXValue - boatPoly.getLayoutX())
) if (Math.abs(rotationalVelocity) > 0.075) {
) rotationalVelocity = 0.0;
); wake.rotate(rotation);
setDestination(newXValue, newYValue, rotation, groundSpeed, raceIDs);
} }
rotateTo(rotation);
wake.setRotationalVelocity(rotationalVelocity, groundSpeed);
velocityObject.setText(String.format("%.2f m/s", groundSpeed));
lastTimeValid = timeValid;
isStopped = false;
lastRotation = rotation;
} }
public void rotateTo (double rotation) {
currentRotation = rotation; /**
boatPoly.getTransforms().setAll(new Rotate(rotation)); * This function works out if a boat is going upwind or down wind. It looks at the boats current position, the next
* gates position and the current wind
* If bot the wind vector from the next gate and the boat from the next gate lay on the same side, then the boat is
* going up wind, if they are on different sides of the gate, then the boat is going downwind
* @param canvasController
*/
public Boolean isUpwindLeg(CanvasController canvasController, Mark nextMark) {
Double windAngle = StreamParser.getWindDirection();
GateMark thisGateMark = (GateMark) nextMark;
SingleMark nextMark1 = thisGateMark.getSingleMark1();
SingleMark nextMark2 = thisGateMark.getSingleMark2();
Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude());
Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
Point2D windTestPoint = GeometryUtils.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
Integer boatLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
Integer windLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint);
/*
If both the wind vector from the gate and the boat from the gate are on the same side of that gate, then the
boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going
with the wind.
*/
if (boatLineFuncResult == windLineFuncResult) {
return true;
} else {
return false;
}
} }
public void forceRotation () {
rotateTo (rotationalGoal); public void setIsSelected(Boolean isSelected) {
wake.rotate(rotationalGoal); this.isSelected = isSelected;
setTeamNameObjectVisible(isSelected);
setVelocityObjectVisible(isSelected);
setLineGroupVisible(isSelected);
setWakeVisible(isSelected);
setEstTimeToNextMarkObjectVisible(isSelected);
setLegTimeObjectVisible(isSelected);
setLayLinesVisible(isSelected);
} }
public void setTeamNameObjectVisible(Boolean visible) { public void setTeamNameObjectVisible(Boolean visible) {
teamNameObject.setVisible(visible); teamNameObject.setVisible(visible);
} }
@@ -287,6 +429,14 @@ public class BoatGroup extends RaceObject{
velocityObject.setVisible(visible); velocityObject.setVisible(visible);
} }
public void setEstTimeToNextMarkObjectVisible(Boolean visible) {
estTimeToNextMarkObject.setVisible(visible);
}
public void setLegTimeObjectVisible(Boolean visible) {
legTimeObject.setVisible(visible);
}
public void setLineGroupVisible(Boolean visible) { public void setLineGroupVisible(Boolean visible) {
lineGroup.setVisible(visible); lineGroup.setVisible(visible);
} }
@@ -295,22 +445,25 @@ public class BoatGroup extends RaceObject{
wake.setVisible(visible); wake.setVisible(visible);
} }
public Yacht getBoat() { public void setLayLinesVisible(Boolean visible) {
return boat; leftLayLine.setVisible(visible);
rightLayline.setVisible(visible);
} }
/** public void setLaylines(Line line1, Line line2) {
* Returns true if this BoatGroup contains at least one of the given IDs. this.leftLayLine = line1;
* this.rightLayline = line2;
* @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 ArrayList<Line> getLaylines() {
public boolean hasRaceId (int... raceIds) { ArrayList<Line> laylines = new ArrayList<>();
for (int id : raceIds) { laylines.add(leftLayLine);
if (id == boat.getSourceID()) laylines.add(rightLayline);
return true; return laylines;
} }
return false;
public Yacht getBoat() {
return boat;
} }
/** /**
@@ -318,41 +471,39 @@ public class BoatGroup extends RaceObject{
* *
* @return An array containing all ID's associated with this RaceObject. * @return An array containing all ID's associated with this RaceObject.
*/ */
public int[] getRaceIds () { public long getRaceId() {
return new int[] {boat.getSourceID()}; return boat.getSourceID();
} }
/** /**
* Due to javaFX limitations annotations associated with a boat that you want to appear below all boats in the * Due to javaFX limitations annotations associated with a boat that you want to appear below
* Z-axis need to be pulled out of the BoatGroup and added to the parent group of the BoatGroups. This function * all boats in the Z-axis need to be pulled out of the BoatGroup and added to the parent group
* returns these annotations as a group. * of the BoatGroups. This function returns these annotations as a group.
* *
* @return A group containing low priority annotations. * @return A group containing low priority annotations.
*/ */
public Group getLowPriorityAnnotations () { public Group getLowPriorityAnnotations() {
Group group = new Group(); Group group = new Group();
group.getChildren().addAll(wake, lineGroup); group.getChildren().addAll(wake, lineGroup);
return group; 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 public Double getBoatLayoutX() {
* listen to the iconified property of that stage and change it's behaviour upon minimization. Without setting the return boatPoly.getLayoutX();
* 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();
}
});
} }
}
public Double getBoatLayoutY() {
return boatPoly.getLayoutY();
}
public boolean isStopped() {
return isStopped;
}
@Override
public String toString() {
return boat.toString();
}
}
+2 -2
View File
@@ -3,10 +3,10 @@ package seng302.models;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
/** /**
* Created by ryan_ on 16/03/2017. * Enum for randomly generating colours.
*/ */
public enum Colors { public enum Colors {
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE; RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE;
static Integer index = 0; static Integer index = 0;
-172
View File
@@ -1,172 +0,0 @@
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;
}
}
-116
View File
@@ -1,116 +0,0 @@
package seng302.models;
import seng302.models.mark.SingleMark;
/**
* Represents the leg of a race.
*/
public class Leg {
private int heading;
private int distance;
private boolean isFinishingLeg;
private SingleMark startingSingleMark;
/**
* Create a new leg
*
* @param heading, the magnetic heading of this leg
* @param distance, the total distance of this leg in meters
* @param singleMark, the singleMark this leg starts on
*/
public Leg(int heading, int distance, SingleMark singleMark) {
this.heading = heading;
this.distance = distance;
this.startingSingleMark = singleMark;
this.isFinishingLeg = false;
}
/**
* Create a new leg
*
* @param heading, the magnetic heading of this leg
* @param distance, the total distance of this leg in meters
* @param markerName, the name of the marker this leg starts on
*/
public Leg(int heading, int distance, String markerName) {
this.heading = heading;
this.distance = distance;
this.startingSingleMark = new SingleMark(markerName);
this.isFinishingLeg = false;
}
/**
* Get the heading of this leg
* @return int
*/
public int getHeading() {
return this.heading;
}
/**
* Set the heading for this leg
* @param heading
*/
public void setHeading(int heading) {
this.heading = heading;
}
/**
* Get the total distance of this leg in meters
* @return int
*/
public int getDistance() {
return this.distance;
}
/**
* Set the distance of this leg in meters
* @param distance
*/
public void setDistance(int distance) {
this.distance = distance;
}
/**
* Returns the marker this leg started on
* @return SingleMark
*/
public SingleMark getMarker() {
return this.startingSingleMark;
}
/**
* Set the singleMark this leg starts on
* @param singleMark
*/
public void setMarker(SingleMark singleMark) {
this.startingSingleMark = singleMark;
}
/**
* Returns the name of the marker this leg started on
* @return String
*/
public String getMarkerLabel() {
return this.startingSingleMark.getName();
}
/**
* Specify whether or not the race finishes on this leg
*
* @param isFinishingLeg whether or not the race finishes on this leg
*/
public void setFinishingLeg(boolean isFinishingLeg) {
this.isFinishingLeg = isFinishingLeg;
}
/**
* Returns whether or not the race finishes after this leg
* @return true if this the race finishes after this leg
*/
public boolean getIsFinishingLeg() {
return this.isFinishingLeg;
}
}
@@ -0,0 +1,163 @@
package seng302.models;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
/**
* A static class for parsing and storing the polars. Will parse the whole polar table and also store the optimised
* upwind and downwind in separate tables here as well
* Created by wmu16 on 22/05/17.
*/
public final class PolarTable {
//A Polar table will consist of a wind speed key to a hashmap value of pairs of wind angles and boat speeds
private static HashMap<Double, HashMap<Double, Double>> polarTable;
private static HashMap<Double, HashMap<Double, Double>> upwindOptimal;
private static HashMap<Double, HashMap<Double, Double>> downwindOptimal;
private static int upTwaIndex;
private static int dnTwaIndex;
/**
* Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed.
* These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap
* as a value
* @param file containing the polar csv information
*/
public static void parsePolarFile(String file) {
polarTable = new HashMap<>();
upwindOptimal = new HashMap<>();
downwindOptimal = new HashMap<>();
String line;
Boolean isHeaderLine = true;
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
while ((line = br.readLine()) != null) {
String[] thisLine = line.split(",");
//Initial line in file
if (isHeaderLine) {
deduceHeaders(thisLine);
isHeaderLine = false;
} else {
HashMap<Double, Double> thisPolar = new HashMap<>();
HashMap<Double, Double> thisUpWindPolar = new HashMap<>();
HashMap<Double, Double> thisDnWindPolar = new HashMap<>();
Double thisWindSpeed = Double.parseDouble(thisLine[0]);
// -3 <== -1 for length -1, and a further -2 as we iterate in pairs of 2 so finish before final 2
for (int i = 1; i < thisLine.length; i += 2) {
Double thisWindAngle = Double.parseDouble(thisLine[i]);
Double thisBoatSpeed = Double.parseDouble(thisLine[i + 1]);
thisPolar.put(thisWindAngle, thisBoatSpeed);
if (i == upTwaIndex) {
thisUpWindPolar.put(thisWindAngle, thisBoatSpeed);
} else if (i == dnTwaIndex) {
thisDnWindPolar.put(thisWindAngle, thisBoatSpeed);
}
}
polarTable.put(thisWindSpeed, thisPolar);
upwindOptimal.put(thisWindSpeed, thisUpWindPolar);
downwindOptimal.put(thisWindSpeed, thisDnWindPolar);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Parses the header line of a polar file
* @param thisLine The line which is the header of a polar file
*/
private static void deduceHeaders(String[] thisLine) {
for (int i = 0; i < thisLine.length; i++) {
String thisItem = thisLine[i];
if (thisItem.toLowerCase().startsWith("uptwa")) {
upTwaIndex = i;
}
else if (thisItem.toLowerCase().startsWith("dntwa")) {
dnTwaIndex = i;
}
}
}
/**
* @return The entire polar table
*/
public static HashMap<Double, HashMap<Double, Double>> getPolarTable() {
return polarTable;
}
/**
* @return The polar table just containing the optimal upwind values
*/
public static HashMap<Double, HashMap<Double, Double>> getUpwindOptimal() {
return upwindOptimal;
}
/**
* @return The polar table just containing the optimal downwind values
*/
public static HashMap<Double, HashMap<Double, Double>> getDownwindOptimal() {
return downwindOptimal;
}
/**
* Will raise an exception if a polar table has just one row of data
* @param thisWindSpeed The current wind speed
* @return HashMap containing just the optimal upwind angle and resulting boat speed
*/
public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) {
Double polarWindSpeed = getClosestMatch(thisWindSpeed);
return upwindOptimal.get(polarWindSpeed);
}
/**
* Will raise an exception if a polar table has just one row of data
* @param thisWindSpeed The current wind speed
* @return HashMap containing just the optimal downwind angle and resulting boat speed
*/
public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) {
Double polarWindSpeed = getClosestMatch(thisWindSpeed);
return downwindOptimal.get(polarWindSpeed);
}
private static Double getClosestMatch(Double thisWindSpeed) {
ArrayList<Double> windValues = new ArrayList<>(polarTable.keySet());
Double lowerVal = windValues.get(0);
Double upperVal = windValues.get(1);
for(int i = 0; i < windValues.size() - 1; i++) {
lowerVal = windValues.get(i);
upperVal = windValues.get(i+1);
if (thisWindSpeed <= upperVal) {
break;
}
}
Double lowerDiff = Math.abs(lowerVal - thisWindSpeed);
Double upperDiff = Math.abs(upperVal - thisWindSpeed);
return (lowerDiff <= upperDiff) ? lowerVal : upperVal;
}
}
-198
View File
@@ -1,198 +0,0 @@
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;
}
}
@@ -1,87 +0,0 @@
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 ();
}
@@ -1,31 +0,0 @@
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;
}
}
+55 -48
View File
@@ -1,30 +1,34 @@
package seng302.models; package seng302.models;
import javafx.scene.CacheHint;
import javafx.scene.Group; import javafx.scene.Group;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Arc; import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType; import javafx.scene.shape.ArcType;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.transform.Rotate; 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 * A group containing objects used to represent wakes onscreen. Contains functionality for their animation.
* 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 { class Wake extends Group {
private int numWakes = 5; //The number of wakes
private double[] velocities = new double[13]; private int numWakes = 8;
//The total possible difference between the first wake and the last. Increasing/Decreasing this will make wakes fan out more/less.
private final double MAX_DIFF = 75;
//Increasing/decreasing this will alter the speed that wakes converge when the heading stop changing. Anything over about 1500 may cause oscillation.
private final int UNIFICATION_SPEED = 750;
private Arc[] arcs = new Arc[numWakes]; private Arc[] arcs = new Arc[numWakes];
private double[] rotationalVelocities = new double[numWakes];
private double[] rotations = new double[numWakes]; private double[] rotations = new double[numWakes];
private int[] velocityIndices = new int[numWakes]; private double baseRad;
private double sum = 0;
private static double max;
/** /**
* Create a wake at the given location. * Create a wake at the given location.
*
* @param startingX x location where the tip of wake arcs will be. * @param startingX x location where the tip of wake arcs will be.
* @param startingY y location where the tip of wake arcs will be. * @param startingY y location where the tip of wake arcs will be.
*/ */
@@ -34,74 +38,77 @@ class Wake extends Group {
Arc arc; Arc arc;
for (int i = 0; i < numWakes; i++) { 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. //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); arc = new Arc(0, 0, 0, 0, -110, 40);
//Opacity increases from 0.5 -> 0 evenly over the 5 wake arcs. arc.setCache(true);
arc.setFill(new Color(0.18, 0.7, 1.0, 1.0 + -0.175 * i)); arc.setCacheHint(CacheHint.SPEED);
arc.setType(ArcType.ROUND); arc.setType(ArcType.OPEN);
arc.setStroke(new Color(0.18, 0.7, 1.0, 1.0 + (-0.99 / numWakes * i)));
arc.setStrokeWidth(3.0);
arc.setStrokeLineCap(StrokeLineCap.ROUND);
arc.setFill(new Color(0.0, 0.0, 0.0, 0.0));
baseRad = (20 / numWakes);
arcs[i] = arc; arcs[i] = arc;
} }
super.getChildren().addAll(arcs); super.getChildren().addAll(arcs);
} }
/** /**
* Sets the rotationalVelocity of each arc. Each arc is 3 velocities behind the next smallest arc. The smallest uses * Sets the rotationalVelocity of each arc.
* the latest given velocity. *
* @param rotationalVelocity The rotationalVelocity the wake should move at. * @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 * @param velocity The real world velocity of the boat in m/s.
* 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) { void setRotationalVelocity(double rotationalVelocity, double velocity) {
sum -= Math.abs(velocities[(velocityIndices[0] + 10) % 13]); rotationalVelocities[0] = rotationalVelocity;
sum += Math.abs(rotationalVelocity); for (int i = 1; i < numWakes; i++) {
max = Math.max(max, rotationalVelocity); double wakeSeparationRad = Math.toRadians(rotations[i - 1] - rotations[i]);
if (sum < (max / 3)) double shortestDistance = Math.atan2(
rotate (rotationGoal); //In relatively straight segments the wake snaps to match the boats current position. Math.sin(wakeSeparationRad),
//This stops the wake from eventually becoming out of sync with the boat. Math.cos(wakeSeparationRad)
//This accounts for rogue rotations that are greater than what would be realistic. Value is kinda rough. );
//Basically just for our internal mock. double distDeg = Math.toDegrees(shortestDistance);
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. if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) {
double baseRad = 20; rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
double rad;
for (Arc arc :arcs) { } else {
rad = baseRad + velocity; if (distDeg < (MAX_DIFF / numWakes))
rotationalVelocities[i] = rotationalVelocities[i - 1] * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
else
rotationalVelocities[i] = rotationalVelocities[i - 1];
}
}
double rad = baseRad + velocity;
for (Arc arc : arcs) {
arc.setRadiusX(rad); arc.setRadiusX(rad);
arc.setRadiusY(rad); arc.setRadiusY(rad);
baseRad += 5 + (velocity / 2); rad += (20 / numWakes) + (velocity / 2);
} }
} }
/** /**
* Arcs rotate based on the distance they would have travelled over the supplied time interval. * 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. * @param timeInterval the time interval, in microseconds, that the wake should move.
*/ */
void updatePosition (long timeInterval) { void updatePosition(long timeInterval) {
for (int i = 0; i < numWakes; i++) { for (int i = 0; i < numWakes; i++) {
rotations[i] = rotations[i] + velocities[velocityIndices[i]] * timeInterval; rotations[i] = rotations[i] + rotationalVelocities[i] * timeInterval;
arcs[i].getTransforms().setAll(new Rotate(rotations[i])); arcs[i].getTransforms().setAll(new Rotate(rotations[i]));
} }
} }
/** /**
* Rotate all wakes to the given rotation. * Rotate all wakes to the given rotation.
*
* @param rotation the from north angle in degrees to rotate to. * @param rotation the from north angle in degrees to rotate to.
*/ */
void rotate (double rotation) { void rotate(double rotation) {
for (int i = 0; i < arcs.length; i++) { for (int i = 0; i < arcs.length; i++) {
rotations[i] = rotation; rotations[i] = rotation;
rotationalVelocities[i] = 0;
arcs[i].getTransforms().setAll(new Rotate(rotation)); arcs[i].getTransforms().setAll(new Rotate(rotation));
} }
} }
} }
+52 -11
View File
@@ -1,20 +1,25 @@
package seng302.models; package seng302.models;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import seng302.models.mark.Mark;
import seng302.controllers.RaceViewController;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import seng302.models.stream.StreamParser;
import seng302.models.stream.XMLParser.RaceXMLObject.Corner;
/** /**
* Yacht class for the racing boat. * Yacht class for the racing boat.
* *
* Class created to store more variables (eg. boat statuses) compared to the XMLParser boat class, * Class created to store more variables (eg. boat statuses) compared to the XMLParser boat class,
* also done outside Boat class because some old variables are not used anymore. * also done outside Boat class because some old variables are not used anymore.
*/ */
public class Yacht { public class Yacht {
// Used in boat group
private Color colour; private Color colour;
private double velocity; private double velocity;
private Integer markLastPast;
private String boatType; private String boatType;
private Integer sourceID; private Integer sourceID;
@@ -30,22 +35,27 @@ public class Yacht {
private Long estimateTimeAtNextMark; private Long estimateTimeAtNextMark;
private Long estimateTimeAtFinish; private Long estimateTimeAtFinish;
private String position; private String position;
// Mark rounding
private Long markRoundingTime;
private Mark lastMarkRounded;
private Mark nextMark;
/** /**
* Used in EventTest and RaceTest. * Used in EventTest and RaceTest.
* *
* @param boatName Create a yacht object with name. * @param boatName Create a yacht object with name.
*/ */
public Yacht (String boatName) { public Yacht(String boatName) {
this.boatName = boatName; this.boatName = boatName;
} }
/** /**
* Used in BoatGroupTest. * Used in BoatGroupTest.
* *
* @param boatName The name of the team sailing the boat * @param boatName The name of the team sailing the boat
* @param boatVelocity The speed of the boat in meters/second * @param boatVelocity The speed of the boat in meters/second
* @param shortName A shorter version of the teams name * @param shortName A shorter version of the teams name
*/ */
public Yacht(String boatName, double boatVelocity, String shortName, int id) { public Yacht(String boatName, double boatVelocity, String shortName, int id) {
this.boatName = boatName; this.boatName = boatName;
@@ -54,30 +64,37 @@ public class Yacht {
this.sourceID = id; this.sourceID = id;
} }
public Yacht(String boatType, Integer sourceID, String hullID, String shortName, String boatName, String country) { public Yacht(String boatType, Integer sourceID, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType; this.boatType = boatType;
this.sourceID = sourceID; this.sourceID = sourceID;
this.hullID = hullID; this.hullID = hullID;
this.shortName = shortName; this.shortName = shortName;
this.boatName = boatName; this.boatName = boatName;
this.country = country; this.country = country;
this.position = "-";
} }
public String getBoatType() { public String getBoatType() {
return boatType; return boatType;
} }
public Integer getSourceID() { public Integer getSourceID() {
return sourceID; return sourceID;
} }
public String getHullID() { public String getHullID() {
return hullID; return hullID;
} }
public String getShortName() { public String getShortName() {
return shortName; return shortName;
} }
public String getBoatName() { public String getBoatName() {
return boatName; return boatName;
} }
public String getCountry() { public String getCountry() {
return country; return country;
} }
@@ -95,6 +112,9 @@ public class Yacht {
} }
public void setLegNumber(Integer legNumber) { public void setLegNumber(Integer legNumber) {
if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus(sourceID)) {
RaceViewController.updateYachtPositionSparkline(this, legNumber);
}
this.legNumber = legNumber; this.legNumber = legNumber;
} }
@@ -149,7 +169,7 @@ public class Yacht {
this.colour = colour; this.colour = colour;
} }
public double getVelocity() { public Double getVelocity() {
return velocity; return velocity;
} }
@@ -157,11 +177,32 @@ public class Yacht {
this.velocity = velocity; this.velocity = velocity;
} }
public Integer getMarkLastPast() { public Long getMarkRoundingTime() {
return markLastPast; return markRoundingTime;
} }
public void setMarkLastPast(Integer markLastPast) { public void setMarkRoundingTime(Long markRoundingTime) {
this.markLastPast = markLastPast; this.markRoundingTime = markRoundingTime;
} }
public Mark getLastMarkRounded() {
return lastMarkRounded;
}
public void setLastMarkRounded(Mark lastMarkRounded) {
this.lastMarkRounded = lastMarkRounded;
}
@Override
public String toString() {
return boatName;
}
public void setNextMark(Mark nextMark) {
this.nextMark = nextMark;
}
public Mark getNextMark(){
return nextMark;
}
} }
@@ -0,0 +1,44 @@
package seng302.models.map;
/**
* The Boundary class represents a rectangle territorial boundary on a map. It
* contains four extremity double values(N, E, S, W). N and S are represented as
* latitudes in radians. E and W are represented as longitudes in radians.
*
* Created by Haoming on 10/5/17
*/
public class Boundary {
private double northLat, eastLng, southLat, westLng;
public Boundary(double northLat, double eastLng, double southLat, double westLng) {
this.northLat = northLat;
this.eastLng = eastLng;
this.southLat = southLat;
this.westLng = westLng;
}
double getCentreLat() {
return (northLat + southLat) / 2;
}
double getCentreLng() {
return (eastLng + westLng) / 2;
}
double getNorthLat() {
return northLat;
}
double getEastLng() {
return eastLng;
}
double getSouthLat() {
return southLat;
}
double getWestLng() {
return westLng;
}
}
@@ -0,0 +1,104 @@
package seng302.models.map;
import javafx.scene.image.Image;
import javax.net.ssl.HttpsURLConnection;
import java.net.URL;
import java.lang.Math;
/**
* CanvasMap retrieves a map image with given geo boundary from Google Map server.
* By passing a rectangle like geo boundary, it returns a map image with the
* highest resolution. However, due to free quote account usage limit, the maximum
* resolution is only 1280 * 1280.
*
* Created by Haoming on 15/5/2017
*/
public class CanvasMap {
private Boundary boundary;
private long width, height; // desired image size
private int zoom;
private String KEY = "AIzaSyC-5oOShMCY5Oy_9L7guYMPUPFHDMr37wE";
public CanvasMap(Boundary boundary) {
this.boundary = boundary;
calculateOptimalMapSize();
}
public Image getMapImage() {
try {
URL url = new URL(getRequest());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
return new Image(connection.getInputStream());
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private String getRequest() {
StringBuilder sb = new StringBuilder();
sb.append("https://maps.googleapis.com/maps/api/staticmap?");
sb.append(String.format("center=%f,%f", boundary.getCentreLat(), boundary.getCentreLng()));
sb.append(String.format("&zoom=%d", zoom));
sb.append(String.format("&size=%dx%d&scale=2", width, height));
sb.append("&style=feature:all|element:labels|visibility:off"); // hide all labels on map
// sb.append(String.format("&markers=%f,%f", boundary.getSouthLat(), boundary.getWestLng()));
// sb.append(String.format("&key=%s", KEY));
return sb.toString();
}
private void calculateOptimalMapSize() {
for (int z = 20; z > 0; z--) {
MapSize mapSize = getMapSize(z, boundary);
zoom = z;
width = mapSize.width;
height = mapSize.height;
// if map size is valid, exit the loop as we have the highest resolution
if (mapSize.isValid()) break;
}
}
private MapSize getMapSize(int zoom, Boundary boundary) {
double scale = Math.pow(2, zoom);
MapGeo geoSW = new MapGeo(boundary.getSouthLat(), boundary.getWestLng());
MapGeo geoNE = new MapGeo(boundary.getNorthLat(), boundary.getEastLng());
MapPoint pointSW = MercatorProjection.toMapPoint(geoSW);
MapPoint pointNE = MercatorProjection.toMapPoint(geoNE);
return new MapSize(Math.abs(pointNE.getX() - pointSW.getX()) * scale,
Math.abs(pointNE.getY() - pointSW.getY()) * scale);
}
class MapSize {
long width, height;
MapSize(double width, double height) {
this.width = Math.round(width);
this.height = Math.round(height);
}
/**
* Map size is valid when width and height are both less than 640 pixels
* @return true if both dimensions are less than 640px
*/
boolean isValid() {
return Math.max(width, height) <= 640;
}
}
public long getWidth() {
return width;
}
public long getHeight() {
return height;
}
public int getZoom() {
return zoom;
}
}
@@ -0,0 +1,31 @@
package seng302.models.map;
/**
* A class represent Geo location (latitude, longitude).
* Created by Haoming on 15/5/2017
*/
class MapGeo {
private double lat, lng;
MapGeo(double lat, double lng) {
this.lat = lat;
this.lng = lng;
}
double getLat() {
return lat;
}
void setLat(double lat) {
this.lat = lat;
}
double getLng() {
return lng;
}
void setLng(double lng) {
this.lng = lng;
}
}
@@ -0,0 +1,31 @@
package seng302.models.map;
/**
* A class represent euclidean planar point (x, y)
* Created by Haoming on 15/5/2017
*/
class MapPoint {
private double x, y;
MapPoint(double x, double y) {
this.x = x;
this.y = y;
}
double getX() {
return x;
}
void setX(double x) {
this.x = x;
}
double getY() {
return y;
}
void setY(double y) {
this.y = y;
}
}
@@ -0,0 +1,52 @@
package seng302.models.map;
/**
* An utility class useful to convert between Geo locations and Mercator projection
* planar coordinates.
* Created by Haoming on 15/5/2017
*/
public class MercatorProjection {
private static final double MERCATOR_RANGE = 256;
private static final double pixelsPerLngDegree = MERCATOR_RANGE / 360.0;
private static final double pixelsPerLngRadian = MERCATOR_RANGE / (2 * Math.PI);
/**
* A help function keeps the value in bound between -0.9999 and 0.9999.
* @param value in bound value
* @return the value in bound
*/
private static double bound(double value) {
return Math.min(Math.max(value, -0.9999), 0.9999);
}
/**
* Projects a Geo Location (lat, lng) on a planar
* @param geo MapGeo (lat, lng) location to be projected
* @return the projection GeoPoint (x, y) on planar
*/
public static MapPoint toMapPoint(MapGeo geo) {
MapPoint point = new MapPoint(0, 0);
MapPoint origin = new MapPoint(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
point.setX(origin.getX() + geo.getLng() * pixelsPerLngDegree);
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
// 89.189. This is about a third of a tile past the edge of the world tile.
double sinY = bound(Math.sin(Math.toRadians(geo.getLat())));
point.setY(origin.getY() + 0.5 * Math.log((1 + sinY) / (1 - sinY)) * (-pixelsPerLngRadian));
return point;
}
/**
* Converts the planar projection (x, y) back to Geo Location (lat, lng)
* @param point MapPoint (x, y) to be converted back
* @return the original Geo location converted from the given projection point
*/
public static MapGeo toMapGeo(MapPoint point) {
MapPoint origin = new MapPoint(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
double lng = (point.getX() - origin.getX()) / pixelsPerLngDegree;
double latRadians = (point.getY() - origin.getY()) / (-pixelsPerLngRadian);
double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2.0);
return new MapGeo(lat, lng);
}
}
@@ -0,0 +1,23 @@
package seng302.models.map;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import java.net.URL;
import java.util.ResourceBundle;
public class TestMapController implements Initializable{
@FXML
private Canvas mapCanvas;
@Override
public void initialize(URL location, ResourceBundle resources) {
GraphicsContext gc = mapCanvas.getGraphicsContext2D();
Boundary bound = new Boundary(57.662943, 11.848501, 57.673945, 11.824966);
CanvasMap canvasMap = new CanvasMap(bound);
gc.drawImage(canvasMap.getMapImage(), 0, 0, canvasMap.getWidth(), canvasMap.getHeight());
}
}
@@ -16,8 +16,8 @@ public class GateMark extends Mark {
* @param singleMark1 one single mark inside of the gate mark * @param singleMark1 one single mark inside of the gate mark
* @param singleMark2 the second 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) { public GateMark(String name, MarkType type, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude, int compoundMarkID) {
super(name, type, latitude, longitude); super(name, type, latitude, longitude, compoundMarkID);
this.singleMark1 = singleMark1; this.singleMark1 = singleMark1;
this.singleMark2 = singleMark2; this.singleMark2 = singleMark2;
} }
@@ -39,12 +39,10 @@ public class GateMark extends Mark {
} }
public double getLatitude(){ public double getLatitude(){
//return (this.getSingleMark1().getLatitude() + this.getSingleMark2().getLatitude()) / 2;
return (this.getSingleMark1().getLatitude()); return (this.getSingleMark1().getLatitude());
} }
public double getLongitude(){ public double getLongitude(){
//return (this.getSingleMark1().getLongitude() + this.getSingleMark2().getLongitude()) / 2;
return (this.getSingleMark1().getLongitude()); return (this.getSingleMark1().getLongitude());
} }
+34 -22
View File
@@ -10,25 +10,28 @@ public abstract class Mark {
private MarkType markType; private MarkType markType;
private double latitude; private double latitude;
private double longitude; private double longitude;
private int id; private long id;
private int compoundMarkID;
/** /**
* Create a mark instance by passing its name and type * Create a mark instance by passing its name and type
* @param name the name of the mark * @param name the name of the mark
* @param markType the type of mark. either GATE_MARK or SINGLE_MARK. * @param markType the type of mark. either GATE_MARK or SINGLE_MARK.
*/ */
public Mark (String name, MarkType markType, int id) { public Mark (String name, MarkType markType, int sourceID, int compoundMarkID) {
this.name = name; this.name = name;
this.markType = markType; this.markType = markType;
this.id = id; this.id = sourceID;
this.compoundMarkID = compoundMarkID;
} }
public Mark(String name, MarkType markType, double latitude, double longitude) { public Mark(String name, MarkType markType, double latitude, double longitude, int compoundMarkID) {
this.name = name; this.name = name;
this.markType = markType; this.markType = markType;
this.latitude = latitude; this.latitude = latitude;
this.longitude = longitude; this.longitude = longitude;
id = 0; this.id = 0;
this.compoundMarkID = compoundMarkID;
} }
/** /**
@@ -47,20 +50,24 @@ public abstract class Mark {
} }
/** /**
* Calculate the heading in radians from geographical location with latitude1, longitude 1 to geographical * Calculate the heading in radians from geographical location with latitude1, longitude 1 to
* latitude2, longitude 2 * geographical latitude2, longitude 2
*
* @param longitude1 Longitude of first point in degrees * @param longitude1 Longitude of first point in degrees
* @param longitude2 Longitude of second point in degrees * @param longitude2 Longitude of second point in degrees
* @param latitude1 Latitude of first point in degrees * @param latitude1 Latitude of first point in degrees
* @param latitude2 Latitude of first point in degrees * @param latitude2 Latitude of first point in degrees
* @return Heading in radians * @return Heading in radians
*/ */
public static double calculateHeadingRad (Double latitude1, Double longitude1, Double latitude2, Double longitude2) { public static double calculateHeadingRad(Double latitude1, Double longitude1, Double latitude2,
Double longitude2) {
latitude1 = Math.toRadians(latitude1); latitude1 = Math.toRadians(latitude1);
latitude2 = Math.toRadians(latitude2); latitude2 = Math.toRadians(latitude2);
Double longDiff= Math.toRadians(longitude2-longitude1); Double longDiff = Math.toRadians(longitude2 - longitude1);
Double y = Math.sin(longDiff)*Math.cos(latitude2); 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); Double x =
Math.cos(latitude1) * Math.sin(latitude2) - Math.sin(latitude1) * Math.cos(latitude2)
* Math.cos(longDiff);
return Math.atan2(y, x); return Math.atan2(y, x);
} }
@@ -80,33 +87,35 @@ public abstract class Mark {
} }
/** /**
* Calculate the distance in meters from geographical location with latitude1, longitude 1 to geographical * Calculate the distance in meters from geographical location with latitude1, longitude 1 to
* latitude2, longitude 2 * geographical latitude2, longitude 2
* *
* @param longitude1 Longitude of first point in degrees * @param longitude1 Longitude of first point in degrees
* @param longitude2 Longitude of second point in degrees * @param longitude2 Longitude of second point in degrees
* @param latitude1 Latitude of first point in degrees * @param latitude1 Latitude of first point in degrees
* @param latitude2 Latitude of first point in degrees * @param latitude2 Latitude of first point in degrees
* @return Distance in meters * @return Distance in meters
*/ */
public static Double calculateDistance (Double latitude1, Double longitude1, Double latitude2, Double longitude2) { public static Double calculateDistance(Double latitude1, Double longitude1, Double latitude2,
Double longitude2) {
Double theta = longitude1 - longitude2; Double theta = longitude1 - longitude2;
Double dist = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) + 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(latitude1)) * Math.cos(Math.toRadians(latitude2)) *
Math.cos(Math.toRadians(theta)); Math.cos(Math.toRadians(theta));
dist = Math.acos(dist); dist = Math.acos(dist);
dist = Math.toDegrees(dist); dist = Math.toDegrees(dist);
dist = dist * 60 * 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute) dist = dist * 60
* 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute)
dist = dist * 1609.344; //ratio of miles to metres dist = dist * 1609.344; //ratio of miles to metres
return dist; return dist;
} }
public String getName() { public String getName() {
return name; return name;
} }
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
public MarkType getMarkType() { public MarkType getMarkType() {
@@ -125,7 +134,7 @@ public abstract class Mark {
return longitude; return longitude;
} }
public int getId() { public long getId() {
return id; return id;
} }
@@ -133,4 +142,7 @@ public abstract class Mark {
this.id = id; this.id = id;
} }
public int getCompoundMarkID() {
return compoundMarkID;
}
} }
+107 -175
View File
@@ -1,20 +1,19 @@
package seng302.models.mark; package seng302.models.mark;
import java.util.ArrayList;
import java.util.List;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Circle; import javafx.scene.shape.Circle;
import javafx.scene.shape.Line; import javafx.scene.shape.Line;
import javafx.scene.transform.Rotate; import seng302.GeometryUtils;
import seng302.models.RaceObject;
import java.util.ArrayList;
import java.util.List;
/** /**
* Created by CJIRWIN on 26/04/2017. * Grouping of javaFX objects needed to represent a Mark on screen.
*/ */
public class MarkGroup extends RaceObject { public class MarkGroup extends Group {
private static int MARK_RADIUS = 5; private static int MARK_RADIUS = 5;
private static int LINE_THICKNESS = 2; private static int LINE_THICKNESS = 2;
@@ -23,14 +22,13 @@ public class MarkGroup extends RaceObject {
private List<Mark> marks = new ArrayList<>(); private List<Mark> marks = new ArrayList<>();
private Mark mainMark; private Mark mainMark;
private double[] nodePixelVelocitiesX;
private double[] nodePixelVelocitiesY;
private Point2D[] nodeDestinations;
public MarkGroup (Mark mark, Point2D... points) { /**
nodePixelVelocitiesX = new double[points.length]; * Constructor for singleMark groups
nodePixelVelocitiesY = new double[points.length]; * @param mark
nodeDestinations = new Point2D[points.length]; * @param points
*/
public MarkGroup (SingleMark mark, Point2D points) {
marks.add(mark); marks.add(mark);
mainMark = mark; mainMark = mark;
Color color = Color.BLACK; Color color = Color.BLACK;
@@ -40,192 +38,126 @@ public class MarkGroup extends RaceObject {
color = Color.RED; color = Color.RED;
} }
Circle markCircle; Circle markCircle;
if (mark.getMarkType() == MarkType.SINGLE_MARK) { markCircle = new Circle(
markCircle = new Circle( points.getX(),
points[0].getX(), points.getY(),
points[0].getY(), MARK_RADIUS,
MARK_RADIUS, color
color );
); super.getChildren().add(markCircle);
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( public void addLaylines(Line line1, Line line2) {
points[0].getX(),
points[0].getY(),
MARK_RADIUS,
color
);
nodeDestinations[0] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY());
super.getChildren().add(markCircle);
markCircle = new Circle( super.getChildren().addAll(line1, line2);
points[1].getX(), }
points[1].getY(),
MARK_RADIUS,
color public void removeLaylines() {
); ArrayList<Node> toRemove = new ArrayList<>();
nodeDestinations[1] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY()); for(Node node : super.getChildren()) {
super.getChildren().add(markCircle); if (node instanceof Line) {
Line line = new Line( Line layLine = (Line) node;
points[0].getX(),
points[0].getY(), /***
points[1].getX(), * OOHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHhhh
points[1].getY() */
); if (layLine.getStrokeWidth() == 0.5){
line.setStrokeWidth(LINE_THICKNESS); toRemove.add(layLine);
line.setStroke(color); }
if (mark.getMarkType() == MarkType.OPEN_GATE) {
line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN);
} }
super.getChildren().add(line);
} }
super.getChildren().removeAll(toRemove);
} }
public void setDestination (double x, double y, double rotation, double groundSpeed, int... raceIds) { public MarkGroup(GateMark mark, Point2D points1, Point2D points2) {
setDestination(x, y, 0, raceIds); marks.add(mark.getSingleMark1());
this.rotationalGoal = rotation; marks.add(mark.getSingleMark2());
calculateRotationalVelocity(); mainMark = mark;
} Color color = Color.BLACK;
if (mark.getName().equals("Start")){
public void setDestination (double x, double y, double groundSpeed, int... raceIds) { color = Color.GREEN;
for (int i = 0; i < marks.size(); i++) } else if (mark.getName().equals("Finish")){
for (int id : raceIds) color = Color.RED;
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));
} }
} Circle markCircle;
markCircle = new Circle(
points1.getX(),
points1.getY(),
MARK_RADIUS,
color
);
super.getChildren().add(markCircle);
public void updatePosition (long timeInterval) { markCircle = new Circle(
Circle markCircle = (Circle) super.getChildren().get(0); points2.getX(),
points2.getY(),
if (nodePixelVelocitiesX[0] > 0 && markCircle.getCenterX() > nodeDestinations[0].getX() || MARK_RADIUS,
nodePixelVelocitiesX[0] < 0 && markCircle.getCenterX() < nodeDestinations[0].getY()) color
nodePixelVelocitiesX[0] = 0; );
else if (nodePixelVelocitiesX[0] != 0) super.getChildren().add(markCircle);
markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[0] * timeInterval); Line line = new Line(
points1.getX(),
if (nodePixelVelocitiesY[0] > 0 && markCircle.getCenterY() > nodeDestinations[0].getY() || points1.getY(),
nodePixelVelocitiesY[0] < 0 && markCircle.getCenterY() < nodeDestinations[0].getY()) points2.getX(),
nodePixelVelocitiesY[0] = 0; points2.getY()
else if (nodePixelVelocitiesY[0] != 0) );
markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[0] * timeInterval); line.setStrokeWidth(LINE_THICKNESS);
line.setStroke(color);
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) { if (mark.getMarkType() == MarkType.OPEN_GATE) {
line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN);
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());
} }
super.getChildren().add(line);
//Laylines
// if (mark.)
// addLayLine(points1, 12.0, 90.0);
// addLayLine(points2, 12.0, 90.0);
} }
public void moveGroupBy (double x, double y, double rotation) { public void moveMarkTo (double x, double y, long raceId)
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) { {
Line line = (Line) super.getChildren().get(2); if (mainMark.getMarkType() == MarkType.SINGLE_MARK) {
for (int childIndex = 0; childIndex < 2; childIndex++){ Circle markCircle = (Circle) super.getChildren().get(0);
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.setCenterX(x);
markCircle.setCenterY(y); markCircle.setCenterY(y);
Line line = (Line) super.getChildren().get(2); } else {
line.setStartX(x); Circle markCircle1 = (Circle) super.getChildren().get(0);
line.setStartY(y); Circle markCircle2 = (Circle) super.getChildren().get(1);
line.setEndX(x); Line connectingLine = (Line) super.getChildren().get(2);
line.setEndY(y); if (marks.get(0).getId() == raceId) {
markCircle1.setCenterX(x);
markCircle1.setCenterY(y);
connectingLine.setStartX(markCircle1.getCenterX());
connectingLine.setStartY(markCircle1.getCenterY());
} else if (marks.get(1).getId() == raceId) {
markCircle2.setCenterX(x);
markCircle2.setCenterY(y);
connectingLine.setEndX(markCircle2.getCenterX());
connectingLine.setEndY(markCircle2.getCenterY());
}
} }
} }
public boolean hasRaceId (int... raceIds) { public boolean hasRaceId (int... raceIds) {
for (int id : raceIds) for (int id : raceIds)
for (Mark mark : marks) for (Mark mark : marks)
if (id == mark.getId()) if (id == mark.getId())
return true; return true;
return false; return false;
} }
public static int getMarkRadius() { public long[] getRaceIds () {
return MARK_RADIUS; long[] idArray = new long[marks.size()];
}
public static void setMarkRadius(int markRadius) {
MARK_RADIUS = markRadius;
}
public int[] getRaceIds () {
int[] idArray = new int[marks.size()];
int i = 0; int i = 0;
for (Mark mark : marks) for (Mark mark : marks)
idArray[i++] = mark.getId(); idArray[i++] = mark.getId();
return idArray; return idArray;
} }
}
public Mark getMainMark() {
return mainMark;
}
}
@@ -5,5 +5,5 @@ package seng302.models.mark;
* Created by Haoming Yin (hyi25) on 17/3/17. * Created by Haoming Yin (hyi25) on 17/3/17.
*/ */
public enum MarkType { public enum MarkType {
SINGLE_MARK, OPEN_GATE, CLOSED_GATE SINGLE_MARK, OPEN_GATE
} }
@@ -9,8 +9,6 @@ public class SingleMark extends Mark {
private double lat; private double lat;
private double lon; private double lon;
private String name; private String name;
private int id;
/** /**
* Represents a marker * Represents a marker
@@ -19,24 +17,12 @@ public class SingleMark extends Mark {
* @param lat, the latitude of the marker * @param lat, the latitude of the marker
* @param lon, the longitude of the marker * @param lon, the longitude of the marker
*/ */
public SingleMark(String name, double lat, double lon, int id) { public SingleMark(String name, double lat, double lon, int sourceID, int compoundMarkID) {
super(name, MarkType.SINGLE_MARK, id); super(name, MarkType.SINGLE_MARK, sourceID, compoundMarkID);
this.lat = lat; this.lat = lat;
this.lon = lon; 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() { public double getLatitude() {
return this.lat; return this.lat;
@@ -1,78 +0,0 @@
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;
}
}
}
@@ -1,145 +0,0 @@
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;
}
}
@@ -1,54 +0,0 @@
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;
}
}
@@ -1,53 +0,0 @@
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;
}
}
@@ -1,44 +0,0 @@
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;
}
}
@@ -1,583 +0,0 @@
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");
}
}
@@ -1,64 +0,0 @@
//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,668 @@
package seng302.models.stream;
import java.io.IOException;
import java.io.StringReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.PriorityBlockingQueue;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.models.Yacht;
import seng302.models.mark.Mark;
import seng302.models.stream.packets.BoatPositionPacket;
import seng302.models.stream.packets.StreamPacket;
/**
* The purpose of this class is to take in the stream of divided packets so they can be read
* and parsed in by turning the byte arrays into useful data. There are two public static hashmaps
* that are threadsafe so the visualiser can always access the latest speed and position available
* Created by kre39 on 23/04/17.
*/
public class StreamParser extends Thread {
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markLocations = new ConcurrentHashMap<>();
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatLocations = new ConcurrentHashMap<>();
private String threadName;
private Thread t;
private static boolean newRaceXmlReceived = false;
private static boolean raceStarted = false;
private static XMLParser xmlObject;
private static boolean raceFinished = false;
private static boolean streamStatus = false;
private static long timeSinceStart = -1;
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
private static Map<Integer, Yacht> boatsPos = new ConcurrentSkipListMap<>();
private static double windDirection = 0;
private static Double windSpeed = 0d;
private static Long currentTimeLong;
private static String currentTimeString;
private static boolean appRunning;
//CONVERSION CONSTANTS
private static final Double MS_TO_KNOTS = 1.94384;
/**
* Used to initialise the thread name and stream parser object so a thread can be executed
*
* @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 {
streamStatus = true;
xmlObject = new XMLParser();
while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) {
Thread.sleep(1);
}
while (appRunning) {
StreamPacket packet = StreamReceiver.packetBuffer.take();
parsePacket(packet);
Thread.sleep(1);
while (StreamReceiver.packetBuffer.peek() == null) {
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Used to start the stream parser thread when multithreading
*/
public void start() {
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:
newRaceXmlReceived = true;
extractXmlMessage(packet);
break;
case RACE_START_STATUS:
extractRaceStartStatus(packet);
break;
case YACHT_EVENT_CODE:
extractYachtEventCode(packet);
break;
case YACHT_ACTION_CODE:
extractYachtActionCode(packet);
break;
case CHATTER_TEXT:
extractChatterText(packet);
break;
case BOAT_LOCATION:
extractBoatLocation(packet);
break;
case MARK_ROUNDING:
extractMarkRounding(packet);
break;
case COURSE_WIND:
extractCourseWind(packet);
break;
case AVG_WIND:
extractAvgWind(packet);
break;
default:
break;
}
} catch (NullPointerException e) {
System.out.println("Error parsing packet");
e.printStackTrace();
}
}
/**
* Extracts the seq num used in the heartbeat packet
*
* @param packet Packet parsed in to use the payload
*/
private static void extractHeartBeat(StreamPacket packet) {
long heartbeat = bytesToLong(packet.getPayload());
}
private static String getTimeZoneString() {
Integer offset = xmlObject.getRegattaXML().getUtcOffset();
StringBuilder utcOffset = new StringBuilder();
utcOffset.append("GMT");
if (offset > 0) {
utcOffset.append("+");
utcOffset.append(offset);
} else if (offset < 0) {
utcOffset.append("-");
utcOffset.append(offset);
}
return utcOffset.toString();
}
/**
* Extracts the useful race status data from race status type packets. This method will also
* print to the console the current state of the race (if it has started/finished or is about to
* start), along side this it'll also display the amount of time since the race has started or
* time till it starts
*
* @param packet Packet parsed in to use the payload
*/
private static void extractRaceStatus(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long currentTime = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11));
int raceStatus = payload[11];
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18));
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
currentTimeLong = currentTime;
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
if (xmlObject.getRegattaXML() != null) {
format.setTimeZone(TimeZone.getTimeZone(getTimeZoneString()));
currentTimeString = format.format((new Date(currentTime)).getTime());
}
long timeTillStart =
((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000;
if (timeTillStart > 0) {
timeSinceStart = timeTillStart;
} else {
if (raceStatus == 4 || raceStatus == 8) {
raceFinished = true;
raceStarted = false;
} else if (!raceStarted) {
raceStarted = true;
raceFinished = false;
}
timeSinceStart = timeTillStart;
}
double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc...
windDirection = windDir / windDirFactor;
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
int noBoats = payload[22];
int raceType = payload[23];
for (int i = 0; i < noBoats; i++) {
long boatStatusSourceID = bytesToLong(
Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20)));
Yacht boat = boats.get((int) boatStatusSourceID);
boat.setBoatStatus((int) payload[28 + (i * 20)]);
setBoatLegPosition(boat, (int) payload[29 + (i * 20)]);
boat.setPenaltiesAwarded((int) payload[30 + (i * 20)]);
boat.setPenaltiesServed((int) payload[31 + (i * 20)]);
Long estTimeAtNextMark = bytesToLong(
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20)));
boat.setEstimateTimeAtNextMark(estTimeAtNextMark);
Long estTimeAtFinish = bytesToLong(
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
boat.setEstimateTimeAtFinish(estTimeAtFinish);
// boatsPos.put(estTimeAtFinish, boat);
// String boatStatus = "SourceID: " + boatStatusSourceID;
// boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)];
// boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)];
// boatStatus += "\nPenaltiesAwarded: " + (int)payload[29 + (i * 20)];
// boatStatus += "\nPenaltiesServed: " + (int)payload[30 + (i * 20)];
// boatStatus += "\nEstTimeAtNextMark: " + bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20)));
// boatStatus += "\nEstTimeAtFinish: " + bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20)));
// boatStatuses.add(boatStatus);
}
// if (isRaceStarted()) {
// int pos = 1;
// for (Yacht yacht : boatsPos.values()) {
// yacht.setPosition(String.valueOf(pos));
// pos++;
// }
// } else {
// for (Yacht yacht : boatsPos.values()) {
// yacht.setPosition("-");
// }
// }
}
private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
Integer placing = 1;
if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) {
for (Yacht boat : boats.values()) {
if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){
placing += 1;
}
}
updatingBoat.setPosition(placing.toString());
updatingBoat.setLegNumber(leg);
boatsPos.putIfAbsent(placing, updatingBoat);
boatsPos.replace(placing, updatingBoat);
} else if(updatingBoat.getLegNumber() == null){
updatingBoat.setPosition("1");
updatingBoat.setLegNumber(leg);
}
}
/**
* Used to extract the messages passed through with the display message packet
*
* @param packet Packet parsed in to use the payload
*/
private static void extractDisplayMessage(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int numOfLines = payload[3];
int totalLen = 0;
for (int i = 0; i < numOfLines; i++) {
int lineNum = payload[4 + totalLen];
int textLength = payload[5 + totalLen];
byte[] messageTextBytes = Arrays
.copyOfRange(payload, 6 + totalLen, 6 + textLength + totalLen);
String messageText = new String(messageTextBytes);
totalLen += 2 + textLength;
}
}
/**
* Used to read in the xml data. Will call the specific methods to create the course and boats
*
* @param packet Packet parsed in to use the payload
*/
private static void extractXmlMessage(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageType = payload[9];
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
String xmlMessage = new String(
(Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim();
//Create XML document Object
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = null;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xmlMessage)));
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
xmlObject.constructXML(doc, messageType);
if (messageType == 7) { //7 is the boat XML
boats = xmlObject.getBoatXML().getCompetingBoats();
}
if (messageType == 6) { //6 is race info xml
newRaceXmlReceived = true;
}
}
/**
* Extracts the race start status from the packet, currently is unused within the app but
* is here for potential future use
*
* @param packet Packet parsed in to use the payload
*/
private static void extractRaceStartStatus(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceStartTime = bytesToLong(Arrays.copyOfRange(payload, 9, 15));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 15, 19));
int notificationType = payload[19];
}
/**
* When a yacht event occurs this will parse the byte array to retrieve the necessary info,
* currently unused
*
* @param packet Packet parsed in to use the payload
*/
private static void extractYachtEventCode(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 17, 21));
int eventId = payload[21];
}
/**
* When a yacht action occurs this will parse the parse the byte array to retrieve the necessary
* info, currently unused
*
* @param packet Packet parsed in to use the payload
*/
private static void extractYachtActionCode(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
int eventId = payload[17];
}
/**
* Strips the message from the chatter text type packets, currently the message is unused
*
* @param packet Packet parsed in to use the payload
*/
private static void extractChatterText(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int messageType = payload[1];
int length = payload[2];
String message = new String(Arrays.copyOfRange(payload, 3, 3 + length));
}
/**
* 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) {
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
heading, groundSpeed);
//add a new priority que to the boatLocations HashMap
if (!boatLocations.containsKey(boatId)) {
boatLocations.put(boatId,
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
@Override
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
return (int) (p1.getTimeValid() - p2.getTimeValid());
}
}));
}
boatLocations.get(boatId).put(boatPacket);
} else if (deviceType == 3) {
BoatPositionPacket markPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
heading, groundSpeed);
//add a new priority que to the boatLocations HashMap
if (!markLocations.containsKey(boatId)) {
markLocations.put(boatId,
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
@Override
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
return (int) (p1.getTimeValid() - p2.getTimeValid());
}
}));
}
markLocations.get(boatId).put(markPacket);
}
}
/**
* This packet type is received when a mark or gate is rounded by a boat
*
* @param packet The packet containing the payload
*/
private static void extractMarkRounding(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
int boatStatus = payload[17];
int roundingSide = payload[18];
int markType = payload[19];
int markId = payload[20];
// assign mark rounding time to boat
boats.get((int)subjectId).setMarkRoundingTime(timeStamp);
for (Mark mark : xmlObject.getRaceXML().getAllCompoundMarks()) {
if (mark.getCompoundMarkID() == markId) {
boats.get((int)subjectId).setLastMarkRounded(mark);
}
}
}
/**
* 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 the wind speed in knots
* @return A double indicating the wind speed in knots
*/
public static Double getWindSpeed() {
return windSpeed;
}
/**
* returns stream time in formatted string format
*
* @return String of stream time
*/
public static String getCurrentTimeString() {
return currentTimeString;
}
/**
* used in boat position since tree map can sort position efficiently.
*
* @return a map of time to finish and boat.
*/
public static Map<Integer, Yacht> getBoatsPos() {
return boatsPos;
}
/**
* returns current time in stream in long
*
* @return a long value of current time
*/
public static Long getCurrentTimeLong() {
return currentTimeLong;
}
public static void appClose() {
appRunning = false;
}
/**
* Used to check if a new un-processed xml has been found, if so will return true before
* toggling off so that the next check will return false.
*
* @return the status of if new xml has been received
*/
public static boolean isNewRaceXmlReceived() {
if (newRaceXmlReceived) {
newRaceXmlReceived = false;
return true;
} else {
return false;
}
}
}
@@ -1,13 +1,11 @@
package seng302.models.parsers; package seng302.models.stream;
import seng302.models.parsers.packets.StreamPacket; import seng302.models.stream.packets.StreamPacket;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.Socket; import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
import java.util.zip.CRC32; import java.util.zip.CRC32;
@@ -46,7 +44,6 @@ public class StreamReceiver extends Thread {
} }
public void start () { public void start () {
System.out.println("[CLIENT] Starting " + threadName );
if (t == null) { if (t == null) {
t = new Thread (this, threadName); t = new Thread (this, threadName);
t.start (); t.start ();
@@ -157,6 +154,5 @@ public class StreamReceiver extends Thread {
public static void noMoreBytes(){ public static void noMoreBytes(){
moreBytes = false; moreBytes = false;
System.out.println("[CLIENT] Shutting down stream receiver");
} }
} }
@@ -1,27 +1,29 @@
package seng302.models.parsers; package seng302.models.stream;
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.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import seng302.models.Yacht;
import seng302.models.mark.GateMark;
import seng302.models.mark.Mark;
import seng302.models.mark.MarkType;
import seng302.models.mark.SingleMark;
/** /**
* Class to create an XML object from the XML Packet Messages. * Class to create an XML object from the XML Packet Messages.
* *
* Example usage: * Example usage:
* *
* Document doc; // some xml document * Document doc; // some xml document
* Integer xmlMessageType; // an Integer of value 5, 6, 7 * Integer xmlMessageType; // an Integer of value 5, 6, 7
*
* xmlP = new XMLParser(doc, xmlMessageType);
* RegattaXMLObject rXmlObj = xmlP.createRegattaXML(); // creates a regattaXML object.
* *
* xmlP = new XMLParser(doc, xmlMessageType);
* RegattaXMLObject rXmlObj = xmlP.createRegattaXML(); // creates a regattaXML object.
*/ */
public class XMLParser { public class XMLParser {
@@ -31,10 +33,12 @@ public class XMLParser {
private RegattaXMLObject regattaXML; private RegattaXMLObject regattaXML;
private BoatXMLObject boatXML; private BoatXMLObject boatXML;
public XMLParser() {} public XMLParser() {
}
/** /**
* Constructor for XMLParser * Constructor for XMLParser
*
* @param doc Document to create XML object. * @param doc Document to create XML object.
* @param messageType Defines if a message is a RegattaXML(5), RaceXML(6), BoatXML(7). * @param messageType Defines if a message is a RegattaXML(5), RaceXML(6), BoatXML(7).
*/ */
@@ -53,13 +57,22 @@ public class XMLParser {
} }
} }
public RaceXMLObject getRaceXML() { return raceXML; } public RaceXMLObject getRaceXML() {
public RegattaXMLObject getRegattaXML() { return regattaXML; } return raceXML;
public BoatXMLObject getBoatXML() { return boatXML; } }
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. * Returns the text content of a given child element tag, assuming it exists, as an Integer.
*
* @param ele Document Element with child elements. * @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements. * @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise. * @return Text content from tag if found, null otherwise.
@@ -75,6 +88,7 @@ public class XMLParser {
/** /**
* Returns the text content of a given child element tag, assuming it exists, as an String. * Returns the text content of a given child element tag, assuming it exists, as an String.
*
* @param ele Document Element with child elements. * @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements. * @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise. * @return Text content from tag if found, null otherwise.
@@ -90,6 +104,7 @@ public class XMLParser {
/** /**
* Returns the text content of a given child element tag, assuming it exists, as a Double. * Returns the text content of a given child element tag, assuming it exists, as a Double.
*
* @param ele Document Element with child elements. * @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements. * @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise. * @return Text content from tag if found, null otherwise.
@@ -105,9 +120,11 @@ public class XMLParser {
/** /**
* Returns the text content of an attribute of a given Node, assuming it exists, as a String. * 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 n A node object that should have some attributes
* @param attr The attribute you want to get from the given node. * @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. * @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) { private static String getNodeAttributeString(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr); Node attrItem = n.getAttributes().getNamedItem(attr);
@@ -120,9 +137,11 @@ public class XMLParser {
/** /**
* Returns the text content of an attribute of a given Node, assuming it exists, as an Integer. * 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 n A node object that should have some attributes
* @param attr The attribute you want to get from the given node. * @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. * @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) { private static Integer getNodeAttributeInt(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr); Node attrItem = n.getAttributes().getNamedItem(attr);
@@ -135,9 +154,11 @@ public class XMLParser {
/** /**
* Returns the text content of an attribute of a given Node, assuming it exists, as a Double. * 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 n A node object that should have some attributes
* @param attr The attribute you want to get from the given node. * @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. * @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) { private static Double getNodeAttributeDouble(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr); Node attrItem = n.getAttributes().getNamedItem(attr);
@@ -149,6 +170,7 @@ public class XMLParser {
} }
public class RegattaXMLObject { public class RegattaXMLObject {
//Regatta Info //Regatta Info
private Integer regattaID; private Integer regattaID;
private String regattaName; private String regattaName;
@@ -160,6 +182,7 @@ public class XMLParser {
/** /**
* Constructor for a RegattaXMLObject. * Constructor for a RegattaXMLObject.
* Takes the information from a Document object and creates a more usable format. * Takes the information from a Document object and creates a more usable format.
*
* @param doc XML Document Object * @param doc XML Document Object
*/ */
RegattaXMLObject(Document doc) { RegattaXMLObject(Document doc) {
@@ -173,12 +196,29 @@ public class XMLParser {
this.utcOffset = getElementInt(docEle, "UtcOffset"); this.utcOffset = getElementInt(docEle, "UtcOffset");
} }
public Integer getRegattaID() { return regattaID; } public Integer getRegattaID() {
public String getRegattaName() { return regattaName; } return regattaID;
public String getCourseName() { return courseName; } }
public Double getCentralLat() { return centralLat; }
public Double getCentralLng() { return centralLng; } public String getRegattaName() {
public Integer getUtcOffset() { return utcOffset; } return regattaName;
}
public String getCourseName() {
return courseName;
}
public Double getCentralLat() {
return centralLat;
}
public Double getCentralLng() {
return centralLng;
}
public Integer getUtcOffset() {
return utcOffset;
}
} }
@@ -195,13 +235,18 @@ public class XMLParser {
//Non atomic race attributes //Non atomic race attributes
private ArrayList<Participant> participants; private ArrayList<Participant> participants;
private ArrayList<CompoundMark> course; private ArrayList<Mark> allMarks;
private ArrayList<Mark> nonDuplicateMarks;
private ArrayList<Corner> compoundMarkSequence; private ArrayList<Corner> compoundMarkSequence;
private ArrayList<Limit> courseLimit; private ArrayList<Limit> courseLimit;
// ensures there's no duplicate marks.
private List<Long> seenSourceIDs = new ArrayList<Long>();
/** /**
* Constructor for a RaceXMLObject. * Constructor for a RaceXMLObject.
* Takes the information from a Document object and creates a more usable format. * Takes the information from a Document object and creates a more usable format.
*
* @param doc XML Document Object * @param doc XML Document Object
*/ */
RaceXMLObject(Document doc) { RaceXMLObject(Document doc) {
@@ -213,8 +258,9 @@ public class XMLParser {
this.creationTimeDate = getElementString(docEle, "CreationTimeDate"); this.creationTimeDate = getElementString(docEle, "CreationTimeDate");
Node raceStart = docEle.getElementsByTagName("RaceStartTime").item(0); Node raceStart = docEle.getElementsByTagName("RaceStartTime").item(0);
this.raceStartTime = getNodeAttributeString(raceStart, "Start") ; this.raceStartTime = getNodeAttributeString(raceStart, "Start");
this.postponeStatus = Boolean.parseBoolean(getNodeAttributeString(raceStart, "Postpone")); this.postponeStatus = Boolean
.parseBoolean(getNodeAttributeString(raceStart, "Postpone"));
//Participants //Participants
participants = new ArrayList<>(); participants = new ArrayList<>();
@@ -238,21 +284,15 @@ public class XMLParser {
} }
//Course //Course
course = new ArrayList<>(); allMarks = new ArrayList<>();
nonDuplicateMarks = new ArrayList<>();
NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes(); createCompoundMarks(docEle);
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 //Course Mark Sequence
compoundMarkSequence = new ArrayList<>(); compoundMarkSequence = new ArrayList<>();
NodeList cornerList = docEle.getElementsByTagName("CompoundMarkSequence").item(0).getChildNodes(); NodeList cornerList = docEle.getElementsByTagName("CompoundMarkSequence").item(0)
.getChildNodes();
for (int i = 0; i < cornerList.getLength(); i++) { for (int i = 0; i < cornerList.getLength(); i++) {
Node cornerNode = cornerList.item(i); Node cornerNode = cornerList.item(i);
if (cornerNode.getNodeName().equals("Corner")) { if (cornerNode.getNodeName().equals("Corner")) {
@@ -274,18 +314,116 @@ public class XMLParser {
} }
} }
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; } private void createCompoundMarks(Element docEle) {
public ArrayList<CompoundMark> getCompoundMarks() { return course; }
public ArrayList<Corner> getCompoundMarkSequence() { return compoundMarkSequence; } NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
public ArrayList<Limit> getCourseLimit() { return courseLimit; } for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) {
createAndAddMark(cMarkNode);
}
}
}
private void createAndAddMark(Node compoundMark) {
Boolean markSeen = false;
List<SingleMark> marksList = new ArrayList<>();
Integer compoundMarkID = getNodeAttributeInt(compoundMark, "CompoundMarkID");
String cMarkName = getNodeAttributeString(compoundMark, "Name");
NodeList childMarks = compoundMark.getChildNodes();
for (int i = 0; i < childMarks.getLength(); i++) {
Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) {
Integer sourceID = getNodeAttributeInt(markNode, "SourceID");
String markName = getNodeAttributeString(markNode, "Name");
Double targetLat = getNodeAttributeDouble(markNode, "TargetLat");
Double targetLng = getNodeAttributeDouble(markNode, "TargetLng");
SingleMark mark = new SingleMark(markName, targetLat, targetLng, sourceID, compoundMarkID);
marksList.add(mark);
}
}
for (SingleMark mark : marksList) {
if (seenSourceIDs.contains(mark.getId())) {
markSeen = true;
} else {
seenSourceIDs.add(mark.getId());
}
}
if (marksList.size() == 1) {
if (!markSeen) {
nonDuplicateMarks.add(marksList.get(0));
}
allMarks.add(marksList.get(0));
} else if (marksList.size() == 2) {
GateMark thisGateMark = new GateMark(cMarkName, MarkType.OPEN_GATE, marksList.get(0),
marksList.get(1), marksList.get(0).getLatitude(),
marksList.get(0).getLongitude(), compoundMarkID);
if(!markSeen) {
nonDuplicateMarks.add(thisGateMark);
}
allMarks.add(thisGateMark);
}
}
public Integer getRaceID() {
return raceID;
}
public String getRaceType() {
return raceType;
}
public String getCreationTimeDate() {
return creationTimeDate;
}
public String getRaceStartTime() {
return raceStartTime;
}
public Boolean getPostponeStatus() {
return postponeStatus;
}
public ArrayList<Participant> getParticipants() {
return participants;
}
/**
* @return Returns ALL compound marks as stated in the RaceXML (INCLUDING DUPLICATE MARKS)
*/
public List<Mark> getAllCompoundMarks() {
return allMarks;
}
/**
* @return Returns Marks from the race XML without any duplicates
*/
public List<Mark> getNonDupCompoundMarks() {
return nonDuplicateMarks;
}
public ArrayList<Corner> getCompoundMarkSequence() {
return compoundMarkSequence;
}
public ArrayList<Limit> getCourseLimit() {
return courseLimit;
}
public class Participant { public class Participant {
Integer sourceID; Integer sourceID;
String entry; String entry;
@@ -294,57 +432,17 @@ public class XMLParser {
this.entry = entry; this.entry = entry;
} }
public Integer getsourceID() { return sourceID; } public Integer getsourceID() {
public String getEntry() { return entry; } return sourceID;
}
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 getEntry() {
public String getcMarkName() { return cMarkName; } return entry;
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 { public class Corner {
private Integer seqID; private Integer seqID;
private Integer compoundMarkID; private Integer compoundMarkID;
private String rounding; private String rounding;
@@ -357,13 +455,25 @@ public class XMLParser {
this.zoneSize = getNodeAttributeInt(cornerNode, "ZoneSize"); this.zoneSize = getNodeAttributeInt(cornerNode, "ZoneSize");
} }
public Integer getSeqID() { return seqID; } public Integer getSeqID() {
public Integer getCompoundMarkID() { return compoundMarkID; } return seqID;
public String getRounding() { return rounding; } }
public Integer getZoneSize() { return zoneSize; }
public Integer getCompoundMarkID() {
return compoundMarkID;
}
public String getRounding() {
return rounding;
}
public Integer getZoneSize() {
return zoneSize;
}
} }
public class Limit { public class Limit {
private Integer seqID; private Integer seqID;
private Double lat; private Double lat;
private Double lng; private Double lng;
@@ -374,9 +484,17 @@ public class XMLParser {
this.lng = getNodeAttributeDouble(limitNode, "Lon"); this.lng = getNodeAttributeDouble(limitNode, "Lon");
} }
public Integer getSeqID() { return seqID; } public Integer getSeqID() {
public Double getLat() { return lat; } return seqID;
public Double getLng() { return lng; } }
public Double getLat() {
return lat;
}
public Double getLng() {
return lng;
}
} }
} }
@@ -402,6 +520,7 @@ public class XMLParser {
/** /**
* Constructor for a BoatXMLObject. * Constructor for a BoatXMLObject.
* Takes the information from a Document object and creates a more usable format. * Takes the information from a Document object and creates a more usable format.
*
* @param doc XML Document Object * @param doc XML Document Object
*/ */
BoatXMLObject(Document doc) { BoatXMLObject(Document doc) {
@@ -421,7 +540,7 @@ public class XMLParser {
Node zoneLimitsList = settingsList.item(7); Node zoneLimitsList = settingsList.item(7);
this.zoneLimits = new ArrayList<>(); this.zoneLimits = new ArrayList<>();
for (int i = 0; i < zoneLimitsList.getAttributes().getLength(); i++) { for (int i = 0; i < zoneLimitsList.getAttributes().getLength(); i++) {
String tag = String.format("Limit%d", i+1); String tag = String.format("Limit%d", i + 1);
this.zoneLimits.add(getNodeAttributeDouble(zoneLimitsList, tag)); this.zoneLimits.add(getNodeAttributeDouble(zoneLimitsList, tag));
} }
@@ -432,61 +551,60 @@ public class XMLParser {
if (currentBoat.getNodeName().equals("Boat")) { if (currentBoat.getNodeName().equals("Boat")) {
// Boat boat = new Boat(currentBoat); // Boat boat = new Boat(currentBoat);
Yacht boat = new Yacht(getNodeAttributeString(currentBoat, "Type"), Yacht boat = new Yacht(getNodeAttributeString(currentBoat, "Type"),
getNodeAttributeInt(currentBoat, "SourceID"), getNodeAttributeInt(currentBoat, "SourceID"),
getNodeAttributeString(currentBoat, "HullNum"), getNodeAttributeString(currentBoat, "HullNum"),
getNodeAttributeString(currentBoat, "ShortName"), getNodeAttributeString(currentBoat, "ShortName"),
getNodeAttributeString(currentBoat, "BoatName"), getNodeAttributeString(currentBoat, "BoatName"),
getNodeAttributeString(currentBoat, "Country")); getNodeAttributeString(currentBoat, "Country"));
this.boats.add(boat); this.boats.add(boat);
if (boat.getBoatType().equals("Yacht")) { if (boat.getBoatType().equals("Yacht")) {
competingBoats.put(boat.getSourceID(), boat); competingBoats.put(boat.getSourceID(), boat);
} }
} }
//System.out.println(this.getBoats());
} }
} }
public String getLastModified() { return lastModified; } public String getLastModified() {
public Integer getVersion() { return version; } return lastModified;
public String getBoatType() { return boatType; } }
public Double getBoatLength() { return boatLength; }
public Double getHullLength() { return hullLength; } public Integer getVersion() {
public Double getMarkZoneSize() { return markZoneSize; } return version;
public Double getCourseZoneSize() { return courseZoneSize; } }
public ArrayList<Double> getZoneLimits() { return zoneLimits; }
public ArrayList<Yacht> getBoats() { return boats; } 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() { public Map<Integer, Yacht> getCompetingBoats() {
return competingBoats; 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; }
//
// }
} }
} }
@@ -1,4 +1,4 @@
package seng302.models.parsers.packets; package seng302.models.stream.packets;
public class BoatPositionPacket { public class BoatPositionPacket {
private long boatId; private long boatId;
@@ -1,4 +1,4 @@
package seng302.models.parsers.packets; package seng302.models.stream.packets;
/** /**
* Created by Kusal on 4/24/2017. * Created by Kusal on 4/24/2017.
@@ -48,6 +48,4 @@ public enum PacketType {
} }
return OTHER; return OTHER;
} }
} }
@@ -1,4 +1,4 @@
package seng302.models.parsers.packets; package seng302.models.stream.packets;
/** /**
* Created by kre39 on 23/04/17. * Created by kre39 on 23/04/17.
+29 -20
View File
@@ -29,8 +29,6 @@ public class ServerThread implements Runnable, Observer {
Thread runner = new Thread(this, threadName); Thread runner = new Thread(this, threadName);
runner.setDaemon(true); runner.setDaemon(true);
serverLog("Spawning Server", 0);
raceSimulator = new Simulator(BOAT_LOCATION_PERIOD); raceSimulator = new Simulator(BOAT_LOCATION_PERIOD);
raceSimulator.addObserver(this); raceSimulator.addObserver(this);
// run race simulator, so it can send boats' static location. // run race simulator, so it can send boats' static location.
@@ -134,7 +132,6 @@ public class ServerThread implements Runnable, Observer {
* Starts an instance of the race simulator * Starts an instance of the race simulator
*/ */
private void startRaceSim(){ private void startRaceSim(){
serverLog("Starting Running Race Simulator", 0);
// set race started to true, so the simulator will start moving boats // set race started to true, so the simulator will start moving boats
raceSimulator.setRaceStarted(true); raceSimulator.setRaceStarted(true);
} }
@@ -142,8 +139,7 @@ public class ServerThread implements Runnable, Observer {
/** /**
* Starts sending heartbeat messages to the client * Starts sending heartbeat messages to the client
*/ */
private void startSendingHeartbeats(){ private void startSendingHeartbeats() {
serverLog("Sending Heartbeats", 0);
Timer t = new Timer(); Timer t = new Timer();
t.schedule(new TimerTask() { t.schedule(new TimerTask() {
@@ -154,7 +150,7 @@ public class ServerThread implements Runnable, Observer {
try { try {
server.send(heartbeat); server.send(heartbeat);
} catch (IOException e) { } catch (IOException e) {
System.out.print(""); e.printStackTrace();
} }
} }
}, 0, HEARTBEAT_PERIOD); }, 0, HEARTBEAT_PERIOD);
@@ -174,14 +170,13 @@ public class ServerThread implements Runnable, Observer {
if (startTime < System.currentTimeMillis() && !raceStarted){ if (startTime < System.currentTimeMillis() && !raceStarted){
startRaceSim(); startRaceSim();
raceStarted = true; raceStarted = true;
serverLog("Race Started", 0);
} }
else{ else{
server.send(raceStartStatusMessage); server.send(raceStartStatusMessage);
} }
} catch (IOException e) { } catch (IOException e) {
System.out.print(""); e.printStackTrace();
} }
} }
}, 0, RACE_START_STATUS_PERIOD); }, 0, RACE_START_STATUS_PERIOD);
@@ -191,7 +186,6 @@ public class ServerThread implements Runnable, Observer {
* Start sending race start status messages until race starts * Start sending race start status messages until race starts
*/ */
private void startSendingRaceStatusMessages(){ private void startSendingRaceStatusMessages(){
serverLog("Sending race status messages", 0);
Timer t = new Timer(); Timer t = new Timer();
t.schedule(new TimerTask() { t.schedule(new TimerTask() {
@Override @Override
@@ -199,9 +193,8 @@ public class ServerThread implements Runnable, Observer {
Message raceStatusMessage = getRaceStatusMessage(); Message raceStatusMessage = getRaceStatusMessage();
try { try {
server.send(raceStatusMessage); server.send(raceStatusMessage);
} catch (IOException e) { } catch (IOException e) {
System.out.print(""); e.printStackTrace();
} }
} }
}, 0, RACE_STATUS_PERIOD); }, 0, RACE_STATUS_PERIOD);
@@ -218,23 +211,39 @@ public class ServerThread implements Runnable, Observer {
if (raceData != null){ if (raceData != null){
server.send(raceData); server.send(raceData);
serverLog("Sending race data", 0);
} }
if (boatData != null){ if (boatData != null){
server.send(boatData); server.send(boatData);
serverLog("Sending boat data", 0);
} }
if (regatta != null){ if (regatta != null){
server.send(regatta); server.send(regatta);
serverLog("Sending regatta data", 0);
} }
} catch (IOException e) { } catch (IOException e) {
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0); serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
} }
} }
/**
* Send the post-start race course information
*/
private void sendPostStartCourseXml(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
try {
Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE);
if (raceData != null) {
server.send(raceData);
}
}catch (IOException e) {
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
}
}
},25000);
//Delays the new course xml data for 25 seconds so the boats are able to pass the starting line
}
public void run() { public void run() {
try{ try{
server = new StreamingServerSocket(PORT_NUMBER); server = new StreamingServerSocket(PORT_NUMBER);
@@ -252,12 +261,13 @@ public class ServerThread implements Runnable, Observer {
sendXml(); sendXml();
startSendingRaceStartStatusMessages(); startSendingRaceStartStatusMessages();
startSendingRaceStatusMessages(); startSendingRaceStatusMessages();
sendPostStartCourseXml();
} }
/** /**
* Start sending static boat position updates when race has finished * Start sending static boat position updates when race has finished
*/ */
private void startSendingRaceFinishedBoatPostions(){ private void startSendingRaceFinishedBoatPositions(){
Timer t = new Timer(); Timer t = new Timer();
t.schedule(new TimerTask() { t.schedule(new TimerTask() {
@Override @Override
@@ -272,7 +282,7 @@ public class ServerThread implements Runnable, Observer {
} }
} catch (IOException e) { } catch (IOException e) {
System.out.print(""); e.printStackTrace();
} }
} }
}, 0, BOAT_LOCATION_PERIOD); }, 0, BOAT_LOCATION_PERIOD);
@@ -299,7 +309,6 @@ public class ServerThread implements Runnable, Observer {
numOfBoatsFinished ++; numOfBoatsFinished ++;
if (!boatsFinished.get(boat.getSourceID())) { if (!boatsFinished.get(boat.getSourceID())) {
boatsFinished.put(boat.getSourceID(), true); boatsFinished.put(boat.getSourceID(), true);
serverLog("Boat " + boat.getSourceID() + " finished the race", 1);
} }
} }
Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(), Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(),
@@ -316,7 +325,7 @@ public class ServerThread implements Runnable, Observer {
} }
if (numOfBoatsFinished == ((List<Boat>) arg).size()) { if (numOfBoatsFinished == ((List<Boat>) arg).size()) {
startSendingRaceFinishedBoatPostions(); startSendingRaceFinishedBoatPositions();
} }
} }
@@ -28,7 +28,6 @@ class StreamingServerSocket {
} }
void start(){ void start(){
ServerThread.serverLog("Listening For Connections",0);
try { try {
client = socket.accept(); client = socket.accept();
} catch (IOException e) { } catch (IOException e) {
@@ -39,7 +38,6 @@ class StreamingServerSocket {
} }
else{ else{
isServerStarted = true; isServerStarted = true;
ServerThread.serverLog("client connected from " + client.socket().getInetAddress(),0);
} }
} }
@@ -1,10 +1,7 @@
package seng302.server.messages; package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class BoatLocationMessage extends Message { public class BoatLocationMessage extends Message {
private final int MESSAGE_SIZE = 56; private final int MESSAGE_SIZE = 56;
@@ -44,7 +41,7 @@ public class BoatLocationMessage extends Message {
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){ public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
boatSpeed /= 10; boatSpeed /= 10;
messageVersionNumber = 1; messageVersionNumber = 1;
time = System.currentTimeMillis() / 1000L; time = System.currentTimeMillis();
this.sourceId = sourceId; this.sourceId = sourceId;
this.sequenceNum = sequenceNum; this.sequenceNum = sequenceNum;
this.deviceType = DeviceType.RACING_YACHT; this.deviceType = DeviceType.RACING_YACHT;
@@ -54,7 +54,6 @@ public class Simulator extends Observable implements Runnable {
numOfFinishedBoats += moveBoat(boat, lapse); numOfFinishedBoats += moveBoat(boat, lapse);
} }
} }
//System.out.println(boats.get(0));
setChanged(); setChanged();
notifyObservers(boats); notifyObservers(boats);
@@ -65,8 +64,6 @@ public class Simulator extends Observable implements Runnable {
e.printStackTrace(); e.printStackTrace();
} }
} }
System.out.println("[SERVER] Race simulator has been terminated");
} }
/** /**
+8
View File
@@ -0,0 +1,8 @@
Tws,Twa0,Bsp0,Twa1,Bsp1,UpTwa,UpBsp,Twa2,Bsp2,Twa3,Bsp3,Twa4,Bsp4,Twa5,Bsp5,Twa6,Bsp6,DnTwa,DnBsp,Twa7,Bsp7
4,0,0,30,4,45,8,60,9,75,10,90,10,115,10,145,10,155,10,175,4
8,0,0,30,7,43,10,60,11,75,11,90,11,115,12,145,12,153,12,175,10
12,0,0,30,11,43,14.4,60,16,75,20,90,23,115,24,145,23,153,21.6,175,14
16,0,0,30,12,42,19.2,60,25,75,27,90,31,115,32,145,30,153,28.8,175,20
20,0,0,30,13,41,24,60,29,75,37,90,39,115,40,145,38,153,36,175,24
25,0,0,30,15,40,30,60,38,75,44,90,49,115,50,145,49,151,47,175,30
30,0,0,30,15,42,30,60,37,75,42,90,48,115,49,145,48,150,46,175,32
1 Tws Twa0 Bsp0 Twa1 Bsp1 UpTwa UpBsp Twa2 Bsp2 Twa3 Bsp3 Twa4 Bsp4 Twa5 Bsp5 Twa6 Bsp6 DnTwa DnBsp Twa7 Bsp7
2 4 0 0 30 4 45 8 60 9 75 10 90 10 115 10 145 10 155 10 175 4
3 8 0 0 30 7 43 10 60 11 75 11 90 11 115 12 145 12 153 12 175 10
4 12 0 0 30 11 43 14.4 60 16 75 20 90 23 115 24 145 23 153 21.6 175 14
5 16 0 0 30 12 42 19.2 60 25 75 27 90 31 115 32 145 30 153 28.8 175 20
6 20 0 0 30 13 41 24 60 29 75 37 90 39 115 40 145 38 153 36 175 24
7 25 0 0 30 15 40 30 60 38 75 44 90 49 115 50 145 49 151 47 175 30
8 30 0 0 30 15 42 30 60 37 75 42 90 48 115 49 145 48 150 46 175 32
+200
View File
@@ -0,0 +1,200 @@
/**
Background colours
*/
.background-blue{
-fx-background-color: #119796;
}
.background-dark{
-fx-background-color: #2C2c36;
}
/**
Exit button with no background
*/
.clear-exit-btn{
-fx-background-insets: 0;
-fx-background-color: #119796;
-fx-border-style: none;
}
/**
Buttons
*/
.blue-ui-btn{
-fx-background-color: #119796;
-fx-text-fill: #fff;
}
.text-white {
-fx-text-fill: white !important;
-fx-fill:white !important;
}
/**
Sliders
*/
.ui-slider .thumb {
-fx-background-color: rgb(60, 60, 60);
-fx-border-radius: 10;
-fx-border-color: darkgray;
}
.ui-slider .track{
-fx-background-color: #119796;
}
.ui-slider .axis{
-fx-tick-label-fill: white;
}
.ui-slider .axis .axis-label{
-fx-text-fill: white;
}
/**
Checkbox
*/
.ui-checkbox .box{
-fx-background-color: white;
-fx-graphic:none;
-fx-shape: none;
}
.ui-checkbox .box .mark{
-fx-background-image: none;
-fx-image: none;
-fx-graphic: none;
-fx-shape: none;
}
.ui-checkbox:selected .box{
-fx-background-color: #119796;
-fx-shape: none;
}
.ui-checkbox:selected .box .mark{
-fx-background-color: #119796;
-fx-shape: none;
-fx-graphic: none;
}
/**
Table
*/
.ui-table{
-fx-background-color: transparent;
}
.ui-table:focused{
-fx-background-color: transparent;
}
.ui-table .column-header-background{
-fx-background-color: white
}
.ui-table .column-header-background .label{
-fx-background-color: transparent;
-fx-text-fill: black;
}
.ui-table .column-header {
-fx-background-color: transparent;
}
.ui-table .table-cell{
-fx-text-fill: white;
-fx-border-style: none;
}
.table-row-cell{
-fx-background-color: #119796;
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: 0.0em; /* 0 */
-fx-border-style: none;
}
.table-row-cell:odd{
-fx-background-color: #0e6d6c;
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: 0.0em; /* 0 */
-fx-border-style: none;
}
.table-row-cell:selected {
-fx-background-color: #005797;
-fx-background-insets: 0;
-fx-border-style: none;
}
/**
Combo Box
*/
.combo-box-base {
-fx-background-color: #119796;
-fx-text-fill: white;
}
.combo-box-popup .list-view .list-cell:hover {
-fx-text-fill: white;
}
.combo-box .cell:selected {
-fx-text-fill: white;
}
/**
Remove scroll bars
*/
.ui-table *.scroll-bar:horizontal *.increment-button,
.ui-table *.scroll-bar:horizontal *.decrement-button {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
}
.ui-table *.scroll-bar:horizontal *.increment-arrow,
.ui-table *.scroll-bar:horizontal *.decrement-arrow {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
-fx-shape: null;
}
.ui-table *.scroll-bar:vertical *.increment-arrow,
.ui-table *.scroll-bar:vertical *.decrement-arrow {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
-fx-shape: null;
}
.ui-table *.scroll-bar:vertical *.increment-button,
.ui-table *.scroll-bar:vertical *.decrement-button {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
}
.chart{
}
.chart-title {
-fx-text-fill: #ffffff;
-fx-font-size: 1.6em;
}
.axis-label {
-fx-text-fill: #ffffff;
}
.axis {
-fx-tick-label-fill: #ffffff;
}
@@ -0,0 +1,105 @@
<?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>
+4 -2
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Race> <Race>
<CreationTimeDate>2015-08-29T13:12:40+02:00</CreationTimeDate> <CreationTimeDate>2015-08-29T11:27:15+02:00</CreationTimeDate>
<RaceStartTime Start="2015-08-29T13:10:00+02:00" Postpone="False" /> <RaceStartTime Start="2015-08-29T13:10:00+02:00" Postpone="False" />
<RaceID>15082901</RaceID> <RaceID>15082901</RaceID>
<RaceType>Fleet</RaceType> <RaceType>Fleet</RaceType>
@@ -80,6 +80,8 @@
<Limit SeqID="7" Lat="57.6629430" Lon="11.8332030" /> <Limit SeqID="7" Lat="57.6629430" Lon="11.8332030" />
<Limit SeqID="8" Lat="57.6629480" Lon="11.8249660" /> <Limit SeqID="8" Lat="57.6629480" Lon="11.8249660" />
<Limit SeqID="9" Lat="57.6686890" Lon="11.8250920" /> <Limit SeqID="9" Lat="57.6686890" Lon="11.8250920" />
<Limit SeqID="10" Lat="57.6708220" Lon="11.8321340" /> <Limit SeqID="10" Lat="57.6692230" Lon="11.8231430" />
<Limit SeqID="11" Lat="57.6725370" Lon="11.8272480" />
<Limit SeqID="12" Lat="57.6708220" Lon="11.8321340" />
</CourseLimit> </CourseLimit>
</Race> </Race>
+1 -1
View File
@@ -4,4 +4,4 @@
<?import javafx.scene.layout.*?> <?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" /> <AnchorPane fx:id="canvasPane" maxHeight="960.0" maxWidth="1280.0" prefHeight="960.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.CanvasController" />
+1 -55
View File
@@ -7,58 +7,4 @@
<?import java.lang.*?> <?import java.lang.*?>
<?import javafx.scene.layout.*?> <?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"> <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>
+27 -26
View File
@@ -1,23 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?> <?import java.lang.*?>
<?import javafx.scene.chart.*?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?> <?import javafx.scene.shape.*?>
<?import javafx.scene.text.*?> <?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"> <GridPane maxHeight="960.0" maxWidth="1530.0" prefHeight="960.0" prefWidth="1530.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>
<ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" /> <ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" /> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" />
@@ -28,39 +18,50 @@
<RowConstraints /> <RowConstraints />
</rowConstraints> </rowConstraints>
<children> <children>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.rowSpan="3"> <AnchorPane prefHeight="960.0" prefWidth="250.0" style="-fx-background-color: #2C2c36;" GridPane.rowSpan="3">
<children> <children>
<Label layoutX="11.0" layoutY="259.0" text="Team Position" /> <Label layoutX="11.0" layoutY="259.0" text="Team Position" textFill="WHITE" />
<Label layoutX="13.0" layoutY="432.0" text="Settings" /> <Label layoutX="13.0" layoutY="432.0" text="Settings" textFill="WHITE" />
<Label layoutX="11.0" layoutY="14.0" text="Timer" /> <Label layoutX="11.0" layoutY="14.0" text="Timer" textFill="WHITE" />
<Label layoutX="11.0" layoutY="88.0" text="Wind direction" /> <Label layoutX="11.0" layoutY="88.0" text="Wind direction" textFill="WHITE" />
<Circle fx:id="windBackgroundCircle" blendMode="DARKEN" fill="#76baf8" layoutX="110.0" layoutY="166.0" radius="35.0" stroke="#686868" strokeType="INSIDE" strokeWidth="3.0" /> <Circle fx:id="windBackgroundCircle" blendMode="DARKEN" fill="#3dcdc8" layoutX="110.0" layoutY="166.0" radius="35.0" stroke="#d7d7d7" strokeType="INSIDE" strokeWidth="3.0" />
<Text fx:id="windArrowText" fill="#686868" layoutX="86.0" layoutY="186.0" strokeType="OUTSIDE" strokeWidth="0.0" text=""> <Text fx:id="windArrowText" fill="#a8a8a8" layoutX="86.0" layoutY="186.0" strokeType="OUTSIDE" strokeWidth="0.0" text="">
<font> <font>
<Font name="AdobeArabic-Regular" size="55.0" /> <Font name="AdobeArabic-Regular" size="55.0" />
</font> </font>
</Text> </Text>
<Text fx:id="windDirectionText" fill="#a8a7a7" layoutX="171.0" layoutY="214.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0°" textAlignment="RIGHT"> <Text fx:id="windDirectionText" fill="#d3d3d3" layoutX="171.0" layoutY="214.0" strokeType="OUTSIDE" strokeWidth="0.0" text="0.0°" textAlignment="RIGHT">
<font> <font>
<Font name="System Bold" size="13.0" /> <Font name="System Bold" size="13.0" />
</font> </font>
</Text> </Text>
<CheckBox fx:id="toggleFps" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" text="Show FPS" /> <CheckBox fx:id="toggleFps" graphicTextGap="0.0" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" styleClass="ui-checkbox" text="Show FPS" textFill="WHITE" />
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" /> <VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" styleClass="text-white" />
<Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0"> <Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0">
<children> <children>
<Text fx:id="timerLabel" layoutX="-26.0" layoutY="34.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="246.0"> <Text fx:id="timerLabel" fill="#f8f8f8" layoutX="-26.0" layoutY="34.0" strokeType="OUTSIDE" strokeWidth="0.0" text="00:00" textAlignment="CENTER" wrappingWidth="246.0">
<font> <font>
<Font size="25.0" /> <Font size="25.0" />
</font> </font>
</Text> </Text>
</children> </children>
</Pane> </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" /> <Slider fx:id="annotationSlider" blockIncrement="1.0" layoutX="38.0" layoutY="527.0" majorTickUnit="1.0" max="2.0" minorTickCount="0" prefHeight="51.0" prefWidth="170.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" styleClass="ui-slider" />
<Label layoutX="10.0" layoutY="499.0" text="Annotations" /> <Label layoutX="10.0" layoutY="499.0" text="Annotations" textFill="WHITE" />
<Button fx:id="selectAnnotationBtn" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" />
<Text fill="WHITE" layoutX="11.0" layoutY="649.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Boat Selection" />
<ComboBox fx:id="boatSelectionComboBox" layoutX="37.0" layoutY="664.0" prefHeight="25.0" prefWidth="170.0" promptText="Select Boat" styleClass="combo-box-base" />
<LineChart fx:id="raceSparkLine" layoutX="-1.0" layoutY="719.0" legendVisible="false" prefHeight="277.0" prefWidth="246.0" title="Boat Positions">
<xAxis>
<CategoryAxis label="Leg Number" side="BOTTOM" styleClass="spark-line-xaxis" />
</xAxis>
<yAxis>
<NumberAxis fx:id="sparklineYAxis" minorTickCount="1" minorTickLength="1.0" side="LEFT" styleClass="spark-line-yaxis" tickLabelGap="1.0" tickUnit="1.0" upperBound="7.0" />
</yAxis>
</LineChart>
</children> </children>
</AnchorPane> </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"> <AnchorPane fx:id="contentAnchorPane" maxHeight="960.0" maxWidth="1280.0" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: skyblue;" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP">
<children> <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" /> <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></AnchorPane>
@@ -0,0 +1,60 @@
<?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.*?>
<GridPane fx:id="gridPane" nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" style="-fx-background-color: #2C2c36;" 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.StartScreenController">
<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="9.0" prefHeight="55.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="0.0" minHeight="0.0" percentHeight="29.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" textFill="WHITE" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<font>
<Font size="40.0" />
</font>
</Label>
<Label text="Your live AC35 livestream" textFill="WHITE" GridPane.halignment="CENTER" GridPane.rowIndex="1">
<font>
<Font size="20.0" />
</font>
</Label>
<Label text="Livestream Status:" textFill="WHITE" 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" styleClass="blue-ui-btn" text="Click to stream" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
<Button fx:id="switchToRaceViewButton" disable="true" mnemonicParsing="false" onAction="#switchToRaceView" styleClass="blue-ui-btn" text="Watch Race" GridPane.halignment="CENTER" GridPane.rowIndex="7" GridPane.valignment="TOP" />
<TableView fx:id="teamList" maxWidth="661.0" prefHeight="324.0" prefWidth="629.0" styleClass="ui-table" GridPane.halignment="CENTER" GridPane.hgrow="NEVER" GridPane.rowIndex="5" GridPane.vgrow="NEVER">
<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="155.18472290039062" minWidth="107.0" prefWidth="155.18472290039062" resizable="false" sortable="false" text="Short Name" />
<TableColumn fx:id="countryCol" editable="false" maxWidth="258.9999694824219" minWidth="147.0" prefWidth="258.9999694824219" resizable="false" sortable="false" text="Country" />
</columns>
<GridPane.margin>
<Insets top="10.0" />
</GridPane.margin>
</TableView>
<Label fx:id="realTime" text="Local time" textFill="WHITE" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="BOTTOM" />
</children>
</GridPane>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.layout.*?>
<?import java.lang.*?>
<?import javafx.scene.canvas.*?>
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: #ddd;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.models.map.TestMapController">
<children>
<Canvas fx:id="mapCanvas" height="960.0" width="1280.0" />
</children>
</Pane>
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="annotationSelectWindow" maxHeight="270.0" maxWidth="469.0" minHeight="270.0" minWidth="469.0" prefHeight="270.0" prefWidth="469.0" styleClass="background-blue" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Text fill="WHITE" layoutX="26.0" layoutY="52.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Select important annotations">
<font>
<Font size="24.0" />
</font>
</Text>
<CheckBox fx:id="boatWakeSelect" layoutX="26.0" layoutY="80.0" mnemonicParsing="false" style="-fx-border-width: 0; -fx-background-insets: 0;" text="Boat Wakes" textFill="#e7e7e7" />
<CheckBox fx:id="boatSpeedSelect" layoutX="26.0" layoutY="111.0" mnemonicParsing="false" text="Boat Speed" textFill="#e7e7e7" />
<CheckBox fx:id="boatTrackSelect" layoutX="26.0" layoutY="142.0" mnemonicParsing="false" text="Boat Tracks" textFill="#e7e7e7" />
<CheckBox fx:id="boatNameSelect" layoutX="26.0" layoutY="173.0" mnemonicParsing="false" text="Boat Name" textFill="#e7e7e7" />
<CheckBox fx:id="boatEstTimeToNextMarkSelect" layoutX="26.0" layoutY="204.0" mnemonicParsing="false" text="Boat Estimated Time To Next Mark" textFill="#e7e7e7" />
<Button fx:id="closeButton" layoutX="424.0" layoutY="-1.0" mnemonicParsing="false" prefHeight="11.0" prefWidth="49.0" style=": 0;" text="X" textFill="#ffffff4e">
<font>
<Font size="24.0" />
</font>
<styleClass>
<String fx:value="background-blue" />
<String fx:value="clearExitButton" />
</styleClass>
</Button>
<CheckBox fx:id="boatElapsedTimeSelect" layoutX="26.0" layoutY="235.0" mnemonicParsing="false" text="Boat Elapsed Time Since Last Mark" textFill="#e7e7e7" />
</children>
</AnchorPane>
+1 -4
View File
@@ -8,14 +8,11 @@ import seng302.models.Colors;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
/**
* Created by ryan_ on 16/03/2017.
*/
public class ColorsTest { public class ColorsTest {
@Test @Test
public void testNextColor() { public void testNextColor() {
Color expectedColors[] = {Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.PURPLE}; Color expectedColors[] = {Color.RED, Color.PERU, Color.SEAGREEN, Color.GREEN, Color.BLUE, Color.PURPLE};
for (int i = 0; i<6; i++) for (int i = 0; i<6; i++)
{ {
Assert.assertEquals(expectedColors[i], Colors.getColor()); Assert.assertEquals(expectedColors[i], Colors.getColor());
-38
View File
@@ -1,38 +0,0 @@
package seng302;
import org.junit.Test;
import seng302.models.Event;
import seng302.models.Yacht;
import seng302.models.mark.SingleMark;
import static org.junit.Assert.assertEquals;
/**
* Test for Event class
* Created by Haoming on 7/03/17.
*/
public class EventTest {
@Test
public void getTimeString() throws Exception {
Yacht boat = new Yacht("testBoat");
Event event = new Event(1231242.2, boat, new SingleMark("mark1"), new SingleMark("mark2"), 0);
assertEquals("20:31:242", event.getTimeString());
}
@Test
public void testBoatHeading() 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);
assertEquals(event.getBoatHeading(), 228.0266137055349, 1e-15);
}
@Test
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);
assertEquals(event.getDistanceBetweenMarks(), 339059.653830461, 1e-15);
}
}
-54
View File
@@ -1,54 +0,0 @@
package seng302;
import org.junit.Test;
import seng302.models.Leg;
import seng302.models.mark.SingleMark;
import static org.junit.Assert.assertEquals;
/**
* Unit test for the Leg class.
*/
public class LegTest {
/**
* Test creation of the leg by specifying a string
* for the marker label
*/
@Test
public void testLegCreationUsingMarkerLabel() {
Leg leg = new Leg(010, 100, "SingleMark");
assertEquals(leg.getHeading(), 010);
assertEquals(leg.getDistance(), 100);
assertEquals(leg.getMarkerLabel(), "SingleMark");
assertEquals(leg.getIsFinishingLeg(), false);
}
/**
* Test creation of the leg by providing a
* SingleMark object
*/
@Test
public void testLegCreation() {
Leg leg = new Leg(010, 100, new SingleMark("SingleMark"));
assertEquals(leg.getHeading(), 010);
assertEquals(leg.getDistance(), 100);
assertEquals(leg.getMarkerLabel(), "SingleMark");
assertEquals(leg.getIsFinishingLeg(), false);
}
/**
* Test changing whether or not a
* leg is the finishing leg
*/
@Test
public void testSetFinishLeg() {
Leg leg = new Leg(010, 100, "SingleMark");
leg.setFinishingLeg(true);
assertEquals(leg.getIsFinishingLeg(), true);
}
}
-41
View File
@@ -1,41 +0,0 @@
package seng302;
import org.junit.Test;
import seng302.models.Race;
import seng302.models.Yacht;
import java.lang.reflect.Array;
import static org.junit.Assert.assertEquals;
/**
* Unit test for the Race class.
*/
public class RaceTest {
/**
* Test that all boats were added to the race
*/
@Test
public void testAddingBoatsToRace() {
Yacht boat1 = new Yacht("Team 1");
Yacht boat2 = new Yacht("Team 2");
Race race = new Race();
race.addBoat(boat1);
race.addBoat(boat2);
assertEquals(Array.getLength(race.getBoats()), 2);
}
@Test
public void testGetShuffledBoats(){
Yacht boat1 = new Yacht("Team 1");
Yacht boat2 = new Yacht("Team 2");
Race race = new Race();
race.addBoat(boat1);
race.addBoat(boat2);
assertEquals(Array.getLength(race.getShuffledBoats()), 2);
}
}
+65
View File
@@ -0,0 +1,65 @@
package seng302;
import javafx.geometry.Point2D;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test Class for the GeometryUtils class
* Created by wmu16 on 24/05/17.
*/
public class TestGeoUtils {
//Line in x = y
private Point2D linePoint1 = new Point2D(0, 0);
private Point2D linePoint2 = new Point2D(1, 1);
//Point below x = y
private Point2D arbitraryPoint1 = new Point2D(1, 0);
//Point above x = y
private Point2D arbitraryPoint2 = new Point2D(0, 1);
//Point on x = y
private Point2D arbitraryPoint3 = new Point2D(2, 2);
@Before
public void setUp() throws Exception {
}
@Test
public void testLineFunction() {
Integer lineFunctionResult1 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint1);
Integer lineFunctionResult2 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint2);
Integer lineFunctionResult3 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint3);
//Point1 and Point2 are on opposite sides
assertEquals(Math.abs(lineFunctionResult1), Math.abs(lineFunctionResult2));
assertNotEquals(lineFunctionResult1, lineFunctionResult2);
//Point3 is on the line
assertEquals((long) lineFunctionResult3, 0L);
}
@Test
public void testMakeArbitraryVectorPoint() {
//Make a point (1,0) from point (0,0)
Point2D newPoint = GeometryUtils.makeArbitraryVectorPoint(linePoint1, 0d, 1d);
Point2D expected = new Point2D(1,0);
assertEquals(expected.getX(), newPoint.getX(), 1E-6);
assertEquals(expected.getY(), newPoint.getY(), 1E-6);
newPoint = GeometryUtils.makeArbitraryVectorPoint(linePoint1, 90d, 1d);
expected = new Point2D(0, 1);
assertEquals(expected.getX(), newPoint.getX(), 1E-6);
assertEquals(expected.getY(), newPoint.getY(), 1E-6);
}
}
@@ -0,0 +1,42 @@
package seng302.models.map;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Unit test for Mercator Project class.
* Created by hyi25 on 15/05/17.
*/
public class MercatorProjectionTest {
@Test
public void toMapPoint() throws Exception {
MapGeo geo1 = new MapGeo(12.485394, 19.38947);
MapPoint actualPoint1 = MercatorProjection.toMapPoint(geo1);
MapPoint expectedPoint1 = new MapPoint(141.78806755555556, 119.0503853635612);
assertEquals(expectedPoint1.getX(), actualPoint1.getX(), 0.0001);
assertEquals(expectedPoint1.getY(), actualPoint1.getY(), 0.0001);
MapGeo geo2 = new MapGeo(77.456432, -23.456462);
MapPoint actualPoint2 = MercatorProjection.toMapPoint(geo2);
MapPoint expectedPoint2 = new MapPoint(111.31984924444444, 38.03143323746788);
assertEquals(expectedPoint2.getX(), actualPoint2.getX(), 0.0001);
assertEquals(expectedPoint2.getY(), actualPoint2.getY(), 0.0001);
}
@Test
public void toMapGeo() throws Exception {
MapPoint point1 = new MapPoint(123.1234, 25.4565);
MapGeo actualGeo1 = MercatorProjection.toMapGeo(point1);
MapGeo expectedGeo1 = new MapGeo(80.77043127275441, -6.857718749999995);
assertEquals(expectedGeo1.getLat(), actualGeo1.getLat(), 0.0001);
assertEquals(expectedGeo1.getLng(), actualGeo1.getLng(), 0.0001);
MapPoint point2 = new MapPoint(1.235, 255.4565);
MapGeo actualGeo2 = MercatorProjection.toMapGeo(point2);
MapGeo expectedGeo2 = new MapGeo(-84.98475532898011, -178.26328125);
assertEquals(expectedGeo2.getLat(), actualGeo2.getLat(), 0.0001);
assertEquals(expectedGeo2.getLng(), actualGeo2.getLng(), 0.0001);
}
}
@@ -16,9 +16,9 @@ public class MarkTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
this.singleMark1 = new SingleMark("testMark_SM1", 12.23234, -34.342, 1); this.singleMark1 = new SingleMark("testMark_SM1", 12.23234, -34.342, 1, 0);
this.singleMark2 = new SingleMark("testMark_SM2", 12.23239, -34.352, 2); this.singleMark2 = new SingleMark("testMark_SM2", 12.23239, -34.352, 2, 1);
this.gateMark = new GateMark("testMark_GM", MarkType.OPEN_GATE, singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude()); this.gateMark = new GateMark("testMark_GM", MarkType.OPEN_GATE, singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude(), 2);
} }
@Test @Test
@@ -1,42 +0,0 @@
package seng302.models.parsers;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created by Haoming on 23/03/17.
*/
public class ConfigParserTest {
private ConfigParser cp;
@Before
public void initializeParser() throws Exception {
cp = new ConfigParser("/config/config.xml");
}
@Test
public void getWindDirection() throws Exception {
assertEquals(135, cp.getWindDirection(), 1e-10);
}
@Test
public void getTimeScale() throws Exception {
assertEquals(10.0, cp.getTimeScale(), 1e-10);
}
@Test
public void getDoubleByTagName() throws Exception {
assertEquals(6, cp.getDoubleByTagName("race-size", 0), 1e-10);
assertEquals(100, cp.getDoubleByTagName("noTag", 100), 1e-10);
}
@Test
public void getStringByTagName() throws Exception {
assertEquals("AC35", cp.getStringByTagName("race-name", "11"));
assertEquals("oops", cp.getStringByTagName("noTag", "oops"));
}
}
@@ -1,59 +0,0 @@
package seng302.models.parsers;
import org.junit.Before;
import org.junit.Test;
import seng302.models.mark.*;
import java.util.ArrayList;
import static org.junit.Assert.*;
/**
* To test if course parser works as expected.
* Created by Haoming on 17/03/17.
*/
public class CourseParserTest {
private CourseParser cp;
@Before
public void initializeParser() throws Exception {
cp = new CourseParser("/config/course.xml");
}
@Test
public void getGates() throws Exception {
ArrayList<Mark> course = cp.getCourse();
GateMark gateMark1 = (GateMark) course.get(0);
assertEquals(57.670633, gateMark1.getSingleMark2().getLatitude(), 0.00000001);
assertEquals(11.8281330, gateMark1.getSingleMark2().getLongitude(), 0.00000001);
GateMark gateMark2 = (GateMark) course.get(5);
assertEquals("Finish1", gateMark2.getSingleMark1().getName());
assertEquals("Finish2", gateMark2.getSingleMark2().getName());
assertEquals(57.671824, gateMark2.getSingleMark2().getLatitude(), 0.00000001);
assertEquals(11.844795, gateMark2.getSingleMark2().getLongitude(), 0.00000001);
}
@Test
public void getMarks() throws Exception {
ArrayList<Mark> course = cp.getCourse();
assertEquals("Mid Mark", course.get(1).getName());
}
@Test
public void getOrder() {
ArrayList<Mark> course = cp.getCourse();
assertEquals(6, course.size());
assertEquals("Start", course.get(0).getName());
assertEquals("Mid Mark", course.get(1).getName());
assertEquals("Leeward Gate", course.get(2).getName());
assertEquals("Windward Gate", course.get(3).getName());
assertEquals("Leeward Gate", course.get(4).getName());
assertEquals("Finish", course.get(5).getName());
}
}
@@ -1,4 +1,4 @@
package seng302.models.parsers; package seng302.models.stream;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -8,6 +8,7 @@ import java.lang.reflect.Method;
import java.net.Socket; import java.net.Socket;
import java.util.Comparator; import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
import seng302.models.stream.packets.StreamPacket;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -98,7 +99,7 @@ public class StreamReceiverTest {
byte[] emptyArray = {}; byte[] emptyArray = {};
assert bytesToLong.invoke(streamReceiver, emptyArray).equals(0L); assert bytesToLong.invoke(streamReceiver, emptyArray).equals(0L);
} catch (Exception e){ } catch (Exception e){
System.out.println(""); e.printStackTrace();
} }
} }
@@ -0,0 +1,53 @@
package seng302.visualizer.annotations;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import seng302.controllers.annotations.Annotation;
import seng302.controllers.annotations.ImportantAnnotationsState;
import static org.junit.Assert.assertEquals;
public class TestImportantAnnotationState {
private ImportantAnnotationsState importantAnnotationsState;
@Before
public void setUpForTest(){
importantAnnotationsState = new ImportantAnnotationsState();
}
@After
public void tearDownAfterTest(){
importantAnnotationsState = null;
}
/**
* Check whether each annotation has its default value set to the default value when
* the class is initialized
*/
@Test
public void testDefaultValueSet(){
for (Annotation annotation : importantAnnotationsState.getAnnotations()){
assertEquals(ImportantAnnotationsState.DEFAULT_ANNOTATION_STATE,
importantAnnotationsState.getAnnotationState(annotation));
}
}
/**
* Check whether an annotations state can be changed
*/
@Test
public void testAnnotationStateChange(){
Annotation[] annotations = importantAnnotationsState.getAnnotations();
// do not run test if there are no annotations
if (annotations.length <= 0){
return;
}
Boolean currentAnnotationState = importantAnnotationsState.getAnnotationState(annotations[0]);
importantAnnotationsState.setAnnotationState(annotations[0], !currentAnnotationState);
assertEquals(!currentAnnotationState, importantAnnotationsState.getAnnotationState(annotations[0]));
}
}