package mock.xml; import org.w3c.dom.Document; import org.xml.sax.SAXException; import shared.dataInput.RaceXMLReader; import shared.enums.XMLFileType; import shared.exceptions.InvalidRaceDataException; import shared.exceptions.XMLReaderException; import shared.model.*; import shared.xml.Race.*; import shared.xml.XMLUtilities; import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.bind.util.JAXBSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.math.BigInteger; import java.net.URL; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; /** * Helper Class for creating a Race XML */ public class RaceXMLCreator { /** * get the windward gate in a race * @param reader reads in the mark * @return the windward gate. */ public static CompoundMark getWindwardGate(RaceXMLReader reader){ for (CompoundMark mark: reader.getCompoundMarks()){ if (mark.getName().equals("Windward Gate")) return mark; } return null; } /** * get the leeward gate in a race * @param reader reads in the mark * @return the leeward gate. */ public static CompoundMark getLeewardGate(RaceXMLReader reader){ for (CompoundMark mark: reader.getCompoundMarks()){ if (mark.getName().equals("Leeward Gate")) return mark; } return null; } /** * Rotates the race in a specified direction. * @param s xml file name * @param degrees degrees to rotate * @return the new xml file as a string * @throws XMLReaderException if the xml is not readable * @throws InvalidRaceDataException if the race is invalid * @throws JAXBException if the Race class cannot be parsed into a xml. * @throws IOException if the schema file cannot be found * @throws SAXException error in schema file * @throws ParserConfigurationException error in parsing the schema file */ public static String alterRaceToWind(String s, double degrees) throws XMLReaderException, InvalidRaceDataException, JAXBException, IOException, SAXException, ParserConfigurationException { RaceXMLReader reader = new RaceXMLReader(s, XMLFileType.ResourcePath); XMLRace race = XMLUtilities.xmlToClass( RaceXMLCreator.class.getClassLoader().getResourceAsStream(s), RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"), XMLRace.class); setRaceXMLAtCurrentTimeToNow(race); double raceOriginalBearing = getLineAngle(getLeewardGate(reader).getMark1Position(), getWindwardGate(reader).getMark1Position()); double degreesToRotate = degrees - raceOriginalBearing; alterRaceRotation(race, degreesToRotate); return XMLUtilities.classToXML(race); } /** * Rotate the features in a race such as the boundary, and the marks. * @param race the race to alter * @param degrees the degrees to rotate by. */ public static void alterRaceRotation(XMLRace race, double degrees){ GPSCoordinate center = getCenter(race); for(XMLLimit limit: race.getCourseLimit().getLimit()){ GPSCoordinate rotatedLim = rotate(center, limitToGPSCoordinate(limit), degrees); limit.setLat(rotatedLim.getLatitude()); limit.setLon(rotatedLim.getLongitude()); } for(XMLCompoundMark compoundMark: race.getCourse().getCompoundMark()){ for (XMLMark mark: compoundMark.getMark()){ GPSCoordinate rotatedMark = rotate(center, markToGPSCoordinate(mark), degrees); mark.setTargetLat(rotatedMark.getLatitude()); mark.setTargetLng(rotatedMark.getLongitude()); } } } /** * Converts a Race.CourseLimit.Limit to a GPS coordinate * @param limit limit to convert * @return gps coordinate corresponding to the limit */ public static GPSCoordinate limitToGPSCoordinate(XMLLimit limit){ return new GPSCoordinate(limit.getLat(), limit.getLon()); } /** * get new gps coordinate after rotating * @param pivot center point to rotating from. * @param point point to rotate * @param degrees number of degress to rotate by * @return the new GPSCoordinate of the transformed point. */ public static GPSCoordinate rotate(GPSCoordinate pivot, GPSCoordinate point, double degrees){ double radDeg = Math.toRadians(degrees); double deltaLat = (point.getLatitude() - pivot.getLatitude()); double deltaLon = (point.getLongitude() - pivot.getLongitude()); //map to (0,1) vector and use vector maths to rotate. double resLat = deltaLat * Math.cos(radDeg) - deltaLon * Math.sin(radDeg) + pivot.getLatitude(); double resLon = deltaLat * Math.sin(radDeg) + deltaLon * Math.cos(radDeg) + pivot.getLongitude(); return new GPSCoordinate(resLat, resLon); } /** * obtains the GPSCoordinates of a mark * @param mark mark to obtain the GPSCoordinates of * @return the GPSCOordinatess of a mark */ public static GPSCoordinate markToGPSCoordinate(XMLMark mark){ return new GPSCoordinate(mark.getTargetLat(), mark.getTargetLng()); } /** * get the center of a race * @param race race to get the center of * @return GPSCoordinates of the center */ public static GPSCoordinate getCenter(XMLRace race){ double avgLat = 0; double avgLng = 0; for (XMLLimit limit: race.getCourseLimit().getLimit()){ avgLat += limit.getLat(); avgLng += limit.getLon(); } avgLat = avgLat/race.getCourseLimit().getLimit().size(); avgLng = avgLng/race.getCourseLimit().getLimit().size(); return new GPSCoordinate(avgLat, avgLng); } /** * gets the angle of a line * @param coord1 point a of the line * @param coord2 point b of the line * @return the angle in degrees that the bearing of the line is [-180, 180] */ public static double getLineAngle(GPSCoordinate coord1, GPSCoordinate coord2){ double dx = coord1.getLongitude() - coord2.getLongitude(); double dy = coord1.getLatitude() - coord2.getLatitude(); return Math.atan2(dy, dx)/Math.PI * 180; } /** * Sets the xml description of the race to show the race was created now, and starts in 4 minutes * @param raceXML The race.xml contents. */ public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) { //The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute. long millisecondsToAdd = Constants.RacePreStartTime + Constants.RacePreparatoryTime; long secondsToAdd = millisecondsToAdd / 1000; //Scale the time using our time scalar. secondsToAdd = secondsToAdd / Constants.RaceTimeScale; DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); ZonedDateTime creationTime = ZonedDateTime.now(); raceXML.setCreationTimeDate(dateFormat.format(creationTime)); raceXML.getRaceStartTime().setTime(dateFormat.format(creationTime.plusSeconds(secondsToAdd))); } }