Reads BoatDataSource into StreamedCourseXMLReader so the latter can function as a single source of static information for the race.
- Mock.StreamedCourseXMLReader populates participant and mark list from BoatDataSource - Added CompoundMarker class to accommodate Mark data from Boat XML - Had to check for Yacht type in Visualiser.BoatXMLReader - Added a missing coordinate value to raceTest.xml - Marker class still exists due to large number of tests, which should be transitioned to CompoundMark - IMPORTANT: race is no longer functional #story[881]main
parent
9902cec688
commit
f31a987787
@ -0,0 +1,71 @@
|
|||||||
|
package seng302.Model;
|
||||||
|
|
||||||
|
import org.geotools.referencing.GeodeticCalculator;
|
||||||
|
import java.awt.geom.Point2D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by esa46 on 29/03/17.
|
||||||
|
*/
|
||||||
|
public class CompoundMark extends Marker{
|
||||||
|
|
||||||
|
private GPSCoordinate averageGPSCoordinate;
|
||||||
|
private Mark mark1;
|
||||||
|
private Mark mark2 = null;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public CompoundMark(Mark mark1) {
|
||||||
|
super(mark1.getPosition());
|
||||||
|
this.mark1 = mark1;
|
||||||
|
this.averageGPSCoordinate = calculateAverage();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundMark(Mark mark1, Mark mark2) {
|
||||||
|
super(mark1.getPosition(), mark2.getPosition());
|
||||||
|
this.mark1 = mark1;
|
||||||
|
this.mark2 = mark2;
|
||||||
|
this.averageGPSCoordinate = calculateAverage();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundMark(String name, Mark mark1, Mark mark2) {
|
||||||
|
super(name, mark1.getPosition(), mark2.getPosition());
|
||||||
|
this.name = name;
|
||||||
|
this.mark1 = mark1;
|
||||||
|
this.mark2 = mark2;
|
||||||
|
this.averageGPSCoordinate = calculateAverage();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getMark1() {
|
||||||
|
return mark1.getPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getMark2() {
|
||||||
|
return mark2.getPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getAverageGPSCoordinate() {
|
||||||
|
return averageGPSCoordinate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GPSCoordinate calculateAverage() {
|
||||||
|
if(mark2 != null) {
|
||||||
|
GeodeticCalculator calc = new GeodeticCalculator();
|
||||||
|
calc.setStartingGeographicPoint(mark1.getPosition().getLongitude(), mark1.getPosition().getLatitude());
|
||||||
|
calc.setDestinationGeographicPoint(mark2.getPosition().getLongitude(), mark2.getPosition().getLatitude());
|
||||||
|
double azimuth = calc.getAzimuth();
|
||||||
|
double distance = calc.getOrthodromicDistance();
|
||||||
|
|
||||||
|
GeodeticCalculator middleCalc = new GeodeticCalculator();
|
||||||
|
middleCalc.setStartingGeographicPoint(mark1.getPosition().getLongitude(), mark1.getPosition().getLatitude());
|
||||||
|
middleCalc.setDirection(azimuth, distance / 2);
|
||||||
|
Point2D middlePoint = middleCalc.getDestinationGeographicPoint();
|
||||||
|
return new GPSCoordinate(middlePoint.getY(), middlePoint.getX());
|
||||||
|
} else return mark1.getPosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package seng302.Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cbt24 on 25/04/17.
|
||||||
|
*/
|
||||||
|
public class StreamedCourseXMLException extends Throwable {
|
||||||
|
}
|
||||||
@ -0,0 +1,274 @@
|
|||||||
|
package seng302.Model;
|
||||||
|
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NamedNodeMap;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import seng302.DataInput.BoatDataSource;
|
||||||
|
import seng302.DataInput.RaceDataSource;
|
||||||
|
import seng302.DataInput.XMLReader;
|
||||||
|
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by jjg64 on 21/04/17.
|
||||||
|
*/
|
||||||
|
public class StreamedCourseXMLReader extends XMLReader implements RaceDataSource {
|
||||||
|
private static final double COORDINATEPADDING = 0.000;
|
||||||
|
private GPSCoordinate mapTopLeft, mapBottomRight;
|
||||||
|
private final List<GPSCoordinate> boundary = new ArrayList<>();
|
||||||
|
private final Map<Integer,Element> compoundMarkMap = new HashMap<>();
|
||||||
|
private final Map<Integer, Boat> participants = new HashMap<>();
|
||||||
|
private final List<Leg> legs = new ArrayList<>();
|
||||||
|
private final List<CompoundMark> compoundMarks = new ArrayList<>();
|
||||||
|
private ZonedDateTime creationTimeDate;
|
||||||
|
private ZonedDateTime raceStartTime;
|
||||||
|
private int raceID;
|
||||||
|
private String raceType;
|
||||||
|
private boolean postpone;
|
||||||
|
|
||||||
|
private Map<Integer, Boat> boats;
|
||||||
|
private Map<Integer, Mark> marks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for Streamed Race XML
|
||||||
|
* @param filePath path of the file
|
||||||
|
* @throws IOException error
|
||||||
|
* @throws SAXException error
|
||||||
|
* @throws ParserConfigurationException error
|
||||||
|
* @throws ParseException error
|
||||||
|
* @throws StreamedCourseXMLException error
|
||||||
|
*/
|
||||||
|
public StreamedCourseXMLReader(String filePath, BoatDataSource boatData) throws IOException, SAXException, ParserConfigurationException, ParseException, StreamedCourseXMLException {
|
||||||
|
this(filePath, boatData, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for Streamed Race XML
|
||||||
|
* @param filePath file path to read
|
||||||
|
* @param read whether or not to read and store the files straight away.
|
||||||
|
* @throws IOException error
|
||||||
|
* @throws SAXException error
|
||||||
|
* @throws ParserConfigurationException error
|
||||||
|
* @throws ParseException error
|
||||||
|
* @throws StreamedCourseXMLException error
|
||||||
|
*/
|
||||||
|
public StreamedCourseXMLReader(String filePath, BoatDataSource boatData, boolean read) throws IOException, SAXException, ParserConfigurationException, ParseException, StreamedCourseXMLException {
|
||||||
|
super(filePath);
|
||||||
|
this.boats = boatData.getBoats();
|
||||||
|
this.marks = boatData.getMarkerBoats();
|
||||||
|
if (read) {
|
||||||
|
read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reads
|
||||||
|
* @throws StreamedCourseXMLException error
|
||||||
|
*/
|
||||||
|
private void read() throws StreamedCourseXMLException {
|
||||||
|
readRace();
|
||||||
|
readParticipants();
|
||||||
|
readCourse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reads a race
|
||||||
|
*/
|
||||||
|
private void readRace() {
|
||||||
|
DateTimeFormatter dateFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
||||||
|
Element settings = (Element) doc.getElementsByTagName("Race").item(0);
|
||||||
|
NamedNodeMap raceTimeTag = doc.getElementsByTagName("RaceStartTime").item(0).getAttributes();
|
||||||
|
|
||||||
|
if (raceTimeTag.getNamedItem("Time") != null) dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||||
|
|
||||||
|
|
||||||
|
raceID = Integer.parseInt(getTextValueOfNode(settings, "RaceID"));
|
||||||
|
raceType = getTextValueOfNode(settings, "RaceType");
|
||||||
|
|
||||||
|
creationTimeDate = ZonedDateTime.parse(getTextValueOfNode(settings, "CreationTimeDate"), dateFormat);
|
||||||
|
|
||||||
|
if (raceTimeTag.getNamedItem("Time") != null) raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Time").getTextContent(), dateFormat);
|
||||||
|
else raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Start").getTextContent(), dateFormat);
|
||||||
|
|
||||||
|
postpone = Boolean.parseBoolean(raceTimeTag.getNamedItem("Postpone").getTextContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readParticipants() {
|
||||||
|
Element nParticipants = (Element) doc.getElementsByTagName("Participants").item(0);
|
||||||
|
nParticipants.getChildNodes().getLength();
|
||||||
|
for (int i = 0; i < nParticipants.getChildNodes().getLength(); i++) {
|
||||||
|
int sourceID;
|
||||||
|
Node yacht = nParticipants.getChildNodes().item(i);
|
||||||
|
if (yacht.getNodeName().equals("Yacht")) {
|
||||||
|
if (exists(yacht, "SourceID")) {
|
||||||
|
sourceID = Integer.parseInt(yacht.getAttributes().getNamedItem("SourceID").getTextContent());
|
||||||
|
participants.put(sourceID, boats.get(sourceID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reads a course
|
||||||
|
* @throws StreamedCourseXMLException error
|
||||||
|
*/
|
||||||
|
private void readCourse() throws StreamedCourseXMLException {
|
||||||
|
readCompoundMarks();
|
||||||
|
readCompoundMarkSequence();
|
||||||
|
readCourseLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers.
|
||||||
|
* @see CompoundMark
|
||||||
|
*/
|
||||||
|
private void readCompoundMarks() throws StreamedCourseXMLException {
|
||||||
|
Element nCourse = (Element) doc.getElementsByTagName("Course").item(0);
|
||||||
|
for(int i = 0; i < nCourse.getChildNodes().getLength(); i++) {
|
||||||
|
Node compoundMark = nCourse.getChildNodes().item(i);
|
||||||
|
if(compoundMark.getNodeName().equals("CompoundMark")) {
|
||||||
|
int compoundMarkID = getCompoundMarkID((Element) compoundMark);
|
||||||
|
compoundMarkMap.put(compoundMarkID, (Element)compoundMark);
|
||||||
|
compoundMarks.add(getCompoundMark(compoundMarkID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a CompoundMark from the CompoundMark element with given ID.
|
||||||
|
* @param compoundMarkID index of required CompoundMark element
|
||||||
|
* @return generated CompoundMark
|
||||||
|
* @throws StreamedCourseXMLException if CompoundMark element contains unhandled number of compoundMarks
|
||||||
|
* @see CompoundMark
|
||||||
|
*/
|
||||||
|
private CompoundMark getCompoundMark(int compoundMarkID) throws StreamedCourseXMLException {
|
||||||
|
Element compoundMark = compoundMarkMap.get(compoundMarkID);
|
||||||
|
NodeList nMarks = compoundMark.getElementsByTagName("Mark");
|
||||||
|
CompoundMark marker;
|
||||||
|
|
||||||
|
switch(nMarks.getLength()) {
|
||||||
|
case 1: marker = new CompoundMark(getMark((Element)nMarks.item(0))); break;
|
||||||
|
case 2: marker = new CompoundMark(getMark((Element)nMarks.item(0)), getMark((Element)nMarks.item(1))); break;
|
||||||
|
default: throw new StreamedCourseXMLException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mark getMark(Element mark) {
|
||||||
|
int sourceID = Integer.parseInt(mark.getAttribute("SourceID"));
|
||||||
|
System.out.println(marks.get(sourceID).getPosition());
|
||||||
|
return marks.get(sourceID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads "compoundMarkID" attribute of CompoundMark or Corner element
|
||||||
|
* @param element with "compoundMarkID" attribute
|
||||||
|
* @return value of "compoundMarkID" attribute
|
||||||
|
*/
|
||||||
|
private int getCompoundMarkID(Element element) {
|
||||||
|
return Integer.parseInt(element.getAttribute("CompoundMarkID"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads "name" attribute of CompoundMark element with corresponding CompoundMarkID
|
||||||
|
* @param compoundMarkID unique ID for CompoundMark element
|
||||||
|
* @return value of "name" attribute
|
||||||
|
*/
|
||||||
|
private String getCompoundMarkName(int compoundMarkID) {
|
||||||
|
return compoundMarkMap.get(compoundMarkID).getAttribute("Name");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements.
|
||||||
|
* @throws StreamedCourseXMLException if compoundMarks cannot be resolved from CompoundMark
|
||||||
|
*/
|
||||||
|
private void readCompoundMarkSequence() throws StreamedCourseXMLException {
|
||||||
|
Element nCompoundMarkSequence = (Element) doc.getElementsByTagName("CompoundMarkSequence").item(0);
|
||||||
|
NodeList nCorners = nCompoundMarkSequence.getElementsByTagName("Corner");
|
||||||
|
Element markXML = (Element)nCorners.item(0);
|
||||||
|
CompoundMark lastCompoundMark = getCompoundMark(getCompoundMarkID(markXML));
|
||||||
|
String legName = getCompoundMarkName(getCompoundMarkID(markXML));
|
||||||
|
for(int i = 1; i < nCorners.getLength(); i++) {
|
||||||
|
markXML = (Element)nCorners.item(i);
|
||||||
|
CompoundMark currentCompoundMark = getCompoundMark(getCompoundMarkID(markXML));
|
||||||
|
legs.add(new Leg(legName, lastCompoundMark, currentCompoundMark, i-1));
|
||||||
|
lastCompoundMark = currentCompoundMark;
|
||||||
|
legName = getCompoundMarkName(getCompoundMarkID(markXML));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readCourseLimit() {
|
||||||
|
Element nCourseLimit = (Element) doc.getElementsByTagName("CourseLimit").item(0);
|
||||||
|
for(int i = 0; i < nCourseLimit.getChildNodes().getLength(); i++) {
|
||||||
|
Node limit = nCourseLimit.getChildNodes().item(i);
|
||||||
|
if (limit.getNodeName().equals("Limit")) {
|
||||||
|
double lat = Double.parseDouble(limit.getAttributes().getNamedItem("Lat").getTextContent());
|
||||||
|
double lon = Double.parseDouble(limit.getAttributes().getNamedItem("Lon").getTextContent());
|
||||||
|
boundary.add(new GPSCoordinate(lat, lon));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING;
|
||||||
|
double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING;
|
||||||
|
double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING;
|
||||||
|
double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING;
|
||||||
|
|
||||||
|
mapTopLeft = new GPSCoordinate(minLatitude, minLongitude);
|
||||||
|
mapBottomRight = new GPSCoordinate(maxLatitude, maxLongitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GPSCoordinate> getBoundary() {
|
||||||
|
return boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getMapTopLeft() {
|
||||||
|
return mapTopLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate getMapBottomRight() {
|
||||||
|
return mapBottomRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Leg> getLegs() {
|
||||||
|
return legs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CompoundMark> getCompoundMarks() { return compoundMarks; }
|
||||||
|
|
||||||
|
public Double getPadding() {
|
||||||
|
return COORDINATEPADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZonedDateTime getCreationTimeDate() {
|
||||||
|
return creationTimeDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZonedDateTime getZonedDateTime() {
|
||||||
|
return raceStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRaceId() {
|
||||||
|
return raceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRaceType() {
|
||||||
|
return raceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPostpone() {
|
||||||
|
return postpone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Boat> getBoats() {
|
||||||
|
return new ArrayList<>(participants.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in new issue