Merge branch 'master' into bug4881

This commit is contained in:
Axel Uhl
2020-02-12 17:02:40 +01:00
19 changed files with 1189 additions and 199 deletions
@@ -3,33 +3,67 @@ package com.sap.sailing.domain.common.orc;
import java.io.Serializable;
import java.util.Map;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Speed;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.WithID;
import com.sap.sse.common.impl.DegreeBearingImpl;
/**
* Represents semantically a real ORC certificate for a {@link Competitor}, which is used to rate different type of
* boats for different insohre and offshore race conditions.
* boats for different inshore and offshore race conditions.
* <p>
* An ORC certificate is issued by the "Member National Authorities" of World Sailing and are available for insight at
* https://www.orc.org/index.asp . Other information about the whole scoring system and different variants are available
* too.
* <p>
* One implementing class provides all necessary functionalities to score the Competitors with a choosen
* {@link RankingMetric}.<p>
* {@link RankingMetric}.
* <p>
*
* The {@link WithID} interface is to be implemented such that a {@link String} is produced as the ID that
* contains the concatenation (without intermediate white space) of the {@code NatAuth}, the {@code CertNo}
* and the {@code BIN} fields, as provided as single fields in the JSON representation, and as provided
* in the concatenated form in the {@code NATCERTN.FILE_ID} column in the RMS format.
* The {@link WithID} interface is to be implemented such that a {@link String} is produced as the ID that contains the
* concatenation (without intermediate white space) of the {@code NatAuth}, the {@code CertNo} and the {@code BIN}
* fields, as provided as single fields in the JSON representation, and as provided in the concatenated form in the
* {@code NATCERTN.FILE_ID} column in the RMS format.
* <p>
*
* The certificate contains time allowances (or, conversely, speed predictions) for a combination of true wind speeds
* (TWS) and true wind angles (TWA). There are defaults for the values used for the true wind speeds and the true wind
* angles, forming a regular, fully populated matrix of allowances / speed predictions. These defaults can be found in
* {@link #ALLOWANCES_TRUE_WIND_SPEEDS} and {@link #ALLOWANCES_TRUE_WIND_SPEEDS}.
* <p>
*
* Certificates may also specify non-default values for the slots or "bins" of the matrix that describes the allowances
* / speed predictions. As for the default values, the allowances matrix has to be fully populated. The values in the
* TWS and TWA arrays are expected to be increasing monotonously.
*
* @author Daniel Lisunkin (i505543)
*
*/
public interface ORCCertificate extends WithID, Serializable {
/**
* Equals the column heading of the allowances table of an ORC certificate. The speeds are set by the offshore
* racing congress. The speeds occur in the array in ascending order.
*
* There are references in the persistance module. If the values change, there will be an adjustment needed
* in {@link MongoObjectFactoryImpl.speedToKnotsString}.
*/
Speed[] ALLOWANCES_TRUE_WIND_SPEEDS = { new KnotSpeedImpl(6), new KnotSpeedImpl(8),
new KnotSpeedImpl(10), new KnotSpeedImpl(12), new KnotSpeedImpl(14), new KnotSpeedImpl(16),
new KnotSpeedImpl(20) };
/**
* Equals the line heading of the allowances table of an ORC certificate. The true wind angles are set by the
* offshore racing congress. The angles occur in the array in ascending order.
*
* There are references in the persistance module. If the values change, there will be an adjustment needed
* in {@link MongoObjectFactoryImpl.bearingToDegreeString}.
*/
Bearing[] ALLOWANCES_TRUE_WIND_ANGLES = { new DegreeBearingImpl(52), new DegreeBearingImpl(60),
new DegreeBearingImpl(75), new DegreeBearingImpl(90), new DegreeBearingImpl(110),
new DegreeBearingImpl(120), new DegreeBearingImpl(135), new DegreeBearingImpl(150) };
/**
* As the ID of an ORC certificate we use the concatenation (without intermediate white space) of the
* {@code NatAuth}, the {@code CertNo} and the {@code BIN} fields, as provided as single fields in the JSON
@@ -128,4 +162,19 @@ public interface ORCCertificate extends WithID, Serializable {
Map<Speed, Map<Bearing, Speed>> getVelocityPredictionPerTrueWindSpeedAndAngle();
String getBoatName();
/**
* @return the true wind speeds used for the matrix returned by
* {@link #getVelocityPredictionPerTrueWindSpeedAndAngle()}. The {@link Speed} values returned by this
* method form the key set of the map returned by {@link #getVelocityPredictionPerTrueWindSpeedAndAngle()}.
*/
Speed[] getTrueWindSpeeds();
/**
* @return the true wind angles used for the matrix returned by
* {@link #getVelocityPredictionPerTrueWindSpeedAndAngle()}. The {@link Bearing} values returned by this
* method form the key set of all the maps returned as values in the map returned by
* {@link #getVelocityPredictionPerTrueWindSpeedAndAngle()}.
*/
Bearing[] getTrueWindAngles();
}
@@ -3,7 +3,6 @@ package com.sap.sailing.domain.common.orc.impl;
import java.util.Collections;
import java.util.Map;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.NauticalMileDistance;
import com.sap.sailing.domain.common.orc.ORCCertificate;
import com.sap.sse.common.Bearing;
@@ -12,7 +11,6 @@ import com.sap.sse.common.Duration;
import com.sap.sse.common.Speed;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.DegreeBearingImpl;
/**
* Holds an ORC boat certificate including various of the metrics found in it. Note that multiple different certificates
@@ -28,28 +26,6 @@ public class ORCCertificateImpl implements ORCCertificate {
private final String idConsistingOfNatAuthCertNoAndBIN;
/**
* Equals the column heading of the allowances table of an ORC certificate. The speeds are set by the offshore
* racing congress. The speeds occur in the array in ascending order.
*
* There are references in the persistance module. If the values change, there will be an adjustment needed
* in {@link MongoObjectFactoryImpl.speedToKnotsString}.
*/
public static final Speed[] ALLOWANCES_TRUE_WIND_SPEEDS = { new KnotSpeedImpl(6), new KnotSpeedImpl(8),
new KnotSpeedImpl(10), new KnotSpeedImpl(12), new KnotSpeedImpl(14), new KnotSpeedImpl(16),
new KnotSpeedImpl(20) };
/**
* Equals the line heading of the allowances table of an ORC certificate. The true wind angles are set by the
* offshore racing congress. The angles occur in the array in ascending order.
*
* There are references in the persistance module. If the values change, there will be an adjustment needed
* in {@link MongoObjectFactoryImpl.bearingToDegreeString}.
*/
public static final Bearing[] ALLOWANCES_TRUE_WIND_ANGLES = { new DegreeBearingImpl(52), new DegreeBearingImpl(60),
new DegreeBearingImpl(75), new DegreeBearingImpl(90), new DegreeBearingImpl(110),
new DegreeBearingImpl(120), new DegreeBearingImpl(135), new DegreeBearingImpl(150) };
public final static Distance NAUTICAL_MILE = new NauticalMileDistance(1);
private final String sailNumber;
@@ -60,7 +36,16 @@ public class ORCCertificateImpl implements ORCCertificate {
private final Double cdl;
private final TimePoint issueDate;
// TODO add meaningful Javadoc
/**
* The "core" of the certificate for performance curve scoring; the values in the map tell how fast the boat
* described by this certificate is expected to sail for the true wind speed (TWS) provided by the key, and the true
* wind angle (TWA) provided by the value map's key. This is the reciprocal of the duration it takes the boat to
* sail a certain distance.
* <p>
*
* The keys are the speeds as provided by {@link #allowancesTrueWindSpeeds}; the keys of the value maps are the
* angles as provided by {@link #allowancesTrueWindAngles}.
*/
private final Map<Speed, Map<Bearing, Speed>> velocityPredictionPerTrueWindSpeedAndAngle;
/**
@@ -110,20 +95,34 @@ public class ORCCertificateImpl implements ORCCertificate {
private final Map<Speed, Speed> circularRandomSpeedPredictionPerTrueWindSpeed;
private final Map<Speed, Speed> nonSpinnakerSpeedPredictionPerTrueWindSpeed;
public ORCCertificateImpl(String idConsistingOfNatAuthCertNoAndBIN,
String sailnumber, String boatName, String boatClassName,
Distance length, Duration gph,
Double cdl, TimePoint issueDate,
Map<Speed, Map<Bearing, Speed>> velocityPredictionsPerTrueWindSpeedAndAngle,
Map<Speed, Bearing> beatAngles, Map<Speed, Speed> beatVMGPredictionPerTrueWindSpeed,
Map<Speed, Duration> beatAllowancePerTrueWindSpeed, Map<Speed, Bearing> runAngles,
Map<Speed, Speed> runVMGPredictionPerTrueWindSpeed,
private final Speed[] allowancesTrueWindSpeeds;
private final Bearing[] allowancesTrueWindAngles;
/**
* Can be used to specify non-default TWS/TWA values for the time allowances / speed predictions.
*
* @param allowancesTrueWindSpeeds
* a monotonously-increasing sequence of true wind speed (TWS) values for the matrix of time allowances /
* velocity predictions for this certificate. See also
* {@link ORCCertificate#ALLOWANCES_TRUE_WIND_SPEEDS}.
* @param allowancesTrueWindAngles
* a monotonously-increasing sequence of true wind angle (TWA) values for the matrix of time allowances /
* velocity predictions for this certificate. See also
* {@link ORCCertificate#ALLOWANCES_TRUE_WIND_ANGLES}.
*/
public ORCCertificateImpl(Speed[] allowancesTrueWindSpeeds, Bearing[] allowancesTrueWindAngles,
String idConsistingOfNatAuthCertNoAndBIN, String sailnumber, String boatName, String boatClassName,
Distance length, Duration gph, Double cdl, TimePoint issueDate,
Map<Speed, Map<Bearing, Speed>> velocityPredictionsPerTrueWindSpeedAndAngle, Map<Speed, Bearing> beatAngles,
Map<Speed, Speed> beatVMGPredictionPerTrueWindSpeed, Map<Speed, Duration> beatAllowancePerTrueWindSpeed,
Map<Speed, Bearing> runAngles, Map<Speed, Speed> runVMGPredictionPerTrueWindSpeed,
Map<Speed, Duration> runAllowancePerTrueWindSpeed,
Map<Speed, Speed> windwardLeewardSpeedPredictionsPerTrueWindSpeed,
Map<Speed, Speed> longDistanceSpeedPredictionsPerTrueWindSpeed,
Map<Speed, Speed> circularRandomSpeedPredictionsPerTrueWindSpeed,
Map<Speed, Speed> nonSpinnakerSpeedPredictionsPerTrueWindSpeed) {
this.allowancesTrueWindAngles = allowancesTrueWindAngles;
this.allowancesTrueWindSpeeds = allowancesTrueWindSpeeds;
this.idConsistingOfNatAuthCertNoAndBIN = idConsistingOfNatAuthCertNoAndBIN;
this.sailNumber = sailnumber;
this.boatName = boatName;
@@ -145,6 +144,32 @@ public class ORCCertificateImpl implements ORCCertificate {
this.circularRandomSpeedPredictionPerTrueWindSpeed = Collections.unmodifiableMap(circularRandomSpeedPredictionsPerTrueWindSpeed);
this.nonSpinnakerSpeedPredictionPerTrueWindSpeed = Collections.unmodifiableMap(nonSpinnakerSpeedPredictionsPerTrueWindSpeed);
}
/**
* Uses the default TWS/TWA values from {@link ORCCertificate#ALLOWANCES_TRUE_WIND_SPEEDS} and
* {@link ORCCertificate#ALLOWANCES_TRUE_WIND_ANGLES}, respectively, for the matrix of time allowances
* or, conversely, the speed predictions.
*/
public ORCCertificateImpl(String idConsistingOfNatAuthCertNoAndBIN,
String sailnumber, String boatName, String boatClassName,
Distance length, Duration gph,
Double cdl, TimePoint issueDate,
Map<Speed, Map<Bearing, Speed>> velocityPredictionsPerTrueWindSpeedAndAngle,
Map<Speed, Bearing> beatAngles, Map<Speed, Speed> beatVMGPredictionPerTrueWindSpeed,
Map<Speed, Duration> beatAllowancePerTrueWindSpeed, Map<Speed, Bearing> runAngles,
Map<Speed, Speed> runVMGPredictionPerTrueWindSpeed,
Map<Speed, Duration> runAllowancePerTrueWindSpeed,
Map<Speed, Speed> windwardLeewardSpeedPredictionsPerTrueWindSpeed,
Map<Speed, Speed> longDistanceSpeedPredictionsPerTrueWindSpeed,
Map<Speed, Speed> circularRandomSpeedPredictionsPerTrueWindSpeed,
Map<Speed, Speed> nonSpinnakerSpeedPredictionsPerTrueWindSpeed) {
this(ALLOWANCES_TRUE_WIND_SPEEDS, ALLOWANCES_TRUE_WIND_ANGLES, idConsistingOfNatAuthCertNoAndBIN, sailnumber,
boatName, boatClassName, length, gph, cdl, issueDate, velocityPredictionsPerTrueWindSpeedAndAngle,
beatAngles, beatVMGPredictionPerTrueWindSpeed, beatAllowancePerTrueWindSpeed, runAngles,
runVMGPredictionPerTrueWindSpeed, runAllowancePerTrueWindSpeed,
windwardLeewardSpeedPredictionsPerTrueWindSpeed, longDistanceSpeedPredictionsPerTrueWindSpeed,
circularRandomSpeedPredictionsPerTrueWindSpeed, nonSpinnakerSpeedPredictionsPerTrueWindSpeed);
}
@Override
public String getId() {
@@ -255,4 +280,14 @@ public class ORCCertificateImpl implements ORCCertificate {
return "Certificate with ID \""+getId()+"\" for "+getSailNumber()+" / "+getBoatName() + " - Issued on: " + (getIssueDate()==null?"n/a":getIssueDate().asDate())+" with GPH "+
Util.padPositiveValue(getGPHInSecondsToTheMile(), 1, 1, /* round */ true);
}
@Override
public Speed[] getTrueWindSpeeds() {
return allowancesTrueWindSpeeds;
}
@Override
public Bearing[] getTrueWindAngles() {
return allowancesTrueWindAngles;
}
}
@@ -0,0 +1,221 @@
{
"rms":[
{
"NatAuth":"GR",
"BIN":"GR317",
"CertNo":"A00073",
"RefNo":"N\/A",
"SailNo":"GRE-317",
"YachtName":"AVANTES",
"Class":"FIRST 31.7",
"Builder":"BENETEAU",
"Designer":"FINOT",
"Address3":"I.O. HALKIDAS",
"C_Type":"INTL",
"Family":"ORC",
"Division":"C",
"IssueDate":"2020-02-05T12:54:23.484Z",
"Age_Year":1998,
"CrewWT":550,
"LOA":9.49,
"IMSL":9.003,
"Draft":1.94,
"MB":3.216,
"Dspl_Measurement":3936,
"Stability_Index":103.7,
"Dynamic_Allowance":0.31,
"GPH":678,
"TMF_Inshore":0.9229,
"ILCWA":731.4,
"TMF_Offshore":0.9145,
"OSN":656.1,
"CDL":8.155,
"TN_Offshore_Low":0.8578,
"TN_Offshore_Medium":1.1292,
"TN_Offshore_High":1.2751,
"TN_Inshore_Low":0.6639,
"TN_Inshore_Medium":0.9158,
"TN_Inshore_High":1.0666,
"TND_Offshore_Low":786.9,
"TND_Offshore_Medium":597.8,
"TND_Offshore_High":529.4,
"TND_Inshore_Low":1016.7,
"TND_Inshore_Medium":737.1,
"TND_Inshore_High":632.9,
"OSN_Jibs":688.6,
"TMF_Jibs":0.8713,
"Dspl_Sailing":4660,
"WSS":22.41,
"Area_Main":27.04,
"Area_Jib":22.08,
"Area_Sym":61.48,
"Area_ASym":0,
"GPH_Jibs":714.6,
"GPH_Jibs_TOT":0.8396,
"Allowances":{
"WindSpeeds":[
6.1,
8.2,
10.6,
12.1,
14.9,
16.7,
20.1
],
"Beat":[
1175.6,
968.7,
854,
780.9,
752,
744.6,
744.9
],
"Run":[
1074.7,
847.7,
716.4,
628.3,
566,
526.9,
480.1
],
"BeatAngle":[
43.5,
41.6,
40.5,
39.8,
39,
38.6,
39
],
"GybeAngle":[
146,
150,
153,
158,
178,
180,
180
],
"WL":[
1125.2,
908.2,
785.2,
704.6,
659,
635.8,
612.5
],
"CR":[
942.4,
757.3,
657.2,
598.8,
563,
540.1,
512.7
],
"OC":[
1120.1,
852,
706,
616.2,
571.7,
539.8,
487.7
],
"NS":[
1008.9,
804,
691.6,
625.1,
584.2,
558,
527.3
],
"OL":[
1077.4,
874.1,
761.4,
694.6,
658.2,
638.7,
615.2
],
"R52.7":[
767,
645,
577.3,
544,
530.1,
525.8,
525
],
"R64.4":[
725.4,
613.2,
556.7,
530.3,
516.8,
510.9,
506.9
],
"R75":[
697.1,
589.3,
541.9,
519,
502.9,
490.9,
480.5
],
"R90":[
694.3,
577.7,
526.9,
505.5,
495.9,
481.7,
454.3
],
"R110":[
688.6,
568.4,
520.4,
493.9,
469.8,
450.8,
430.3
],
"R120":[
710.9,
583.7,
526.8,
498.8,
473.3,
448.4,
409.9
],
"R135":[
788.3,
640.5,
556.1,
517.8,
492.5,
466.8,
416.9
],
"R150":[
930.8,
734.2,
621.9,
551.9,
517.7,
494,
446.5
]
}
}
]
}
@@ -37,9 +37,9 @@ public class TestORCCertificateImporterJSON {
assertNotNull(moana);
assertEquals(539.1, swan .getGPHInSecondsToTheMile(), 0.0000001);
assertEquals(490.4, moana.getGPHInSecondsToTheMile(), 0.0000001);
assertEquals(862.2, swan .getWindwardLeewardSpeedPrediction().get(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(788.2, moana.getWindwardLeewardSpeedPrediction().get(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(861.0, swan .getLongDistanceSpeedPredictions().get(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(787.2, moana.getLongDistanceSpeedPredictions().get(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(862.2, swan .getWindwardLeewardSpeedPrediction().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(788.2, moana.getWindwardLeewardSpeedPrediction().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(861.0, swan .getLongDistanceSpeedPredictions().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(787.2, moana.getLongDistanceSpeedPredictions().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
}
}
@@ -37,10 +37,10 @@ public class TestORCCertificateImporterRMS {
assertNotNull(moana);
assertEquals(539.1, swan .getGPHInSecondsToTheMile(), 0.0000001);
assertEquals(490.4, moana.getGPHInSecondsToTheMile(), 0.0000001);
assertEquals(862.2, swan .getWindwardLeewardSpeedPrediction().get(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(788.2, moana.getWindwardLeewardSpeedPrediction().get(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(861.0, swan .getLongDistanceSpeedPredictions().get(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(787.2, moana.getLongDistanceSpeedPredictions().get(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(862.2, swan .getWindwardLeewardSpeedPrediction().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(788.2, moana.getWindwardLeewardSpeedPrediction().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(861.0, swan .getLongDistanceSpeedPredictions().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
assertEquals(787.2, moana.getLongDistanceSpeedPredictions().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificateImpl.NAUTICAL_MILE).asSeconds(), 0.1);
}
@Test
@@ -33,7 +33,10 @@ import com.sap.sailing.domain.common.orc.impl.ORCPerformanceCurveCourseImpl;
import com.sap.sailing.domain.common.orc.impl.ORCPerformanceCurveLegImpl;
import com.sap.sailing.domain.orc.impl.ORCCertificatesJsonImporter;
import com.sap.sailing.domain.orc.impl.ORCPerformanceCurveImpl;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Speed;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.DegreeBearingImpl;
@@ -49,6 +52,7 @@ public class TestORCPerformanceCurve {
private static ORCPerformanceCurveCourse alturaCourse;
private static ORCCertificatesCollection importerLocal;
private static ORCCertificatesCollection importerWithSpecificBins;
private static ORCCertificatesCollection importerOnline;
private static final String RESOURCES = "resources/orc/";
@@ -66,6 +70,16 @@ public class TestORCPerformanceCurve {
}
}
public void assertEquals(String message, double a, double b, double accuracy) {
try {
Assert.assertEquals(message, a, b, accuracy);
} catch (AssertionError e) {
if (collectErrors) {
collector.addError(e);
}
}
}
@BeforeClass
public static void initialize() throws IOException, ParseException {
List<ORCPerformanceCurveLeg> legs = new ArrayList<>();
@@ -76,11 +90,12 @@ public class TestORCPerformanceCurve {
legs.add(new ORCPerformanceCurveLegImpl(new NauticalMileDistance(1.03), new DegreeBearingImpl(165)));
legs.add(new ORCPerformanceCurveLegImpl(new NauticalMileDistance(1.17), new DegreeBearingImpl(180)));
alturaCourse = new ORCPerformanceCurveCourseImpl(legs); //this course is the same course as seen in the Altura "IMS Explanation" sheet
// Local File:
File fileGER = new File(RESOURCES + "GER2019.json");
importerLocal = new ORCCertificatesJsonImporter().read(new FileInputStream(fileGER));
// Local File new format with specific TWA/TWS bins:
File fileWithSpecificBins = new File(RESOURCES + "newFormatCertificate.json");
importerWithSpecificBins = new ORCCertificatesJsonImporter().read(new FileInputStream(fileWithSpecificBins));
// Online File:
importerOnline = new ORCCertificatesJsonImporter().read(new URL("https://data.orc.org/public/WPub.dll?action=DownRMS&CountryId=GER&ext=json").openStream());
}
@@ -120,13 +135,13 @@ public class TestORCPerformanceCurve {
final ORCPerformanceCurveCourse course = new ORCPerformanceCurveCourseImpl(Arrays.asList(new ORCPerformanceCurveLegImpl(ORCCertificateImpl.NAUTICAL_MILE, new DegreeBearingImpl(twa))));
final ORCPerformanceCurve performanceCurve = new ORCPerformanceCurveImpl(certificate, course);
for (int i=0; i<expectedAllowancesPerTrueWindSpeed.length; i++) {
assertEquals(expectedAllowancesPerTrueWindSpeed[i], performanceCurve.getAllowancePerCourse(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[i]).asSeconds(), accuracy);
assertEquals(expectedAllowancesPerTrueWindSpeed[i], performanceCurve.getAllowancePerCourse(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[i]).asSeconds(), accuracy);
}
}
@Test
public void testSimpleConstructedCourse() throws FunctionEvaluationException {
ORCCertificateImpl certificate = (ORCCertificateImpl) importerLocal.getCertificateById("GER140772GER5549");
ORCCertificate certificate = (ORCCertificate) importerLocal.getCertificateById("GER140772GER5549");
List<ORCPerformanceCurveLeg> legs = new ArrayList<>();
legs.add(new ORCPerformanceCurveLegImpl(new NauticalMileDistance(1.0), new DegreeBearingImpl(0)));
legs.add(new ORCPerformanceCurveLegImpl(new NauticalMileDistance(1.0), new DegreeBearingImpl(30)));
@@ -142,14 +157,14 @@ public class TestORCPerformanceCurve {
private void testAllowancePerCourse(ORCPerformanceCurve performanceCurve, double... allowancePerNauticalMileInSeconds) throws ArgumentOutsideDomainException {
for (int i=0; i<allowancePerNauticalMileInSeconds.length; i++) {
assertEquals(allowancePerNauticalMileInSeconds[i]*performanceCurve.getCourse().getTotalLength().getNauticalMiles(),
performanceCurve.getAllowancePerCourse(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[i]).asSeconds(), 0.3);
performanceCurve.getAllowancePerCourse(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[i]).asSeconds(), 0.3);
}
}
@Test
public void testPerformanceCurveInversion() throws MaxIterationsExceededException, FunctionEvaluationException {
Double accuracy = 0.1;
ORCCertificateImpl certificate = (ORCCertificateImpl) importerLocal.getCertificateById("GER140772GER5549");
ORCCertificate certificate = (ORCCertificate) importerLocal.getCertificateById("GER140772GER5549");
ORCPerformanceCurveImpl performanceCurve = (ORCPerformanceCurveImpl) new ORCPerformanceCurveImpl(certificate, alturaCourse);
testBackwardForward(accuracy, performanceCurve, 11.5);
testBackwardForward(accuracy, performanceCurve, 17.23);
@@ -247,6 +262,29 @@ public class TestORCPerformanceCurve {
}
}
@Test
public void testAllowancesAndImpliedWindForSpecificBins() throws FunctionEvaluationException, MaxIterationsExceededException {
final double highAccuracy = 0.01;
final double allowanceAccuracy = 0.1;
final Distance ONE_NAUTICAL_MILE = new NauticalMileDistance(1.0);
final ORCCertificate certificateWithSpecificBins = importerWithSpecificBins.getCertificateById("GRA00073GR317");
for (final Bearing twa : certificateWithSpecificBins.getTrueWindAngles()) {
final ORCPerformanceCurveCourse singleLegOneMileCourseWithTwa = createSingleLegCourseWithTwa(twa);
final ORCPerformanceCurve performanceCurveSpecificBins = new ORCPerformanceCurveImpl(certificateWithSpecificBins, singleLegOneMileCourseWithTwa);
for (final Speed tws : certificateWithSpecificBins.getTrueWindSpeeds()) {
final Duration duration = certificateWithSpecificBins.getVelocityPredictionPerTrueWindSpeedAndAngle().get(tws).get(twa).getDuration(ONE_NAUTICAL_MILE);
assertEquals("mismatch for twa "+twa+", tws "+tws, duration.asSeconds(), performanceCurveSpecificBins.getAllowancePerCourse(tws).asSeconds(), allowanceAccuracy);
assertEquals("mismatch for twa "+twa+", tws "+tws, tws.getKnots(), performanceCurveSpecificBins.getImpliedWind(duration).getKnots(), highAccuracy);
}
}
}
private ORCPerformanceCurveCourse createSingleLegCourseWithTwa(Bearing twa) {
List<ORCPerformanceCurveLeg> list = new ArrayList<>();
list.add(new ORCPerformanceCurveLegImpl(new NauticalMileDistance(1), twa));
return new ORCPerformanceCurveCourseImpl(list);
}
// Tests for the calculations with a more complex course which contains some special leg types. (circular random or other)
@Test
public void testComplexCourseImpliedWind() throws FunctionEvaluationException, MaxIterationsExceededException {
@@ -259,25 +297,28 @@ public class TestORCPerformanceCurve {
list.add(new ORCPerformanceCurveLegImpl(new NauticalMileDistance(1), new DegreeBearingImpl(180)));
ORCPerformanceCurveCourse complexCourse = new ORCPerformanceCurveCourseImpl(list);
double accuracy = 0.0001;
final double accuracy = 0.0001;
ORCCertificate certificateMoana = importerLocal.getCertificateById("GER140772GER5549");
ORCCertificate certificateMilan = importerLocal.getCertificateById("GER166844GER7323");
ORCCertificate certificateTutima = importerLocal.getCertificateById("GER140618GER5609");
ORCCertificate certificateBank = importerLocal.getCertificateById("GER140755GER5555");
ORCCertificate certificateHaspa = importerLocal.getCertificateById("GER141411GER6300");
ORCCertificate certificateHalbtrocken = importerLocal.getCertificateById("GER141432GER5564");
ORCCertificate certificateWithSpecificBins=importerWithSpecificBins.getCertificateById("GRA00073GR317");
ORCPerformanceCurve performanceCurveMoana = new ORCPerformanceCurveImpl(certificateMoana, complexCourse);
ORCPerformanceCurve performanceCurveMilan = new ORCPerformanceCurveImpl(certificateMilan, complexCourse);
ORCPerformanceCurve performanceCurveTutima = new ORCPerformanceCurveImpl(certificateTutima, complexCourse);
ORCPerformanceCurve performanceCurveBank = new ORCPerformanceCurveImpl(certificateBank, complexCourse);
ORCPerformanceCurve performanceCurveHaspa = new ORCPerformanceCurveImpl(certificateHaspa, complexCourse);
ORCPerformanceCurve performanceCurveHalbtrocken = new ORCPerformanceCurveImpl(certificateHalbtrocken, complexCourse);
ORCPerformanceCurve performanceCurveSpecificBins = new ORCPerformanceCurveImpl(certificateWithSpecificBins, complexCourse);
assertEquals(15.75777 , performanceCurveMilan .getImpliedWind(Duration.ONE_HOUR.times(1.0)).getKnots(), accuracy);
assertEquals(15.27808 , performanceCurveBank .getImpliedWind(Duration.ONE_HOUR.times(1.25)).getKnots(), accuracy);
assertEquals(15.10141 , performanceCurveMoana .getImpliedWind(Duration.ONE_HOUR.times(1.25)).getKnots(), accuracy);
assertEquals(14.44527 , performanceCurveHaspa .getImpliedWind(Duration.ONE_HOUR.times(1.25)).getKnots(), accuracy);
assertEquals(10.86927 , performanceCurveTutima .getImpliedWind(Duration.ONE_HOUR.times(1.5)).getKnots(), accuracy);
assertEquals(9.13385 , performanceCurveHalbtrocken.getImpliedWind(Duration.ONE_HOUR.times(2.0)).getKnots(), accuracy);
assertEquals(10.83989 , performanceCurveSpecificBins.getImpliedWind(Duration.ONE_HOUR.times(2.0)).getKnots(), accuracy);
}
@Test
@@ -298,12 +339,14 @@ public class TestORCPerformanceCurve {
ORCCertificate certificateBank = importerLocal.getCertificateById("GER140755GER5555");
ORCCertificate certificateHaspa = importerLocal.getCertificateById("GER141411GER6300");
ORCCertificate certificateHalbtrocken = importerLocal.getCertificateById("GER141432GER5564");
ORCCertificate certificateWithSpecificBins=importerWithSpecificBins.getCertificateById("GRA00073GR317");
ORCPerformanceCurve performanceCurveMoana = new ORCPerformanceCurveImpl(certificateMoana, complexCourse);
ORCPerformanceCurve performanceCurveMilan = new ORCPerformanceCurveImpl(certificateMilan, complexCourse);
ORCPerformanceCurve performanceCurveTutima = new ORCPerformanceCurveImpl(certificateTutima, complexCourse);
ORCPerformanceCurve performanceCurveBank = new ORCPerformanceCurveImpl(certificateBank, complexCourse);
ORCPerformanceCurve performanceCurveHaspa = new ORCPerformanceCurveImpl(certificateHaspa, complexCourse);
ORCPerformanceCurve performanceCurveHalbtrocken = new ORCPerformanceCurveImpl(certificateHalbtrocken, complexCourse);
ORCPerformanceCurve performanceCurveSpecificBins = new ORCPerformanceCurveImpl(certificateWithSpecificBins, complexCourse);
assertEquals(Duration.ONE_HOUR.times(1.0).asHours(), performanceCurveMilan.getAllowancePerCourse(new KnotSpeedImpl(15.75777)).asHours(), accuracy);
assertEquals(Duration.ONE_HOUR.times(1.25).asHours(), performanceCurveBank.getAllowancePerCourse(new KnotSpeedImpl(15.27808)).asHours(), accuracy);
@@ -311,7 +354,7 @@ public class TestORCPerformanceCurve {
assertEquals(Duration.ONE_HOUR.times(1.25).asHours(), performanceCurveHaspa.getAllowancePerCourse(new KnotSpeedImpl(14.44527)).asHours(), accuracy);
assertEquals(Duration.ONE_HOUR.times(1.5).asHours(), performanceCurveTutima.getAllowancePerCourse(new KnotSpeedImpl(10.86927)).asHours(), accuracy);
assertEquals(Duration.ONE_HOUR.times(2.0).asHours(), performanceCurveHalbtrocken.getAllowancePerCourse(new KnotSpeedImpl(9.13385)).asHours(), accuracy);
assertEquals(Duration.ONE_HOUR.times(2.0).asHours(), performanceCurveSpecificBins.getAllowancePerCourse(new KnotSpeedImpl(10.83989)).asHours(), accuracy);
}
}
@@ -1,17 +1,22 @@
package com.sap.sailing.domain.orc.impl;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.bind.DatatypeConverter;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.orc.ORCCertificate;
import com.sap.sailing.domain.common.orc.impl.ORCCertificateImpl;
@@ -57,6 +62,13 @@ public class ORCCertificatesCollectionJSON extends AbstractORCCertificatesCollec
private static final String CIRCULAR_RANDOM = "CR";
private static final String NON_SPINNAKER = "NS";
private static final String EMPTY_CERT_NO = " ";
private static final String WIND_SPEEDS = "WindSpeeds";
/**
* Pattern to recognize a true wind angle in the allowances section. Group 1 contains
* the decimal number that represents the true wind angle in degrees.
*/
private static final Pattern twaPattern = Pattern.compile("R(\\d+(\\.\\d+)?)");
private Map<String, JSONObject> certificateJsonObjectsByCertificateId;
@@ -79,18 +91,21 @@ public class ORCCertificatesCollectionJSON extends AbstractORCCertificatesCollec
*/
@Override
public ORCCertificate getCertificateById(String certificateId) {
String boatName = null;
String boatName = null;
String boatclass = null;
Distance length = null;
Duration gph = null;
Double cdl = null;
Distance length = null;
Duration gph = null;
Double cdl = null;
TimePoint issueDate = null;
Map<Speed, Bearing> beatAngles = new HashMap<>();
Map<Speed, Bearing> gybeAngles = new HashMap<>();
Map<Speed, Map<Bearing, Speed>> velocityPredictionPerTrueWindSpeedAndAngle = new HashMap<>();
Map<Bearing, Map<Speed, Duration>> allowanceDurationsPerTrueWindAngleAndSpeed = new HashMap<>(); //per nautical mile
Map<String, Map<Speed, Duration>> predefinedAllowanceDurationsPerTrueWindSpeed = new HashMap<>(); //per nautical mile
JSONObject object = certificateJsonObjectsByCertificateId.get(certificateId);
final Map<Speed, Bearing> beatAngles = new HashMap<>();
final Map<Speed, Bearing> gybeAngles = new HashMap<>();
final Map<Speed, Map<Bearing, Speed>> velocityPredictionPerTrueWindSpeedAndAngle = new HashMap<>();
final Map<Bearing, Map<Speed, Duration>> allowanceDurationsPerTrueWindAngleAndSpeed = new HashMap<>(); // per nautical mile
final Map<String, Map<Speed, Duration>> predefinedAllowanceDurationsPerTrueWindSpeed = new HashMap<>(); // per nautical mile
final Map<Double, Speed> trueWindSpeedMap = new TreeMap<Double,Speed>(); // Using TreeMap implementation to save the TrueWindSpeed
// so that it first sorts the entries as well as avoids duplicates
final Map<Double, Bearing> trueWindAngleMap = new TreeMap<Double,Bearing>(); // Same like trueWindSpeedMap
final JSONObject object = certificateJsonObjectsByCertificateId.get(certificateId);
if (object == null) {
// TODO Throw Exception for certificate by id not found. InvalidArgumentException?
return null;
@@ -98,84 +113,104 @@ public class ORCCertificatesCollectionJSON extends AbstractORCCertificatesCollec
String sailNumber = null;
for (Entry<Object, Object> entry : object.entrySet()) {
switch ((String) entry.getKey()) {
case SAIL_NO:
sailNumber = entry.getValue() == null ? null : entry.getValue().toString();
break;
case LOA:
length = new MeterDistance(((Number) entry.getValue()).doubleValue());
break;
case YACHT_NAME:
boatName = entry.getValue()==null?null:entry.getValue().toString();
break;
case CLASS:
boatclass = (String) entry.getValue();
break;
case GPH2:
gph = new SecondsDurationImpl(((Number) entry.getValue()).doubleValue());
break;
case CDL2:
cdl = ((Number) entry.getValue()).doubleValue();
break;
case ISSUE_DATE:
Date date = DatatypeConverter.parseDateTime((String) entry.getValue()).getTime();
issueDate = new MillisecondsTimePoint(date);
break;
case ALLOWANCES:
final JSONObject allowances = (JSONObject) object.get(ALLOWANCES);
for (final Object aKey : allowances.keySet()) {
final JSONArray array = (JSONArray) allowances.get(aKey);
if (((String) aKey).equals(BEAT_ANGLE)) {
for (int i = 0; i < array.size(); i++) {
beatAngles.put(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[i],
new DegreeBearingImpl(((Number) array.get(i)).doubleValue()));
}
continue;
}
if (((String) aKey).equals(GYBE_ANGLE)) {
for (int i = 0; i < array.size(); i++) {
gybeAngles.put(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[i],
new DegreeBearingImpl(((Number) array.get(i)).doubleValue()));
}
continue;
}
Map<Speed, Duration> twsMap = new HashMap<>();
case SAIL_NO:
sailNumber = entry.getValue() == null ? null : entry.getValue().toString();
break;
case LOA:
length = new MeterDistance(((Number) entry.getValue()).doubleValue());
break;
case YACHT_NAME:
boatName = entry.getValue() == null ? null : entry.getValue().toString();
break;
case CLASS:
boatclass = (String) entry.getValue();
break;
case GPH2:
gph = new SecondsDurationImpl(((Number) entry.getValue()).doubleValue());
break;
case CDL2:
cdl = ((Number) entry.getValue()).doubleValue();
break;
case ISSUE_DATE:
Date date = DatatypeConverter.parseDateTime((String) entry.getValue()).getTime();
issueDate = new MillisecondsTimePoint(date);
break;
case ALLOWANCES:
final JSONObject allowances = (JSONObject) object.get(ALLOWANCES);
// calculating the TWA and TWS before calculating the other dependent values in allowances object
final Object windSpeedsObject = allowances.get(WIND_SPEEDS);
if (windSpeedsObject != null) {
final JSONArray twsArray = (JSONArray) windSpeedsObject;
for (final Object twsObject : twsArray) {
Double twsValue = ((Number) twsObject).doubleValue();
trueWindSpeedMap.put(twsValue, new KnotSpeedImpl(twsValue));
}
} else {
for (Speed speed : ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS) {
trueWindSpeedMap.put(speed.getKnots(), speed);
}
}
final Double [] trueWindSpeedArray = new Double[trueWindSpeedMap.size()];
trueWindSpeedMap.keySet().toArray(trueWindSpeedArray);
for (final Object aKey : allowances.keySet()) {
String keyString = (String) aKey;
final Matcher matcher = twaPattern.matcher(keyString);
if (matcher.lookingAt()) {
final JSONArray array = (JSONArray) allowances.get(keyString);
final Map<Speed, Duration> twsMap = new HashMap<>();
for (int i = 0; i < array.size(); i++) {
twsMap.put(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[i],
final Double allowanceValue = ((Number) array.get(i)).doubleValue();
twsMap.put(trueWindSpeedMap.get(trueWindSpeedArray[i]), new SecondsDurationImpl(allowanceValue));
}
final Double trueWindAngleValue = Double.parseDouble(matcher.group(1));
trueWindAngleMap.put(trueWindAngleValue, new DegreeBearingImpl(trueWindAngleValue));
allowanceDurationsPerTrueWindAngleAndSpeed.put(trueWindAngleMap.get(trueWindAngleValue), twsMap);
}
}
// Now read the various allowances, assuming we find values for the TWS "bins" identified above
for (final Object aKey : allowances.keySet()) {
final JSONArray array = (JSONArray) allowances.get(aKey);
if (((String) aKey).equals(BEAT_ANGLE)) {
for (int i = 0; i < array.size(); i++) {
beatAngles.put(new KnotSpeedImpl(trueWindSpeedArray[i]),
new DegreeBearingImpl(((Number) array.get(i)).doubleValue()));
}
continue;
}
if (((String) aKey).equals(GYBE_ANGLE)) {
for (int i = 0; i < array.size(); i++) {
gybeAngles.put(new KnotSpeedImpl(trueWindSpeedArray[i]),
new DegreeBearingImpl(((Number) array.get(i)).doubleValue()));
}
continue;
}
Map<Speed, Duration> twsMap = new HashMap<>();
//ignore true wind angles key
if (!twaPattern.matcher((String) aKey).lookingAt()) {
for (int i = 0; i < array.size(); i++) {
twsMap.put(new KnotSpeedImpl(trueWindSpeedArray[i]),
new SecondsDurationImpl(((Number) array.get(i)).doubleValue()));
}
switch ((String) aKey) {
case "R52":
case "R60":
case "R75":
case "R90":
case "R110":
case "R120":
case "R135":
case "R150":
allowanceDurationsPerTrueWindAngleAndSpeed.put(
new DegreeBearingImpl(Integer.parseInt(((String) aKey).substring(1))), twsMap);
break;
case BEAT:
case RUN:
predefinedAllowanceDurationsPerTrueWindSpeed.put((String) aKey, twsMap);
break;
default:
predefinedAllowanceDurationsPerTrueWindSpeed.put((String) aKey, twsMap);
break;
}
}
break;
default:
break;
switch ((String) aKey) {
case BEAT:
case RUN:
predefinedAllowanceDurationsPerTrueWindSpeed.put((String) aKey, twsMap);
break;
default:
predefinedAllowanceDurationsPerTrueWindSpeed.put((String) aKey, twsMap);
break;
}
}
break;
default:
break;
}
}
for (Speed tws : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS) {
velocityPredictionPerTrueWindSpeedAndAngle.put(tws, new HashMap<>());
}
for (Bearing keyTWA : allowanceDurationsPerTrueWindAngleAndSpeed.keySet()) {
for (Speed keyTWS : allowanceDurationsPerTrueWindAngleAndSpeed.get(keyTWA).keySet()) {
velocityPredictionPerTrueWindSpeedAndAngle.get(keyTWS).put(keyTWA,
ORCCertificateImpl.NAUTICAL_MILE
trueWindSpeedMap.values().forEach(tws -> velocityPredictionPerTrueWindSpeedAndAngle.put(tws, new HashMap<>()));
for (final Bearing keyTWA : allowanceDurationsPerTrueWindAngleAndSpeed.keySet()) {
for (final Speed keyTWS : allowanceDurationsPerTrueWindAngleAndSpeed.get(keyTWA).keySet()) {
velocityPredictionPerTrueWindSpeedAndAngle.get(keyTWS).put(keyTWA, ORCCertificateImpl.NAUTICAL_MILE
.inTime(allowanceDurationsPerTrueWindAngleAndSpeed.get(keyTWA).get(keyTWS)));
}
}
@@ -203,20 +238,30 @@ public class ORCCertificatesCollectionJSON extends AbstractORCCertificatesCollec
nonSpinnakerSpeedPredictionPerTrueWindSpeed.put(tws, ORCCertificateImpl.NAUTICAL_MILE
.inTime(predefinedAllowanceDurationsPerTrueWindSpeed.get(NON_SPINNAKER).get(tws)));
}
return new ORCCertificateImpl(getId(object), sailNumber, boatName, boatclass, length, gph, cdl,
issueDate, velocityPredictionPerTrueWindSpeedAndAngle, beatAngles, beatVMGPredictionPerTrueWindSpeed,
final Bearing[] dynamicAllowancesTrueWindAngles = trueWindAngleMap.values().toArray(new Bearing[trueWindAngleMap.values().size()]);
final Speed[] dynamicAllowancesTrueWindSpeeds = trueWindSpeedMap.values().toArray(new Speed[trueWindSpeedMap.values().size()]);
// replace array by the default arrays if they match; this will save the space for the dynamically-parsed arrays
return new ORCCertificateImpl(
Arrays.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS, dynamicAllowancesTrueWindSpeeds)
? ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS
: dynamicAllowancesTrueWindSpeeds,
Arrays.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES, dynamicAllowancesTrueWindAngles)
? ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES
: dynamicAllowancesTrueWindAngles,
getId(object), sailNumber, boatName, boatclass, length, gph, cdl, issueDate,
velocityPredictionPerTrueWindSpeedAndAngle, beatAngles, beatVMGPredictionPerTrueWindSpeed,
beatAllowancePerTrueWindSpeed, gybeAngles, runVMGPredictionPerTrueWindSpeed,
runAllowancePerTrueWindSpeed, windwardLeewardSpeedPredictionPerTrueWindSpeed,
longDistanceSpeedPredictionPerTrueWindSpeed, circularRandomSpeedPredictionPerTrueWindSpeed,
nonSpinnakerSpeedPredictionPerTrueWindSpeed);
}
private String getId(JSONObject certificateAsJson) {
return getIdFromFields(certificateAsJson.get(NAT_AUTH).toString(),
certificateAsJson.get(CERT_NO) == null ? EMPTY_CERT_NO : certificateAsJson.get(CERT_NO).toString(),
certificateAsJson.get(BIN).toString());
}
private String getIdFromFields(final String natAuth, final String certNo, final String bin) {
return natAuth + certNo + bin;
}
@@ -122,7 +122,7 @@ public class ORCCertificatesCollectionRMS extends AbstractORCCertificatesCollect
final Map<Speed, Speed> longDistanceSpeedPredictionPerTrueWindSpeed = new HashMap<>();
final Map<Speed, Speed> circularRandomSpeedPredictionPerTrueWindSpeed = new HashMap<>();
final Map<Speed, Speed> nonSpinnakerSpeedPredictionPerTrueWindSpeed = new HashMap<>();
for (Speed tws : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS) {
for (Speed tws : ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS) {
String windSpeed = Integer.toString((int) tws.getKnots());
String beatAngleKey = BEAT_ANGLE + windSpeed;
String beatAllowanceKey = BEAT_ALLOWANCE + windSpeed;
@@ -147,7 +147,7 @@ public class ORCCertificatesCollectionRMS extends AbstractORCCertificatesCollect
nonSpinnakerSpeedPredictionPerTrueWindSpeed.put(tws, ORCCertificateImpl.NAUTICAL_MILE.inTime(
new SecondsDurationImpl(Double.parseDouble(certificateValues.getValue(nonSpinnakerKey)))));
Map<Bearing, Speed> velocityPredictionPerTrueWindAngle = new HashMap<>();
for (Bearing twa : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES ) {
for (Bearing twa : ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES ) {
String twaCoursesKey = TWA_COURSES + Integer.toString((int) twa.getDegrees()) + windSpeed;
velocityPredictionPerTrueWindAngle.put(twa, ORCCertificateImpl.NAUTICAL_MILE.inTime(
new SecondsDurationImpl(Double.parseDouble(certificateValues.getValue(twaCoursesKey)))));
@@ -71,6 +71,16 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
* allowance in sec/nm.
*/
private final UnivariateDifferentiableFunction functionImpliedWindInKnotsToAverageSpeedInKnotsForCourse;
/**
* The {@link ORCCertificate#getTrueWindAngles()} from the certificate
*/
private final Bearing[] trueWindAngles;
/**
* The {@link ORCCertificate#getTrueWindSpeeds()} from the certificate
*/
private final Speed[] trueWindSpeeds;
/**
* Accepts the simplified polar data, one "column" for each of the defined true wind speeds, where each column is a
@@ -79,6 +89,8 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
*/
public ORCPerformanceCurveImpl(ORCCertificate certificate, ORCPerformanceCurveCourse course) throws FunctionEvaluationException {
this.course = course;
this.trueWindAngles = certificate.getTrueWindAngles();
this.trueWindSpeeds = certificate.getTrueWindSpeeds();
functionImpliedWindInKnotsToAverageSpeedInKnotsForCourse = createPerformanceCurve(certificate);
}
@@ -100,8 +112,8 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
*/
private UnivariateDifferentiableFunction createPerformanceCurve(ORCCertificate certificate) throws FunctionEvaluationException {
final Map<Speed, Duration> allowancesForCoursePerTrueWindSpeed = createAllowancesPerCourse(certificate);
double[] xs = new double[ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS.length+2];
double[] ys = new double[ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS.length+2];
double[] xs = new double[certificate.getTrueWindSpeeds().length+2];
double[] ys = new double[certificate.getTrueWindSpeeds().length+2];
int i = 0;
xs[i] = 0; // see original Pascal code; the first "knot" is set to (0.0, 0.0)
ys[i] = 0;
@@ -142,7 +154,7 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
for (final ORCPerformanceCurveLeg leg : course.getLegs()) {
allowancesPerLeg.put(leg, createAllowancePerLeg(leg, certificate));
}
for (final Speed tws : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS) {
for (final Speed tws : certificate.getTrueWindSpeeds()) {
Duration allowancePerTws = new SecondsDurationImpl(0);
for (final ORCPerformanceCurveLeg leg : course.getLegs()) {
allowancePerTws = allowancePerTws.plus(allowancesPerLeg.get(leg).get(tws));
@@ -168,7 +180,7 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
switch (leg.getType()) {
case TWA:
for (final Speed tws : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS) {
for (final Speed tws : certificate.getTrueWindSpeeds()) {
// Case switching on TWA (0. TWA == 0; 1. TWA < Beat; 2. Beat < TWA < Gybe; 3. Gybe < TWA; 4. TWA == 180)
if (leg.getTwa().abs().compareTo(beatAngles.get(tws)) <= 0) {
// Case 0 & 1 - result = beatVMG * distance * cos(TWA)
@@ -186,23 +198,23 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
}
break;
case CIRCULAR_RANDOM:
for (final Speed tws : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS) {
for (final Speed tws : certificate.getTrueWindSpeeds()) {
result.put(tws, certificate.getCircularRandomSpeedPredictions().get(tws).getDuration(leg.getLength()));
}
break;
case LONG_DISTANCE:
for (final Speed tws : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS) {
for (final Speed tws : certificate.getTrueWindSpeeds()) {
result.put(tws, certificate.getLongDistanceSpeedPredictions().get(tws).getDuration(leg.getLength()));
}
break;
case WINDWARD_LEEWARD:
case WINDWARD_LEEWARD_REAL_LIVE:
for (final Speed tws : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS) {
for (final Speed tws : certificate.getTrueWindSpeeds()) {
result.put(tws, certificate.getWindwardLeewardSpeedPrediction().get(tws).getDuration(leg.getLength()));
}
break;
case NON_SPINNAKER:
for (final Speed tws : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS) {
for (final Speed tws :certificate.getTrueWindSpeeds()) {
result.put(tws, certificate.getNonSpinnakerSpeedPredictions().get(tws).getDuration(leg.getLength()));
}
break;
@@ -216,7 +228,7 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
*/
private double[][] createPolarsPerTrueWindSpeed(Speed trueWindSpeed, Map<Speed, Map<Bearing, Speed>> reachingSpeedPredictionsPerTrueWindSpeedAndAngle,
Map<Speed, Bearing> beatAngles, Map<Speed, Speed> beatVMGPredictionPerTrueWindSpeed,
Map<Speed, Bearing> runAngles, Map<Speed, Speed> runVMGPredictionPerTrueWindSpeed) {
Map<Speed, Bearing> runAngles, Map<Speed, Speed> runVMGPredictionPerTrueWindSpeed, Bearing[] trueWindAngles) {
ArrayList<Double> resultWindAngles = new ArrayList<>();
ArrayList<Double> resultSpeedsOverGroundInKnots = new ArrayList<>();
Bearing beatAngle = beatAngles.get(trueWindSpeed);
@@ -228,7 +240,7 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
resultSpeedsOverGroundInKnots.add(beatVMGPredictionPerTrueWindSpeed.get(trueWindSpeed).getKnots() / Math.cos(beatAngle.add(MINUS_TWO_DEGREES).getRadians()));
resultWindAngles.add(beatAngle.getDegrees());
resultSpeedsOverGroundInKnots.add(beatVMGPredictionPerTrueWindSpeed.get(trueWindSpeed).getKnots() / Math.cos(beatAngle.getRadians()));
for (final Bearing twa : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES) {
for (final Bearing twa : trueWindAngles) {
if (twa.compareTo(beatAngle) > 0 && twa.compareTo(runAngle) < 0) {
resultWindAngles.add(twa.getDegrees());
resultSpeedsOverGroundInKnots.add(reachingSpeedPredictionsPerTrueWindSpeedAndAngle.get(trueWindSpeed).get(twa).getKnots());
@@ -251,14 +263,14 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
@Override
public Speed getImpliedWind(Duration durationToCompleteCourse) throws MaxIterationsExceededException, FunctionEvaluationException{
final Speed averageSpeedOnCourse = getCourse().getTotalLength().inTime(durationToCompleteCourse);
final double[] predictedSpeedsInKnotsForTotalCourseByTrueWindSpeed = Arrays.stream(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS).mapToDouble
final double[] predictedSpeedsInKnotsForTotalCourseByTrueWindSpeed = Arrays.stream(trueWindSpeeds).mapToDouble
(tws->{ return functionImpliedWindInKnotsToAverageSpeedInKnotsForCourse.value(tws.getKnots()); }).toArray();
final Speed result;
// Corner cases for Allowance > Allowance(20kt) or Allowance < Allowance(6kt)
if (averageSpeedOnCourse.getKnots() >= predictedSpeedsInKnotsForTotalCourseByTrueWindSpeed[predictedSpeedsInKnotsForTotalCourseByTrueWindSpeed.length-1]) {
result = ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS.length-1];
result = trueWindSpeeds[trueWindSpeeds.length-1];
} else if (averageSpeedOnCourse.equals(Speed.NULL) || averageSpeedOnCourse.getKnots() <= predictedSpeedsInKnotsForTotalCourseByTrueWindSpeed[0]) {
result = ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[0];
result = trueWindSpeeds[0];
} else {
// find the polynomial splined function that produces the durationToCompleteCourse within its validity range
int i = 1; // skip the auxiliary spline segment from (0.0, 0.0) to (6.0, ...)
@@ -272,7 +284,7 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
final UnivariateDifferentiableFunction targetZeroFunction = FunctionUtils.add(functionImpliedWindInKnotsToAverageSpeedInKnotsForCourse,
FunctionUtils.multiply((UnivariateDifferentiableFunction) averageSpeedInKnots,
(UnivariateDifferentiableFunction) new Constant(-1)));
final double impliedWindSpeedInKnots = newtonSolver.solve(1000, targetZeroFunction, 10 /* knots of true wind speed */);
final double impliedWindSpeedInKnots = newtonSolver.solve(1000, targetZeroFunction, 12 /* knots of true wind speed */);
result = new KnotSpeedImpl(impliedWindSpeedInKnots);
}
return result;
@@ -294,9 +306,18 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
Map<Speed, Bearing> beatAngles, Map<Speed, Speed> beatVMGPredictionPerTrueWindSpeed,
Map<Speed, Bearing> runAngles, Map<Speed, Speed> runVMGPredictionPerTrueWindSpeed, Speed trueWindSpeed,
Bearing trueWindAngle) throws FunctionEvaluationException, IllegalArgumentException {
return getLagrangeSpeedPredictionForTrueWindSpeedAndAngle(twaAllowances, beatAngles,
beatVMGPredictionPerTrueWindSpeed, runAngles, runVMGPredictionPerTrueWindSpeed, trueWindSpeed,
trueWindAngle, trueWindAngles);
}
public Speed getLagrangeSpeedPredictionForTrueWindSpeedAndAngle(Map<Speed, Map<Bearing, Speed>> twaAllowances,
Map<Speed, Bearing> beatAngles, Map<Speed, Speed> beatVMGPredictionPerTrueWindSpeed,
Map<Speed, Bearing> runAngles, Map<Speed, Speed> runVMGPredictionPerTrueWindSpeed, Speed trueWindSpeed,
Bearing trueWindAngle, Bearing[] trueWindAngles) throws FunctionEvaluationException, IllegalArgumentException {
final Speed result;
double[][] polarPoints = createPolarsPerTrueWindSpeed(trueWindSpeed, twaAllowances, beatAngles,
beatVMGPredictionPerTrueWindSpeed, runAngles, runVMGPredictionPerTrueWindSpeed);
beatVMGPredictionPerTrueWindSpeed, runAngles, runVMGPredictionPerTrueWindSpeed, trueWindAngles);
double[] twaPolarPoints = polarPoints[0];
double[] speedsOverGroundInKnotsPolarPoints = polarPoints[1];
int i = -1; // after the loop, i equals the next higher available polar data for the given TWA
@@ -330,9 +351,9 @@ public class ORCPerformanceCurveImpl implements Serializable, ORCPerformanceCurv
* the {@link #course} (which may be a prefix of a longer course) at the true wind speed provided to the resulting
* map as the key. Iteration order is in ascending true wind speeds.
*/
public LinkedHashMap<Speed, Duration> getAllowancesPerTrueWindSpeedsForCourse() throws ArgumentOutsideDomainException {
private LinkedHashMap<Speed, Duration> getAllowancesPerTrueWindSpeedsForCourse() throws ArgumentOutsideDomainException {
final LinkedHashMap<Speed, Duration> result = new LinkedHashMap<>();
for (final Speed tws : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS) {
for (final Speed tws : trueWindSpeeds) {
result.put(tws, getAllowancePerCourse(tws));
}
return result;
@@ -1,12 +1,15 @@
package com.google.gwt.user.client.rpc.core.com.sap.sailing.domain.common.orc.impl;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.sap.sailing.domain.common.orc.ORCCertificate;
import com.sap.sailing.domain.common.orc.impl.ORCCertificateImpl;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
@@ -43,6 +46,10 @@ public class ORCCertificateImpl_CustomFieldSerializer extends CustomFieldSeriali
streamWriter.writeObject(new HashMap<>(instance.getLongDistanceSpeedPredictions()));
streamWriter.writeObject(new HashMap<>(instance.getCircularRandomSpeedPredictions()));
streamWriter.writeObject(new HashMap<>(instance.getNonSpinnakerSpeedPredictions()));
streamWriter.writeObject(Arrays.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES, instance.getTrueWindAngles()) ? null :
Arrays.asList(instance.getTrueWindAngles()));
streamWriter.writeObject(Arrays.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS, instance.getTrueWindSpeeds()) ? null :
Arrays.asList(instance.getTrueWindSpeeds()));
}
@Override
@@ -88,7 +95,13 @@ public class ORCCertificateImpl_CustomFieldSerializer extends CustomFieldSeriali
final Map<Speed, Speed> circularRandomSpeedPredictionsPerTrueWindSpeed = (Map<Speed, Speed>) streamReader.readObject();
@SuppressWarnings("unchecked")
final Map<Speed, Speed> nonSpinnakerSpeedPredictionsPerTrueWindSpeed = (Map<Speed, Speed>) streamReader.readObject();
return new ORCCertificateImpl(idConsistingOfNatAuthCertNoAndBIN, sailnumber, boatName, boatclass, length, gph,
@SuppressWarnings("unchecked")
final List<Bearing> readTWAs = (List<Bearing>) streamReader.readObject();
final Bearing[] trueWindAngles = readTWAs == null ? ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES : readTWAs.toArray(new Bearing[readTWAs.size()]);
@SuppressWarnings("unchecked")
final List<Speed> readTWSs = (List<Speed>) streamReader.readObject();
final Speed[] trueWindSpeeds = readTWSs == null ? ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS : readTWSs.toArray(new Speed[readTWSs.size()]);
return new ORCCertificateImpl(trueWindSpeeds,trueWindAngles,idConsistingOfNatAuthCertNoAndBIN, sailnumber, boatName, boatclass, length, gph,
cdl, issueDate, velocityPredictionsPerTrueWindSpeedAndAngle, beatAngles,
beatVMGPredictionPerTrueWindSpeed, beatAllowancePerTrueWindSpeed, runAngles,
runVMGPredictionPerTrueWindSpeed, runAllowancePerTrueWindSpeed,
@@ -110,6 +110,7 @@ public class MarkPropertiesPanel extends FlowPanel {
strings.add(t.getName());
strings.add(t.getCommonMarkProperties().getShortName());
strings.add(t.getUuid().toString());
Util.addAll(t.getTags(), strings);
return strings;
}
@@ -38,7 +38,6 @@ import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.ProvidesResize;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.WidgetCollection;
@@ -202,7 +201,7 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel {
*
* @author Simon Marcel Pamies, Axel Uhl
*/
abstract class Splitter extends AbsolutePanel implements RequiresResize, ProvidesResize {
abstract class Splitter extends AbsolutePanel {
protected final Widget target;
private int offset;
@@ -319,7 +318,6 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel {
syncedCenterSize = newCenterSize;
centerSize = newCenterSize;
}
return Math.max(((LayoutData) target.getLayoutData()).size + centerSize, 0);
}
@@ -376,11 +374,6 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel {
}
}
@Override
public void onResize() {
setAssociatedWidgetSize(getTargetSize(), /* defer */ false);
}
protected abstract int getAbsolutePosition();
protected abstract double getCenterSize();
protected abstract int getEventPosition(Event event);
@@ -851,7 +844,7 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel {
if (splitter != null) {
LayoutData layoutData = (LayoutData) widget.getLayoutData();
if (hidden) {
if(layoutData.size > 0) {
if (layoutData.size > 0) {
layoutData.oldSize = layoutData.size;
}
widget.setVisible(false);
@@ -861,7 +854,6 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel {
}
}
splitter.setAssociatedWidgetSize(0, /* defer */false);
// need to ensure visibility of dragger, do NOT use setVisible() as south/north splitter should be shown always
splitter.setDraggerVisible(!hidden);
} else {
@@ -1,8 +1,12 @@
package com.sap.sailing.server.gateway.deserialization.racelog.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
@@ -46,7 +50,11 @@ public class ORCCertificateJsonDeserializer implements JsonDeserializer<ORCCerti
Map<Speed, Speed> longDistanceSpeedPredictionPerTrueWindSpeed = new HashMap<>();
Map<Speed, Speed> circularRandomSpeedPredictionPerTrueWindSpeed = new HashMap<>();
Map<Speed, Speed> nonSpinnakerSpeedPredictionPerTrueWindSpeed = new HashMap<>();
for (Speed tws : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS) {
Speed[] trueWindSpeeds = convertJsonArrayOfDoublesToArrayOfObjectsOrReturnDefault(json.get(ORCCertificateJsonSerializer.ORC_CERTIFICATE_TRUE_WIND_SPEEDS_IN_KNOTS),
ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS, speedInKnots->new KnotSpeedImpl(speedInKnots), new Speed[0]);
Bearing[] trueWindAngles = convertJsonArrayOfDoublesToArrayOfObjectsOrReturnDefault(json.get(ORCCertificateJsonSerializer.ORC_CERTIFICATE_TRUE_WIND_ANGLES_IN_TRUE_DEGREES),
ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES, twaInTrueDegrees->new DegreeBearingImpl(twaInTrueDegrees), new Bearing[0]);
for (Speed tws : trueWindSpeeds) {
String twsKey = ORCCertificateJsonSerializer.speedToKnotsString(tws);
beatAngles.put(tws, new DegreeBearingImpl(
((Number) ((JSONObject) json.get(ORCCertificateJsonSerializer.ORC_CERTIFICATE_BEAT_ANGLES))
@@ -84,9 +92,8 @@ public class ORCCertificateJsonDeserializer implements JsonDeserializer<ORCCerti
new KnotSpeedImpl(((Number) ((JSONObject) json
.get(ORCCertificateJsonSerializer.ORC_CERTIFICATE_NON_SPINNAKER_SPEED_PREDICTIONS))
.get(twsKey)).doubleValue()));
Map<Bearing, Speed> velocityPredictionAtCurrentTrueWindSpeedPerTrueWindAngle = new HashMap<>();
for (Bearing twa : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES) {
for (Bearing twa : trueWindAngles) {
String twaKey = ORCCertificateJsonSerializer.bearingToDegreeString(twa);
velocityPredictionAtCurrentTrueWindSpeedPerTrueWindAngle.put(twa,
new KnotSpeedImpl(((Number) ((JSONObject) ((JSONObject) json
@@ -97,7 +104,7 @@ public class ORCCertificateJsonDeserializer implements JsonDeserializer<ORCCerti
velocityPredictionAtCurrentTrueWindSpeedPerTrueWindAngle);
}
final String idConsistingOfNatAuthCertNoAndBIN = json.get(ORCCertificateJsonSerializer.ORC_CERTIFICATE_ID).toString();
final ORCCertificate certificate = new ORCCertificateImpl(idConsistingOfNatAuthCertNoAndBIN, sailnumber,
final ORCCertificate certificate = new ORCCertificateImpl(trueWindSpeeds,trueWindAngles,idConsistingOfNatAuthCertNoAndBIN, sailnumber,
boatName, boatclass, length, gph, cdl, issueDate, velocityPredictionsPerTrueWindSpeedAndAngle,
beatAngles, beatVMGPredictionPerTrueWindSpeed, beatAllowancePerTrueWindSpeed, runAngles,
runVMGPredictionPerTrueWindSpeed, runAllowancePerTrueWindSpeed,
@@ -105,4 +112,21 @@ public class ORCCertificateJsonDeserializer implements JsonDeserializer<ORCCerti
circularRandomSpeedPredictionPerTrueWindSpeed, nonSpinnakerSpeedPredictionPerTrueWindSpeed);
return certificate;
}
private <T> T[] convertJsonArrayOfDoublesToArrayOfObjectsOrReturnDefault(final Object supposedJsonArray,
final T[] defaults, final Function<Double, T> constructor, T[] array) {
final T[] result;
final JSONArray windAnglesJsonArray;
if (supposedJsonArray == null || !(supposedJsonArray instanceof JSONArray) || (windAnglesJsonArray=(JSONArray) supposedJsonArray).isEmpty()) {
result = defaults;
} else {
final List<T> resultList = new ArrayList<>();
for (final Object number : windAnglesJsonArray) {
resultList.add(constructor.apply((Double) number));
}
final T[] tArray = resultList.toArray(array);
result = tArray;
}
return result;
}
}
@@ -1,9 +1,12 @@
package com.sap.sailing.server.gateway.serialization.racelog.impl;
import java.text.MessageFormat;
import java.util.function.Function;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.common.orc.ORCCertificate;
import com.sap.sailing.domain.common.orc.impl.ORCCertificateImpl;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Speed;
@@ -28,6 +31,7 @@ public class ORCCertificateJsonSerializer implements JsonSerializer<ORCCertifica
public static final String ORC_CERTIFICATE_ID = "ID";
public static final String ORC_CERTIFICATE_LENGTH = "length";
public static final String ORC_CERTIFICATE_ISSUE_DATE = "issueDate";
public static final String ORC_CERTIFICATE_TWS_KNOTS = "ORC_CERTIFICATE_TWS_{0}KT";
public static final String ORC_CERTIFICATE_TWS_6KT = "ORC_CERTIFICATE_TWS_6KT";
public static final String ORC_CERTIFICATE_TWS_8KT = "ORC_CERTIFICATE_TWS_8KT";
public static final String ORC_CERTIFICATE_TWS_10KT = "ORC_CERTIFICATE_TWS_10KT";
@@ -35,6 +39,8 @@ public class ORCCertificateJsonSerializer implements JsonSerializer<ORCCertifica
public static final String ORC_CERTIFICATE_TWS_14KT = "ORC_CERTIFICATE_TWS_14KT";
public static final String ORC_CERTIFICATE_TWS_16KT = "ORC_CERTIFICATE_TWS_16KT";
public static final String ORC_CERTIFICATE_TWS_20KT = "ORC_CERTIFICATE_TWS_20KT";
public static final String ORC_CERTIFICATE_R_PERDICTION = "ORC_CERTIFICATE_R{0}_PREDICTION";
public static final String ORC_CERTIFICATE_R52_PREDICTION = "ORC_CERTIFICATE_R52_PREDICTION";
public static final String ORC_CERTIFICATE_R60_PREDICTION = "ORC_CERTIFICATE_R60_PREDICTION";
public static final String ORC_CERTIFICATE_R75_PREDICTION = "ORC_CERTIFICATE_R75_PREDICTION";
@@ -43,6 +49,8 @@ public class ORCCertificateJsonSerializer implements JsonSerializer<ORCCertifica
public static final String ORC_CERTIFICATE_R120_PREDICTION = "ORC_CERTIFICATE_R120_PREDICTION";
public static final String ORC_CERTIFICATE_R135_PREDICTION = "ORC_CERTIFICATE_R135_PREDICTION";
public static final String ORC_CERTIFICATE_R150_PREDICTION = "ORC_CERTIFICATE_R150_PREDICTION";
public static final String ORC_CERTIFICATE_TRUE_WIND_SPEEDS_IN_KNOTS = "trueWindSpeedsInKnots";
public static final String ORC_CERTIFICATE_TRUE_WIND_ANGLES_IN_TRUE_DEGREES = "trueWindAnglesInTrueDegrees";
@Override
public JSONObject serialize(ORCCertificate certificate) {
@@ -54,8 +62,8 @@ public class ORCCertificateJsonSerializer implements JsonSerializer<ORCCertifica
result.put(ORC_CERTIFICATE_GPH, certificate.getGPHInSecondsToTheMile());
result.put(ORC_CERTIFICATE_CDL, certificate.getCDL());
result.put(ORC_CERTIFICATE_LENGTH, certificate.getLengthOverAll().getMeters());
result.put(ORC_CERTIFICATE_ISSUE_DATE, certificate.getIssueDate()==null?null:certificate.getIssueDate().asMillis());
result.put(ORC_CERTIFICATE_ISSUE_DATE,
certificate.getIssueDate() == null ? null : certificate.getIssueDate().asMillis());
JSONObject beatAngles = new JSONObject();
JSONObject runAngles = new JSONObject();
JSONObject beatAllowances = new JSONObject();
@@ -67,7 +75,7 @@ public class ORCCertificateJsonSerializer implements JsonSerializer<ORCCertifica
JSONObject longDistancePredictions = new JSONObject();
JSONObject nonSpinnakerPredictions = new JSONObject();
JSONObject velocityPredictionsPerTrueWindSpeedAndAngle = new JSONObject();
for (Speed tws : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS) {
for (Speed tws : certificate.getTrueWindSpeeds()) {
String keyTWS = speedToKnotsString(tws);
JSONObject velocityPredictionsPerTrueWindAngle = new JSONObject();
beatAngles.put(keyTWS, certificate.getBeatAngles().get(tws).getDegrees());
@@ -80,13 +88,13 @@ public class ORCCertificateJsonSerializer implements JsonSerializer<ORCCertifica
circularRandomPredictions.put(keyTWS, certificate.getCircularRandomSpeedPredictions().get(tws).getKnots());
longDistancePredictions.put(keyTWS, certificate.getLongDistanceSpeedPredictions().get(tws).getKnots());
nonSpinnakerPredictions.put(keyTWS, certificate.getNonSpinnakerSpeedPredictions().get(tws).getKnots());
for (Bearing twa : ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES) {
for (Bearing twa : certificate.getTrueWindAngles()) {
String keyTWA = bearingToDegreeString(twa);
velocityPredictionsPerTrueWindAngle.put(keyTWA, certificate.getVelocityPredictionPerTrueWindSpeedAndAngle().get(tws).get(twa).getKnots());
velocityPredictionsPerTrueWindAngle.put(keyTWA,
certificate.getVelocityPredictionPerTrueWindSpeedAndAngle().get(tws).get(twa).getKnots());
}
velocityPredictionsPerTrueWindSpeedAndAngle.put(keyTWS, velocityPredictionsPerTrueWindAngle);
}
result.put(ORC_CERTIFICATE_BEAT_ANGLES, beatAngles);
result.put(ORC_CERTIFICATE_RUN_ANGLES, runAngles);
result.put(ORC_CERTIFICATE_BEAT_ALLOWANCES, beatAllowances);
@@ -98,47 +106,70 @@ public class ORCCertificateJsonSerializer implements JsonSerializer<ORCCertifica
result.put(ORC_CERTIFICATE_CIRCULAR_RANDOM_SPEED_PREDICTIONS, circularRandomPredictions);
result.put(ORC_CERTIFICATE_NON_SPINNAKER_SPEED_PREDICTIONS, nonSpinnakerPredictions);
result.put(ORC_CERTIFICATE_TWA_SPEED_PREDICTIONS, velocityPredictionsPerTrueWindSpeedAndAngle);
if (certificate.getTrueWindAngles() != ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES) {
result.put(ORC_CERTIFICATE_TRUE_WIND_ANGLES_IN_TRUE_DEGREES, convertObjectsToJsonArrayOfDoubles(twa->twa.getDegrees(), certificate.getTrueWindAngles()));
}
if (certificate.getTrueWindSpeeds() != ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS) {
result.put(ORC_CERTIFICATE_TRUE_WIND_SPEEDS_IN_KNOTS, convertObjectsToJsonArrayOfDoubles(tws->tws.getKnots(), certificate.getTrueWindSpeeds()));
}
return result;
}
@SafeVarargs
private final <T> JSONArray convertObjectsToJsonArrayOfDoubles(Function<T, Double> converterToDouble, T... objects) {
final JSONArray jsonArray = new JSONArray();
for (T t : objects) {
jsonArray.add(converterToDouble.apply(t));
}
return jsonArray;
}
public static String speedToKnotsString(Speed speed) {
String result = null;
if (speed.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[0])) {
if (speed.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0])) {
result = ORC_CERTIFICATE_TWS_6KT;
} else if (speed.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[1])) {
} else if (speed.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[1])) {
result = ORC_CERTIFICATE_TWS_8KT;
} else if (speed.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[2])) {
} else if (speed.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[2])) {
result = ORC_CERTIFICATE_TWS_10KT;
} else if (speed.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[3])) {
} else if (speed.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[3])) {
result = ORC_CERTIFICATE_TWS_12KT;
} else if (speed.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[4])) {
} else if (speed.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[4])) {
result = ORC_CERTIFICATE_TWS_14KT;
} else if (speed.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[5])) {
} else if (speed.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[5])) {
result = ORC_CERTIFICATE_TWS_16KT;
} else if (speed.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_SPEEDS[6])) {
} else if (speed.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[6])) {
result = ORC_CERTIFICATE_TWS_20KT;
} else {
String speedKnots = String.valueOf(speed.getKnots());
speedKnots = speedKnots.replace(".", "P");
result = MessageFormat.format(ORC_CERTIFICATE_TWS_KNOTS, speedKnots);
}
return result;
}
public static String bearingToDegreeString(Bearing bearing) {
String result = null;
if (bearing.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES[0])) {
if (bearing.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES[0])) {
result = ORC_CERTIFICATE_R52_PREDICTION;
} else if (bearing.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES[1])) {
} else if (bearing.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES[1])) {
result = ORC_CERTIFICATE_R60_PREDICTION;
} else if (bearing.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES[2])) {
} else if (bearing.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES[2])) {
result = ORC_CERTIFICATE_R75_PREDICTION;
} else if (bearing.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES[3])) {
} else if (bearing.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES[3])) {
result = ORC_CERTIFICATE_R90_PREDICTION;
} else if (bearing.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES[4])) {
} else if (bearing.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES[4])) {
result = ORC_CERTIFICATE_R110_PREDICTION;
} else if (bearing.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES[5])) {
} else if (bearing.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES[5])) {
result = ORC_CERTIFICATE_R120_PREDICTION;
} else if (bearing.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES[6])) {
} else if (bearing.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES[6])) {
result = ORC_CERTIFICATE_R135_PREDICTION;
} else if (bearing.equals(ORCCertificateImpl.ALLOWANCES_TRUE_WIND_ANGLES[7])) {
} else if (bearing.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES[7])) {
result = ORC_CERTIFICATE_R150_PREDICTION;
} else {
String angleDegress = String.valueOf(bearing.getDegrees());
angleDegress = angleDegress.replace(".", "P");
result = MessageFormat.format(ORC_CERTIFICATE_R_PERDICTION, String.valueOf(angleDegress));
}
return result;
}
@@ -0,0 +1,221 @@
{
"rms":[
{
"NatAuth":"GR",
"BIN":"GR317",
"CertNo":"A00073",
"RefNo":"N\/A",
"SailNo":"GRE-317",
"YachtName":"AVANTES",
"Class":"FIRST 31.7",
"Builder":"BENETEAU",
"Designer":"FINOT",
"Address3":"I.O. HALKIDAS",
"C_Type":"INTL",
"Family":"ORC",
"Division":"C",
"IssueDate":"2020-02-05T12:54:23.484Z",
"Age_Year":1998,
"CrewWT":550,
"LOA":9.49,
"IMSL":9.003,
"Draft":1.94,
"MB":3.216,
"Dspl_Measurement":3936,
"Stability_Index":103.7,
"Dynamic_Allowance":0.31,
"GPH":678,
"TMF_Inshore":0.9229,
"ILCWA":731.4,
"TMF_Offshore":0.9145,
"OSN":656.1,
"CDL":8.155,
"TN_Offshore_Low":0.8578,
"TN_Offshore_Medium":1.1292,
"TN_Offshore_High":1.2751,
"TN_Inshore_Low":0.6639,
"TN_Inshore_Medium":0.9158,
"TN_Inshore_High":1.0666,
"TND_Offshore_Low":786.9,
"TND_Offshore_Medium":597.8,
"TND_Offshore_High":529.4,
"TND_Inshore_Low":1016.7,
"TND_Inshore_Medium":737.1,
"TND_Inshore_High":632.9,
"OSN_Jibs":688.6,
"TMF_Jibs":0.8713,
"Dspl_Sailing":4660,
"WSS":22.41,
"Area_Main":27.04,
"Area_Jib":22.08,
"Area_Sym":61.48,
"Area_ASym":0,
"GPH_Jibs":714.6,
"GPH_Jibs_TOT":0.8396,
"Allowances":{
"WindSpeeds":[
6.1,
8.2,
10.6,
12.1,
14.9,
16.7,
20.1
],
"Beat":[
1175.6,
968.7,
854,
780.9,
752,
744.6,
744.9
],
"Run":[
1074.7,
847.7,
716.4,
628.3,
566,
526.9,
480.1
],
"BeatAngle":[
43.5,
41.6,
40.5,
39.8,
39,
38.6,
39
],
"GybeAngle":[
146,
150,
153,
158,
178,
180,
180
],
"WL":[
1125.2,
908.2,
785.2,
704.6,
659,
635.8,
612.5
],
"CR":[
942.4,
757.3,
657.2,
598.8,
563,
540.1,
512.7
],
"OC":[
1120.1,
852,
706,
616.2,
571.7,
539.8,
487.7
],
"NS":[
1008.9,
804,
691.6,
625.1,
584.2,
558,
527.3
],
"OL":[
1077.4,
874.1,
761.4,
694.6,
658.2,
638.7,
615.2
],
"R52.7":[
767,
645,
577.3,
544,
530.1,
525.8,
525
],
"R64.4":[
725.4,
613.2,
556.7,
530.3,
516.8,
510.9,
508.9
],
"R75":[
697.1,
589.3,
541.9,
519,
502.9,
490.9,
480.5
],
"R90":[
694.3,
577.7,
526.9,
505.5,
495.9,
481.7,
454.3
],
"R110":[
688.6,
568.4,
520.4,
493.9,
469.8,
450.8,
430.3
],
"R120":[
710.9,
583.7,
526.8,
498.8,
473.3,
448.4,
409.9
],
"R135":[
788.3,
640.5,
556.1,
517.8,
492.5,
466.8,
416.9
],
"R150":[
930.8,
734.2,
621.9,
551.9,
517.7,
494,
446.5
]
}
}
]
}
@@ -0,0 +1,215 @@
{
"rms": [
{
"NatAuth": "GRE",
"BIN": "GR49735",
"CertNo": "000021",
"RefNo": "GRE01015829",
"SailNo": "GRE-49735",
"YachtName": "NEMO",
"Class": "PHANTOM 34",
"Builder": "Genzel",
"Designer": "Streuer",
"Address3": "NO RAHON",
"C_Type": "CLUB",
"Division": "C",
"IssueDate": "2019-06-11T21:37:02.000Z",
"Age_Year": 1988,
"CrewWT": 400,
"LOA": 10.3,
"IMSL": 8.518,
"Draft": 1.777,
"MB": 3.15,
"Dspl_Measurement": 6386,
"Stability_Index": 131.7,
"Dynamic_Allowance": 0.702,
"GPH": 780.1,
"TMF_Inshore": 0.8017,
"ILCWA": 842,
"TMF_Offshore": 0.798,
"OSN": 751.9,
"CDL": 7.056,
"TN_Offshore_Low": 0.7209,
"TN_Offshore_Medium": 0.9908,
"TN_Offshore_High": 1.1542,
"TN_Inshore_Low": 0.5529,
"TN_Inshore_Medium": 0.7913,
"TN_Inshore_High": 0.9571,
"TND_Offshore_Low": 936.3,
"TND_Offshore_Medium": 681.3,
"TND_Offshore_High": 584.8,
"TND_Inshore_Low": 1220.8,
"TND_Inshore_Medium": 853,
"TND_Inshore_High": 705.3,
"Double_Handed_TOD": null,
"Double_Handed_TOT": null,
"OSN_Jibs": 751.9,
"TMF_Jibs": 0.798,
"Dspl_Sailing": 7116,
"WSS": 27.39,
"Area_Main": 24.97,
"Area_Jib": 35.81,
"Area_Sym": 0,
"Area_ASym": 0,
"GPH_Jibs": 780.1,
"GPH_Jibs_TOT": 0.7691,
"GPH_DH": 0,
"GPH_DH_TOT": 0,
"Allowances": {
"Beat": [
1363.4,
1113.9,
981.7,
892.9,
841.8,
822.5,
818.8
],
"Run": [
1368.9,
1036.8,
849.8,
734.8,
653.5,
592.3,
531
],
"BeatAngle": [
45.9,
43.6,
42.7,
43.1,
43.2,
42.8,
43
],
"GybeAngle": [
167.1,
167.8,
168.9,
170,
170.1,
171.4,
175.4
],
"WL": [
1366.2,
1075.4,
915.7,
813.8,
747.7,
707.4,
674.9
],
"CR": [
1111.7,
882.2,
754.7,
678,
630.1,
599.8,
567.2
],
"OC": [
1366.4,
1009.6,
820.8,
702.4,
636.3,
593.1,
536.8
],
"NS": [
1111.7,
882.2,
754.7,
678,
630.1,
599.8,
567.2
],
"OL": [
1299.8,
1037,
892.2,
800.5,
743.6,
712.9,
687.5
],
"R52": [
866.3,
724.9,
642.2,
586.7,
561.7,
553.2,
549.9
],
"R60": [
806.5,
682.9,
605,
562.4,
542.9,
534.7,
530.6
],
"R75": [
763.8,
648.6,
578.1,
545.2,
527.6,
516,
505.8
],
"R90": [
770.4,
649.9,
575.9,
541.5,
522.6,
506.7,
486.6
],
"R110": [
841.2,
690.9,
599.3,
550.4,
525.7,
508.1,
476.4
],
"R120": [
899.7,
728.7,
628.2,
564.5,
533.7,
514.4,
481.6
],
"R135": [
1027.7,
808.1,
690.6,
609.2,
558.7,
532,
498
],
"R150": [
1205.8,
924.1,
770,
674,
602.7,
557.4,
514.8
]
}
}
]
}
@@ -0,0 +1,76 @@
package com.sap.sailing.server.gateway.deserialization.test.racelog;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.orc.ORCCertificate;
import com.sap.sailing.domain.orc.ORCCertificatesCollection;
import com.sap.sailing.domain.orc.ORCCertificatesImporter;
import com.sap.sailing.server.gateway.deserialization.racelog.impl.ORCCertificateJsonDeserializer;
import com.sap.sailing.server.gateway.serialization.racelog.impl.ORCCertificateJsonSerializer;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Speed;
import com.sap.sse.common.impl.DegreeBearingImpl;
public class ORCCertificateJsonDeserializerTest {
private ORCCertificateJsonSerializer serializer;
private ORCCertificateJsonDeserializer deserializer;
private static final String RESOURCES = "resources/rms/";
@Before
public void setup() {
deserializer = new ORCCertificateJsonDeserializer();
serializer = new ORCCertificateJsonSerializer();
}
@Test
public void testNewFormateOrcCertificateDeserializer() throws FileNotFoundException, IOException, ParseException {
File fileGER = new File(RESOURCES + "newFormatCertificate.json");
ORCCertificatesCollection importer = ORCCertificatesImporter.INSTANCE.read(new FileInputStream(fileGER));
Iterator<ORCCertificate> iterator = importer.getCertificates().iterator();
assertTrue(iterator.hasNext());
ORCCertificate certificate = iterator.next();
final Speed[] expectedTrueWindSpeeds = { new KnotSpeedImpl(6.1), new KnotSpeedImpl(8.2), new KnotSpeedImpl(10.6), new KnotSpeedImpl(12.1), new KnotSpeedImpl(14.9), new KnotSpeedImpl(16.7), new KnotSpeedImpl(20.1) };
assertTrue(Arrays.equals(expectedTrueWindSpeeds, certificate.getTrueWindSpeeds()));
final Bearing[] expectedTrueWindAngles = { new DegreeBearingImpl(52.7), new DegreeBearingImpl(64.4), new DegreeBearingImpl(75), new DegreeBearingImpl(90),
new DegreeBearingImpl(110), new DegreeBearingImpl(120), new DegreeBearingImpl(135), new DegreeBearingImpl(150) };
assertTrue(Arrays.equals(expectedTrueWindAngles, certificate.getTrueWindAngles()));
JSONObject serializedCertificate = serializer.serialize(certificate);
assertNotNull(serializedCertificate);
ORCCertificate certificateAfterDeserialize = deserializer.deserialize(serializedCertificate);
assertEquals(certificate.getId(), certificateAfterDeserialize.getId());
assertTrue(Arrays.equals(expectedTrueWindSpeeds, certificateAfterDeserialize.getTrueWindSpeeds()));
assertTrue(Arrays.equals(expectedTrueWindAngles, certificateAfterDeserialize.getTrueWindAngles()));
}
@Test
public void testOrcCertificateOldFormatDeserializer() throws IOException, ParseException {
File fileGER = new File(RESOURCES + "oldFormatCertificate.json");
ORCCertificatesCollection importer = ORCCertificatesImporter.INSTANCE.read(new FileInputStream(fileGER));
Iterator<ORCCertificate> iterator = importer.getCertificates().iterator();
assertTrue(iterator.hasNext());
ORCCertificate certificate = iterator.next();
assertTrue(Arrays.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS, certificate.getTrueWindSpeeds()));
assertTrue(Arrays.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES, certificate.getTrueWindAngles()));
JSONObject serializedCertificate = serializer.serialize(certificate);
assertNotNull(serializedCertificate);
ORCCertificate certificateAfterDeserialize = deserializer.deserialize(serializedCertificate);
assertEquals(certificate.getId(), certificateAfterDeserialize.getId());
assertTrue(Arrays.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS, certificateAfterDeserialize.getTrueWindSpeeds()));
assertTrue(Arrays.equals(ORCCertificate.ALLOWANCES_TRUE_WIND_ANGLES, certificateAfterDeserialize.getTrueWindAngles()));
}
}
@@ -185,6 +185,10 @@ public abstract class AbstractLeaderboardsResource extends AbstractSailingServer
? MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis())
: new MillisecondsTimePoint(resultTimePoint)));
trackedRaceInfo.put("delayToLiveInMillis", trackedRace.getDelayToLiveInMillis());
if (trackedRace.getStatus() != null) {
trackedRaceInfo.put("status", trackedRace.getStatus().getStatus().toString());
trackedRaceInfo.put("loadingProgress", trackedRace.getStatus().getLoadingProgress());
}
}
fleetJson.put("trackedRace", trackedRaceInfo);
}
@@ -491,7 +491,6 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
* If {@code raceColumnNames} is empty or {@code null}, return the names of all {@link raceColumnsOfLeaderboard};
* otherwise return those race column names from {@code raceColumnsOfLeaderboard} that are also in
* {@code raceColumnNames}.
* @param leaderboard TODO
* @param showOnlyActiveRacesForCompetitorIds
* {@code null}, or specifies zero or more competitor IDs, requesting that the result contained the race
* column holding that competitor's "active" race at the time requested; see also the