mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-08 13:20:57 +00:00
Merge branch 'master' into bug3526
Conflicts: java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_zh.properties java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties Change-Id: I7264656a9a8273aacedaee76d7dd1f9a442c873f
This commit is contained in:
commit
20fbc24bd8
271 files changed
+22489
-3361
No files matched your search
+4406
File diff suppressed because it is too large.
Load diff
+5868
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -6,7 +6,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.1.0-alpha1'
|
||||
classpath 'com.android.tools.build:gradle:2.1.0-alpha4'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -283,7 +283,6 @@
|
||||
7F44095B19F65B5B0021BF22 /* CoreData.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 7F44095919F65B5B0021BF22 /* CoreData.xcdatamodeld */; };
|
||||
7F4D2F541A1C8EE50038E7D1 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7F4D2F531A1C8EE50038E7D1 /* Images.xcassets */; };
|
||||
7F4EEDBA1A6D2BAC0026F711 /* CheckIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F4EEDB91A6D2BAC0026F711 /* CheckIn.swift */; };
|
||||
7F72C73A1A2DC51900F48EE8 /* SplashScreenDummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F72C7391A2DC51900F48EE8 /* SplashScreenDummy.swift */; };
|
||||
7F83F15619F154F000C53328 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F83F15519F154F000C53328 /* AppDelegate.swift */; };
|
||||
7F83F15819F154F000C53328 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F83F15719F154F000C53328 /* HomeViewController.swift */; };
|
||||
7F83F15B19F154F000C53328 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7F83F15919F154F000C53328 /* Main.storyboard */; };
|
||||
@@ -317,6 +316,7 @@
|
||||
7FED45CA1A25F5910022505F /* event1.json in Resources */ = {isa = PBXBuildFile; fileRef = 7FED45C41A25F5900022505F /* event1.json */; };
|
||||
7FED45CC1A25F5910022505F /* leaderboard1.json in Resources */ = {isa = PBXBuildFile; fileRef = 7FED45C61A25F5900022505F /* leaderboard1.json */; };
|
||||
9E95C3D61C723AB30016632B /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E95C3D51C723AB30016632B /* Constants.swift */; };
|
||||
9ECBDDAE1C9C4F0300F76AEB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9ECBDDAD1C9C4F0300F76AEB /* LaunchScreen.storyboard */; };
|
||||
C8603D4A1C528832002B2F28 /* Acknowledgements.markdown in Sources */ = {isa = PBXBuildFile; fileRef = C8603D491C528832002B2F28 /* Acknowledgements.markdown */; };
|
||||
C8603D511C52892A002B2F28 /* Acknowledgements.markdown in Resources */ = {isa = PBXBuildFile; fileRef = C8603D491C528832002B2F28 /* Acknowledgements.markdown */; };
|
||||
C8A4400B1C18153600DF70A0 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = C8A4400A1C18153600DF70A0 /* Settings.bundle */; };
|
||||
@@ -647,7 +647,6 @@
|
||||
7F44095A19F65B5B0021BF22 /* CoreData.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CoreData.xcdatamodel; sourceTree = "<group>"; };
|
||||
7F4D2F531A1C8EE50038E7D1 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
7F4EEDB91A6D2BAC0026F711 /* CheckIn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckIn.swift; sourceTree = "<group>"; };
|
||||
7F72C7391A2DC51900F48EE8 /* SplashScreenDummy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashScreenDummy.swift; sourceTree = "<group>"; };
|
||||
7F83F15019F154F000C53328 /* SAPTracker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SAPTracker.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7F83F15419F154F000C53328 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
7F83F15519F154F000C53328 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@@ -686,6 +685,7 @@
|
||||
7FED45C41A25F5900022505F /* event1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = event1.json; sourceTree = "<group>"; };
|
||||
7FED45C61A25F5900022505F /* leaderboard1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = leaderboard1.json; sourceTree = "<group>"; };
|
||||
9E95C3D51C723AB30016632B /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||
9ECBDDAD1C9C4F0300F76AEB /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
C8603D491C528832002B2F28 /* Acknowledgements.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Acknowledgements.markdown; sourceTree = "<group>"; };
|
||||
C8A4400A1C18153600DF70A0 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
C8B31F771B68DAD400C8A809 /* Pods.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Pods.xcodeproj; path = Pods/Pods.xcodeproj; sourceTree = "<group>"; };
|
||||
@@ -1059,6 +1059,7 @@
|
||||
7F72C73F1A2DEA2100F48EE8 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9ECBDDAD1C9C4F0300F76AEB /* LaunchScreen.storyboard */,
|
||||
7F83F15919F154F000C53328 /* Main.storyboard */,
|
||||
7F4D2F531A1C8EE50038E7D1 /* Images.xcassets */,
|
||||
7F87CA9D1A2CCE8B0076C617 /* OpenSans-Bold.ttf */,
|
||||
@@ -1077,7 +1078,6 @@
|
||||
7FD9D3021A1236080058FEAD /* Extensions.swift */,
|
||||
7F87CAA51A2CD6BA0076C617 /* Appearance.h */,
|
||||
7F87CAA61A2CD6BA0076C617 /* Appearance.m */,
|
||||
7F72C7391A2DC51900F48EE8 /* SplashScreenDummy.swift */,
|
||||
9E95C3D51C723AB30016632B /* Constants.swift */,
|
||||
);
|
||||
name = Helpers;
|
||||
@@ -1219,7 +1219,6 @@
|
||||
TargetAttributes = {
|
||||
7F83F14F19F154F000C53328 = {
|
||||
CreatedOnToolsVersion = 6.0.1;
|
||||
DevelopmentTeam = RRE8EFVCE3;
|
||||
};
|
||||
7F83F16419F154F000C53328 = {
|
||||
CreatedOnToolsVersion = 6.0.1;
|
||||
@@ -1517,6 +1516,7 @@
|
||||
7F14A1501A13B9A6001C1ECB /* CW.png in Resources */,
|
||||
7F14A1141A13B9A6001C1ECB /* _northern-cyprus.png in Resources */,
|
||||
7F14A13E1A13B9A6001C1ECB /* BW.png in Resources */,
|
||||
9ECBDDAE1C9C4F0300F76AEB /* LaunchScreen.storyboard in Resources */,
|
||||
7F14A1481A13B9A6001C1ECB /* CK.png in Resources */,
|
||||
7F14A11B1A13B9A6001C1ECB /* _unknown.png in Resources */,
|
||||
7F14A19B1A13B9A6001C1ECB /* LI.png in Resources */,
|
||||
@@ -1617,7 +1617,6 @@
|
||||
7F06590419F8094C0077FF95 /* DeviceUDIDManager.swift in Sources */,
|
||||
7F3AA7C81A1CD7D700F8861F /* SendGPSFixController.swift in Sources */,
|
||||
7FCDB6641A1636610075D7D9 /* LeaderBoard.swift in Sources */,
|
||||
7F72C73A1A2DC51900F48EE8 /* SplashScreenDummy.swift in Sources */,
|
||||
C8603D4A1C528832002B2F28 /* Acknowledgements.markdown in Sources */,
|
||||
7F8B8D411A373B0200C34B88 /* SpeedViewController.swift in Sources */,
|
||||
9E95C3D61C723AB30016632B /* Constants.swift in Sources */,
|
||||
@@ -1789,9 +1788,9 @@
|
||||
baseConfigurationReference = EA431711E588F5A2B31A62AC /* Pods.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
|
||||
INFOPLIST_FILE = SAPTracker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
@@ -1801,7 +1800,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = SAPTracker/bridge.h;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1810,9 +1809,9 @@
|
||||
baseConfigurationReference = CF98F25F363113C51C7CB1F3 /* Pods.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
|
||||
INFOPLIST_FILE = SAPTracker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
@@ -1822,7 +1821,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = SAPTracker/bridge.h;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ class AboutViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var versionDescriptionLabel: UILabel!
|
||||
@IBOutlet weak var versionLabel: UILabel!
|
||||
@IBOutlet weak var textView: UITextView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad();
|
||||
@@ -21,7 +22,16 @@ class AboutViewController: UIViewController {
|
||||
} else {
|
||||
versionLabel.text = "-"
|
||||
}
|
||||
|
||||
// add logo to top left
|
||||
let imageView = UIImageView(image: UIImage(named: "sap_logo"))
|
||||
let barButtonItem = UIBarButtonItem(customView: imageView)
|
||||
navigationItem.leftBarButtonItem = barButtonItem
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
textView.scrollRectToVisible(CGRect(x: 0, y: 0, width: 1, height: 1), animated: false)
|
||||
}
|
||||
|
||||
@IBAction func done(sender: AnyObject) {
|
||||
presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
|
||||
|
||||
@@ -44,6 +44,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
}
|
||||
|
||||
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
|
||||
|
||||
let rootViewController = self.window!.rootViewController as! UINavigationController
|
||||
rootViewController.popToRootViewControllerAnimated(false)
|
||||
rootViewController.dismissViewControllerAnimated(false, completion: nil)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="pqQ-jd-OwW">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="pqQ-jd-OwW">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
</dependencies>
|
||||
<customFonts key="customFonts">
|
||||
@@ -47,6 +48,7 @@
|
||||
<string>OpenSans</string>
|
||||
<string>OpenSans</string>
|
||||
<string>OpenSans</string>
|
||||
<string>OpenSans</string>
|
||||
</mutableArray>
|
||||
<mutableArray key="OpenSans-Semibold.ttf">
|
||||
<string>OpenSans-Semibold</string>
|
||||
@@ -208,6 +210,7 @@
|
||||
<outlet property="btnScan" destination="Ieu-ch-jeI" id="FWs-OF-b4i"/>
|
||||
<outlet property="tableView" destination="OXU-ft-TXZ" id="1BA-dy-00A"/>
|
||||
<outlet property="tableViewHeight" destination="yuo-wx-0A1" id="pFK-q1-1DI"/>
|
||||
<outlet property="vSplashScreen" destination="4Od-pk-xCd" id="3sj-Mx-34I"/>
|
||||
<segue destination="G3a-SV-Maf" kind="show" identifier="Regatta" id="FMW-37-go1"/>
|
||||
<segue destination="plm-Uw-c62" kind="show" identifier="Scan" id="U8l-If-CrF"/>
|
||||
<segue destination="4Fb-v3-mkB" kind="presentation" identifier="Settings" id="Xxn-jp-xEB"/>
|
||||
@@ -215,13 +218,33 @@
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="x5A-6p-PRh" sceneMemberID="firstResponder"/>
|
||||
<view contentMode="scaleToFill" id="4Od-pk-xCd">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="536"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="applogo" translatesAutoresizingMaskIntoConstraints="NO" id="K04-Lb-fSm">
|
||||
<rect key="frame" x="117.5" y="220" width="365" height="96"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="K04-Lb-fSm" secondAttribute="height" multiplier="365:96" id="iTN-7Q-YkX"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="K04-Lb-fSm" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="4Od-pk-xCd" secondAttribute="leading" constant="30" id="0ML-xS-Lk4"/>
|
||||
<constraint firstItem="K04-Lb-fSm" firstAttribute="width" secondItem="K04-Lb-fSm" secondAttribute="height" multiplier="365:96" id="YeC-DN-aQX"/>
|
||||
<constraint firstItem="K04-Lb-fSm" firstAttribute="centerY" secondItem="4Od-pk-xCd" secondAttribute="centerY" id="lJN-eB-asJ"/>
|
||||
<constraint firstItem="K04-Lb-fSm" firstAttribute="centerX" secondItem="4Od-pk-xCd" secondAttribute="centerX" id="lP3-oq-Tj2"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="K04-Lb-fSm" secondAttribute="trailing" constant="30" id="tNv-UR-6vj"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1222" y="-307"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="e3W-CJ-soQ">
|
||||
<objects>
|
||||
<navigationController id="pqQ-jd-OwW" sceneMemberID="viewController">
|
||||
<navigationController storyboardIdentifier="rootViewController" id="pqQ-jd-OwW" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="x3H-m5-yJB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -299,7 +322,7 @@
|
||||
</constraint>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CDA-ep-bjo">
|
||||
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CDA-ep-bjo">
|
||||
<rect key="frame" x="200" y="48" width="200" height="33"/>
|
||||
<color key="backgroundColor" white="1" alpha="0.5" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
@@ -345,13 +368,13 @@
|
||||
<rect key="frame" x="0.0" y="114" width="600" height="40"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="GER 17" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="iIf-9z-7fe">
|
||||
<rect key="frame" x="527.5" y="8" width="56.5" height="24"/>
|
||||
<rect key="frame" x="527" y="8" width="56.5" height="24"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="DE.png" translatesAutoresizingMaskIntoConstraints="NO" id="szG-Mg-huO">
|
||||
<rect key="frame" x="495.5" y="8" width="24" height="24"/>
|
||||
<rect key="frame" x="495" y="8" width="24" height="24"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="24" id="Ina-uK-atY"/>
|
||||
<constraint firstAttribute="height" constant="24" id="TVX-WF-dOi"/>
|
||||
@@ -434,12 +457,12 @@
|
||||
<rect key="frame" x="255" y="194" width="90" height="60"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="24" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gaG-Iv-Xyq">
|
||||
<rect key="frame" x="24.5" y="0.0" width="41.5" height="49.5"/>
|
||||
<rect key="frame" x="24" y="0.0" width="41.5" height="49.5"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans-Semibold" family="Open Sans" pointSize="36"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Hours" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c2J-Uj-KEg">
|
||||
<rect key="frame" x="31.5" y="46" width="28.5" height="14"/>
|
||||
<rect key="frame" x="31" y="46" width="28.5" height="14"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="10"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -471,7 +494,7 @@
|
||||
<rect key="frame" x="353" y="194" width="90" height="60"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="59" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Txm-uf-MP4">
|
||||
<rect key="frame" x="24.5" y="0.0" width="41.5" height="49.5"/>
|
||||
<rect key="frame" x="24" y="0.0" width="41.5" height="49.5"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans-Semibold" family="Open Sans" pointSize="36"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
@@ -513,11 +536,11 @@
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="P9e-ZM-HZP">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="P9e-ZM-HZP">
|
||||
<rect key="frame" x="303" y="372" width="277" height="44"/>
|
||||
<color key="backgroundColor" red="0.93725490199999995" green="0.67843137249999996" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" id="jqD-cx-gQM"/>
|
||||
<constraint firstAttribute="height" constant="44" id="jqD-cx-gQM"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
|
||||
<state key="normal" title="Leader Board">
|
||||
@@ -566,7 +589,7 @@
|
||||
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="13"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QHA-RX-lHP" userLabel="Spacer">
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QHA-RX-lHP" userLabel="Spacer">
|
||||
<rect key="frame" x="299" y="389" width="2" height="10"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
@@ -574,7 +597,7 @@
|
||||
<constraint firstAttribute="width" constant="2" id="mpd-5K-GxQ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Lzv-E6-gsr">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Lzv-E6-gsr">
|
||||
<rect key="frame" x="20" y="372" width="277" height="44"/>
|
||||
<color key="backgroundColor" red="0.10980392156862745" green="0.48627450980392156" blue="0.76078431372549016" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
|
||||
@@ -592,6 +615,35 @@
|
||||
<action selector="showEvent:" destination="G3a-SV-Maf" eventType="touchUpInside" id="aOq-oB-vIG"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Gjq-vj-gAR">
|
||||
<rect key="frame" x="170" y="48" width="260" height="33"/>
|
||||
<color key="backgroundColor" white="1" alpha="0.5" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="260" id="5jL-oY-VM2"/>
|
||||
<constraint firstAttribute="height" constant="33" id="ZOg-nP-2k3"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="13"/>
|
||||
<inset key="imageEdgeInsets" minX="5" minY="5" maxX="20" maxY="5"/>
|
||||
<state key="normal" title="Error during upload. Tap to retry." image="retry">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.borderWidth">
|
||||
<real key="value" value="0.5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="color" keyPath="layer.borderUIColor">
|
||||
<color key="value" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="showImageAlertView:" destination="G3a-SV-Maf" eventType="touchUpInside" id="GzU-Yx-U2c"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
@@ -599,6 +651,7 @@
|
||||
<constraint firstItem="CDA-ep-bjo" firstAttribute="top" secondItem="Ezh-Sq-hsb" secondAttribute="top" constant="48" id="3xV-Px-tvy"/>
|
||||
<constraint firstItem="gHJ-am-Ulb" firstAttribute="top" secondItem="5is-0I-Kv3" secondAttribute="bottom" constant="8" symbolic="YES" id="4Ai-mf-Ab3"/>
|
||||
<constraint firstItem="ft9-jD-C5m" firstAttribute="trailing" secondItem="gHJ-am-Ulb" secondAttribute="trailing" id="4rt-0W-Jpl"/>
|
||||
<constraint firstItem="Gjq-vj-gAR" firstAttribute="centerX" secondItem="Ezh-Sq-hsb" secondAttribute="centerX" id="6Zd-Lr-JJ2"/>
|
||||
<constraint firstItem="mj9-m1-nYY" firstAttribute="trailing" secondItem="gHJ-am-Ulb" secondAttribute="trailing" id="6bk-OB-IQS"/>
|
||||
<constraint firstItem="pjH-rV-MGI" firstAttribute="top" secondItem="ft9-jD-C5m" secondAttribute="bottom" constant="8" symbolic="YES" id="6va-QB-QGA"/>
|
||||
<constraint firstItem="8aO-2n-aG5" firstAttribute="bottom" secondItem="cNo-41-lUw" secondAttribute="top" id="716-9b-eFO"/>
|
||||
@@ -621,6 +674,7 @@
|
||||
<constraint firstItem="8aO-2n-aG5" firstAttribute="top" secondItem="mj9-m1-nYY" secondAttribute="bottom" constant="8" symbolic="YES" id="SZP-ow-Och"/>
|
||||
<constraint firstItem="P9e-ZM-HZP" firstAttribute="top" relation="greaterThanOrEqual" secondItem="gHJ-am-Ulb" secondAttribute="bottom" constant="8" symbolic="YES" id="VAj-aB-Ezg"/>
|
||||
<constraint firstItem="tvc-eS-Us6" firstAttribute="leading" secondItem="5is-0I-Kv3" secondAttribute="trailing" constant="8" id="WJd-lR-8UH"/>
|
||||
<constraint firstItem="Gjq-vj-gAR" firstAttribute="top" secondItem="wYi-P2-UTF" secondAttribute="bottom" constant="48" id="XQn-se-otI"/>
|
||||
<constraint firstItem="Lzv-E6-gsr" firstAttribute="leading" secondItem="mj9-m1-nYY" secondAttribute="leading" id="XXL-Ep-csv"/>
|
||||
<constraint firstItem="5is-0I-Kv3" firstAttribute="top" secondItem="ft9-jD-C5m" secondAttribute="bottom" constant="8" symbolic="YES" id="YvG-vU-Ja4"/>
|
||||
<constraint firstItem="Ik1-Z0-TGp" firstAttribute="leading" secondItem="Ezh-Sq-hsb" secondAttribute="leading" id="dUq-Dh-9sc"/>
|
||||
@@ -669,6 +723,7 @@
|
||||
<outlet property="nameLabel" destination="oJS-dM-2B6" id="fXn-FX-UbJ"/>
|
||||
<outlet property="openEventButton" destination="Lzv-E6-gsr" id="I7S-26-Qp6"/>
|
||||
<outlet property="regattaStartLabel" destination="ft9-jD-C5m" id="C70-iD-pB3"/>
|
||||
<outlet property="retryUploadTeamPhotoButton" destination="Gjq-vj-gAR" id="AJd-4h-9Cz"/>
|
||||
<outlet property="sailLabel" destination="iIf-9z-7fe" id="xPM-y9-Tjg"/>
|
||||
<outlet property="startTrackingButton" destination="mj9-m1-nYY" id="eUX-Yd-aix"/>
|
||||
<outlet property="yourTeamPhotoButton" destination="CDA-ep-bjo" id="15b-r4-Mve"/>
|
||||
@@ -728,7 +783,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Status" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="r6d-OU-cRI">
|
||||
<rect key="frame" x="20" y="39.5" width="49.5" height="23.5"/>
|
||||
<rect key="frame" x="20" y="39" width="49.5" height="23.5"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -740,7 +795,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Tracking Running" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="5Xu-6g-tqP">
|
||||
<rect key="frame" x="444" y="39.5" width="136" height="23.5"/>
|
||||
<rect key="frame" x="444" y="39" width="136" height="23.5"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
|
||||
<color key="textColor" red="0.25098039220000001" green="0.50196078430000002" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -767,13 +822,13 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="UcT-H2-Fxp">
|
||||
<rect key="frame" x="0.0" y="134.5" width="600" height="401.5"/>
|
||||
<rect key="frame" x="0.0" y="134" width="600" height="401.5"/>
|
||||
<connections>
|
||||
<segue destination="rjH-vb-OUF" kind="embed" id="ln5-SR-WkO"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="no GPS" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DiM-MR-SGV">
|
||||
<rect key="frame" x="506.5" y="8" width="57.5" height="23.5"/>
|
||||
<rect key="frame" x="506" y="8" width="57.5" height="23.5"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -787,11 +842,34 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Online" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="tJY-RR-vpe">
|
||||
<rect key="frame" x="527.5" y="103" width="52.5" height="23.5"/>
|
||||
<rect key="frame" x="527" y="103" width="52.5" height="23.5"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
|
||||
<color key="textColor" red="0.25098040700000002" green="0.50196081400000003" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mnl-aL-6R2">
|
||||
<rect key="frame" x="20" y="424" width="560" height="75"/>
|
||||
<color key="backgroundColor" red="0.87843137250000003" green="0.20000000000000001" blue="0.090196078430000007" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="75" id="NVa-wY-Jve">
|
||||
<variation key="heightClass=compact" constant="50"/>
|
||||
</constraint>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
|
||||
<state key="normal" title="Stop Tracking">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="stopTrackingButtonTapped:" destination="eHd-jN-WkS" eventType="touchUpInside" id="V6I-ta-wMI"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
@@ -800,6 +878,7 @@
|
||||
<constraint firstItem="Ujw-jg-ilY" firstAttribute="top" secondItem="r6d-OU-cRI" secondAttribute="bottom" constant="8" symbolic="YES" id="76Y-1b-oFJ">
|
||||
<variation key="heightClass=compact" constant="0.0"/>
|
||||
</constraint>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="mnl-aL-6R2" secondAttribute="trailing" id="8vV-66-vsk"/>
|
||||
<constraint firstItem="UcT-H2-Fxp" firstAttribute="top" secondItem="Xww-rc-pOA" secondAttribute="bottom" constant="8" symbolic="YES" id="9el-UI-bzr"/>
|
||||
<constraint firstItem="UcT-H2-Fxp" firstAttribute="bottom" secondItem="nE0-bK-d8x" secondAttribute="top" id="C25-aA-8XZ"/>
|
||||
<constraint firstItem="r6d-OU-cRI" firstAttribute="top" secondItem="Z1d-5I-pbj" secondAttribute="bottom" constant="8" symbolic="YES" id="C3i-US-IRf"/>
|
||||
@@ -810,6 +889,7 @@
|
||||
<constraint firstItem="Xww-rc-pOA" firstAttribute="leading" secondItem="6kF-Wb-Hv2" secondAttribute="leadingMargin" id="JQs-RK-ocI"/>
|
||||
<constraint firstItem="r6d-OU-cRI" firstAttribute="leading" secondItem="Z1d-5I-pbj" secondAttribute="leading" id="L9y-4I-Xx1"/>
|
||||
<constraint firstItem="Ujw-jg-ilY" firstAttribute="leading" secondItem="6kF-Wb-Hv2" secondAttribute="leadingMargin" id="MH7-cy-19A"/>
|
||||
<constraint firstItem="nE0-bK-d8x" firstAttribute="top" secondItem="mnl-aL-6R2" secondAttribute="bottom" constant="37" id="N0D-rv-8NW"/>
|
||||
<constraint firstItem="dz3-w7-CCh" firstAttribute="centerY" secondItem="Ujw-jg-ilY" secondAttribute="centerY" id="NPU-zR-WBN"/>
|
||||
<constraint firstItem="Xww-rc-pOA" firstAttribute="centerY" secondItem="tJY-RR-vpe" secondAttribute="centerY" id="NVc-vI-Vxb"/>
|
||||
<constraint firstItem="Xww-rc-pOA" firstAttribute="top" secondItem="Ujw-jg-ilY" secondAttribute="bottom" constant="8" symbolic="YES" id="Owh-AZ-oxm"/>
|
||||
@@ -819,6 +899,7 @@
|
||||
<constraint firstAttribute="trailingMargin" secondItem="5Xu-6g-tqP" secondAttribute="trailing" id="j12-j8-u1B"/>
|
||||
<constraint firstItem="Z1d-5I-pbj" firstAttribute="top" secondItem="qL4-PH-8ve" secondAttribute="bottom" constant="8" symbolic="YES" id="pnF-1D-Rxo"/>
|
||||
<constraint firstAttribute="trailing" secondItem="BFc-m8-XKV" secondAttribute="trailing" constant="16" id="r8t-UX-UKm"/>
|
||||
<constraint firstItem="mnl-aL-6R2" firstAttribute="leading" secondItem="6kF-Wb-Hv2" secondAttribute="leadingMargin" id="zei-4r-UQH"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Tracking" id="qfB-FT-Ysz">
|
||||
@@ -868,41 +949,14 @@
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ULl-vy-5gb">
|
||||
<rect key="frame" x="20" y="525" width="560" height="75"/>
|
||||
<color key="backgroundColor" red="0.87843137250000003" green="0.20000000000000001" blue="0.090196078430000007" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="75" id="WnL-zt-cEe">
|
||||
<variation key="heightClass=compact" constant="50"/>
|
||||
</constraint>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
|
||||
<state key="normal" title="Stop Tracking">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="stopTrackingButtonTapped:" destination="QJL-mi-hJE" eventType="touchUpInside" id="5fq-CB-McA"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="centerY" secondItem="mUm-EE-6Is" secondAttribute="centerY" priority="750" id="8N8-eA-npB"/>
|
||||
<constraint firstAttribute="centerX" secondItem="mUm-EE-6Is" secondAttribute="centerX" id="8mv-fJ-KnI"/>
|
||||
<constraint firstAttribute="centerX" secondItem="ULl-vy-5gb" secondAttribute="centerX" id="Fzl-H6-UnL"/>
|
||||
<constraint firstItem="IeZ-JM-OWw" firstAttribute="top" secondItem="mP6-V4-QPK" secondAttribute="top" id="IZk-R3-x2F"/>
|
||||
<constraint firstItem="ULl-vy-5gb" firstAttribute="bottom" secondItem="gKu-YN-ryh" secondAttribute="top" id="UN0-98-lxt"/>
|
||||
<constraint firstAttribute="trailing" secondItem="mUm-EE-6Is" secondAttribute="trailing" constant="20" symbolic="YES" id="ZKy-Zc-RiE"/>
|
||||
<constraint firstItem="IeZ-JM-OWw" firstAttribute="leading" secondItem="ULl-vy-5gb" secondAttribute="leading" id="eGE-uB-OHk"/>
|
||||
<constraint firstItem="IeZ-JM-OWw" firstAttribute="leading" secondItem="mP6-V4-QPK" secondAttribute="leadingMargin" id="eYA-zH-AXy"/>
|
||||
<constraint firstItem="mUm-EE-6Is" firstAttribute="leading" secondItem="mP6-V4-QPK" secondAttribute="leading" constant="20" symbolic="YES" id="lbB-rJ-KNj"/>
|
||||
<constraint firstItem="ULl-vy-5gb" firstAttribute="top" relation="greaterThanOrEqual" secondItem="mUm-EE-6Is" secondAttribute="bottom" constant="8" symbolic="YES" id="xXT-VC-dgn"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Tracking" id="fyS-rY-bYp"/>
|
||||
@@ -1008,85 +1062,219 @@
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="536"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="24z-mh-txV">
|
||||
<rect key="frame" x="20" y="198" width="560" height="140"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8Vc-YY-1rh">
|
||||
<rect key="frame" x="0.0" y="80" width="560" height="60"/>
|
||||
<color key="backgroundColor" red="0.93725490196078431" green="0.67843137254901964" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="60" id="lFu-3n-8uz"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
|
||||
<state key="normal" title="EULA">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="titleShadowColor" red="0.5725490196078431" green="0.5725490196078431" blue="0.5725490196078431" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="openEULA:" destination="WMr-pC-C12" eventType="touchUpInside" id="PhA-bf-yMV"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MpZ-GD-6aE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="560" height="60"/>
|
||||
<color key="backgroundColor" red="0.93725490196078431" green="0.67843137254901964" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="60" id="xSB-Dr-qVN"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
|
||||
<state key="normal" title="License Information">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="titleShadowColor" red="0.5725490196078431" green="0.5725490196078431" blue="0.5725490196078431" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<segue destination="awJ-L8-hjX" kind="show" id="0cD-Vt-sEk"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="8Vc-YY-1rh" secondAttribute="trailing" id="0ns-WL-guw"/>
|
||||
<constraint firstItem="MpZ-GD-6aE" firstAttribute="top" secondItem="24z-mh-txV" secondAttribute="top" id="3z0-uD-NWx"/>
|
||||
<constraint firstItem="MpZ-GD-6aE" firstAttribute="leading" secondItem="24z-mh-txV" secondAttribute="leading" id="CgF-IV-BDx"/>
|
||||
<constraint firstItem="8Vc-YY-1rh" firstAttribute="top" secondItem="MpZ-GD-6aE" secondAttribute="bottom" constant="20" id="KgN-wG-9Pf"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8Vc-YY-1rh" secondAttribute="bottom" id="XB1-I9-hVG"/>
|
||||
<constraint firstAttribute="trailing" secondItem="MpZ-GD-6aE" secondAttribute="trailing" id="rvL-Ze-CNy"/>
|
||||
<constraint firstItem="8Vc-YY-1rh" firstAttribute="leading" secondItem="24z-mh-txV" secondAttribute="leading" id="sv6-8L-lF0"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YpQ-wa-mW3">
|
||||
<rect key="frame" x="20" y="346" width="45.5" height="18"/>
|
||||
<rect key="frame" x="20" y="484" width="45.5" height="18"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="13"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1.0.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jsi-LA-7jU">
|
||||
<rect key="frame" x="20" y="372" width="29.5" height="18"/>
|
||||
<rect key="frame" x="20" y="510" width="29.5" height="18"/>
|
||||
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="13"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8Vc-YY-1rh">
|
||||
<rect key="frame" x="20" y="416" width="560" height="60"/>
|
||||
<color key="backgroundColor" red="0.93725490196078431" green="0.67843137254901964" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="60" id="lFu-3n-8uz"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
|
||||
<state key="normal" title="EULA">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="titleShadowColor" red="0.5725490196078431" green="0.5725490196078431" blue="0.5725490196078431" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="openEULA:" destination="WMr-pC-C12" eventType="touchUpInside" id="PhA-bf-yMV"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MpZ-GD-6aE">
|
||||
<rect key="frame" x="20" y="340" width="560" height="60"/>
|
||||
<color key="backgroundColor" red="0.93725490196078431" green="0.67843137254901964" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="60" id="xSB-Dr-qVN"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
|
||||
<state key="normal" title="License Information">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="titleShadowColor" red="0.5725490196078431" green="0.5725490196078431" blue="0.5725490196078431" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<segue destination="awJ-L8-hjX" kind="show" id="0cD-Vt-sEk"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" editable="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="yEk-7o-5Ua">
|
||||
<rect key="frame" x="20" y="20" width="560" height="300"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<attributedString key="attributedText">
|
||||
<fragment>
|
||||
<string key="content">The SAP Sail InSight app supports live tracking of race boats by leveraging mobile phone technology, allowing the ability for friends and fans to follow racing whether onsite or online through live leader boards and 2D visualization. Following racing sailors can replay their races allowing the opportunity to review performance and optimize strategies all available at: </string>
|
||||
<attributes>
|
||||
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<font key="NSFont" size="16" name="Calibri"/>
|
||||
<real key="NSKern" value="0.0"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
|
||||
<tabStops/>
|
||||
</paragraphStyle>
|
||||
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment content="sapsailing.com">
|
||||
<attributes>
|
||||
<color key="NSColor" red="0.2196078431372549" green="0.43137254901960786" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<font key="NSFont" size="16" name="Calibri"/>
|
||||
<real key="NSKern" value="0.0"/>
|
||||
<url key="NSLink" string="http://sapsailing.com/"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
|
||||
<tabStops/>
|
||||
</paragraphStyle>
|
||||
<color key="NSStrokeColor" red="0.2196078431372549" green="0.43137254901960786" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<integer key="NSUnderline" value="1"/>
|
||||
<color key="NSUnderlineColor" red="0.2196078431372549" green="0.43137254901960786" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment content=" ">
|
||||
<attributes>
|
||||
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<font key="NSFont" size="16" name="Calibri"/>
|
||||
<real key="NSKern" value="0.0"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
|
||||
<tabStops/>
|
||||
</paragraphStyle>
|
||||
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment>
|
||||
<string key="content" base64-UTF8="YES">
|
||||
Cg
|
||||
</string>
|
||||
<attributes>
|
||||
<font key="NSFont" size="16" name="TimesNewRomanPSMT"/>
|
||||
<real key="NSKern" value="0.0"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
|
||||
<tabStops/>
|
||||
</paragraphStyle>
|
||||
<color key="NSStrokeColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment content=" ">
|
||||
<attributes>
|
||||
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<font key="NSFont" size="16" name="Calibri"/>
|
||||
<real key="NSKern" value="0.0"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
|
||||
<tabStops/>
|
||||
</paragraphStyle>
|
||||
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment>
|
||||
<string key="content" base64-UTF8="YES">
|
||||
Cg
|
||||
</string>
|
||||
<attributes>
|
||||
<font key="NSFont" size="16" name="TimesNewRomanPSMT"/>
|
||||
<real key="NSKern" value="0.0"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
|
||||
<tabStops/>
|
||||
</paragraphStyle>
|
||||
<color key="NSStrokeColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment>
|
||||
<string key="content">SAP supports US Sailing through the implementation of a range of innovative technology solutions, delivering analytics to help teams and sailors improve performance whilst also providing applications to help sailing operations run more efficiently.</string>
|
||||
<attributes>
|
||||
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<font key="NSFont" size="16" name="Calibri"/>
|
||||
<real key="NSKern" value="0.0"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
|
||||
<tabStops/>
|
||||
</paragraphStyle>
|
||||
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment>
|
||||
<string key="content" base64-UTF8="YES">
|
||||
Cg
|
||||
</string>
|
||||
<attributes>
|
||||
<font key="NSFont" size="16" name="TimesNewRomanPSMT"/>
|
||||
<real key="NSKern" value="0.0"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
|
||||
<tabStops/>
|
||||
</paragraphStyle>
|
||||
<color key="NSStrokeColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment content=" ">
|
||||
<attributes>
|
||||
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<font key="NSFont" size="16" name="Calibri"/>
|
||||
<real key="NSKern" value="0.0"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
|
||||
<tabStops/>
|
||||
</paragraphStyle>
|
||||
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment>
|
||||
<string key="content" base64-UTF8="YES">
|
||||
Cg
|
||||
</string>
|
||||
<attributes>
|
||||
<font key="NSFont" size="16" name="TimesNewRomanPSMT"/>
|
||||
<real key="NSKern" value="0.0"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
|
||||
<tabStops/>
|
||||
</paragraphStyle>
|
||||
<color key="NSStrokeColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
<fragment>
|
||||
<string key="content">As the national governing body for sailing, US Sailing provides leadership, integrity and growth for the sport including training and education programs for instructors and race officials alongside administration and oversight of competitive sailing across the country.</string>
|
||||
<attributes>
|
||||
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<font key="NSFont" size="16" name="Calibri"/>
|
||||
<real key="NSKern" value="0.0"/>
|
||||
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
|
||||
<tabStops/>
|
||||
</paragraphStyle>
|
||||
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</attributes>
|
||||
</fragment>
|
||||
</attributedString>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
<dataDetectorType key="dataDetectorTypes" link="YES"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="yEk-7o-5Ua" firstAttribute="trailing" secondItem="wZ4-L7-UtM" secondAttribute="trailingMargin" id="6Pr-e7-RK2"/>
|
||||
<constraint firstItem="8Vc-YY-1rh" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="Ccw-Sx-67a"/>
|
||||
<constraint firstItem="Jsi-LA-7jU" firstAttribute="top" secondItem="YpQ-wa-mW3" secondAttribute="bottom" constant="8" id="Cj5-RI-puj"/>
|
||||
<constraint firstItem="24z-mh-txV" firstAttribute="centerY" secondItem="wZ4-L7-UtM" secondAttribute="centerY" id="ETl-XU-Jke"/>
|
||||
<constraint firstItem="24z-mh-txV" firstAttribute="centerX" secondItem="wZ4-L7-UtM" secondAttribute="centerX" id="Qeq-G6-dez"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="24z-mh-txV" secondAttribute="trailing" id="Rl3-ut-OsJ"/>
|
||||
<constraint firstItem="UkO-Wl-zJn" firstAttribute="top" secondItem="Jsi-LA-7jU" secondAttribute="bottom" constant="8" id="CyR-gx-2J2"/>
|
||||
<constraint firstItem="MpZ-GD-6aE" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="EG0-ma-ckk"/>
|
||||
<constraint firstItem="yEk-7o-5Ua" firstAttribute="top" secondItem="TdH-NN-ozb" secondAttribute="bottom" constant="20" id="HGZ-ua-ZM2"/>
|
||||
<constraint firstItem="yEk-7o-5Ua" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="JKk-Ew-9V1"/>
|
||||
<constraint firstItem="8Vc-YY-1rh" firstAttribute="top" secondItem="MpZ-GD-6aE" secondAttribute="bottom" constant="16" id="Jy6-Nc-7Jc"/>
|
||||
<constraint firstItem="Jsi-LA-7jU" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="SgV-Fo-88q"/>
|
||||
<constraint firstItem="YpQ-wa-mW3" firstAttribute="top" secondItem="24z-mh-txV" secondAttribute="bottom" constant="8" id="igG-9X-MdL"/>
|
||||
<constraint firstItem="24z-mh-txV" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="tz7-OQ-537"/>
|
||||
<constraint firstItem="MpZ-GD-6aE" firstAttribute="top" secondItem="yEk-7o-5Ua" secondAttribute="bottom" constant="20" id="bfl-MB-ii4"/>
|
||||
<constraint firstItem="YpQ-wa-mW3" firstAttribute="top" secondItem="8Vc-YY-1rh" secondAttribute="bottom" constant="8" id="fR3-Gc-K7V"/>
|
||||
<constraint firstItem="MpZ-GD-6aE" firstAttribute="trailing" secondItem="wZ4-L7-UtM" secondAttribute="trailingMargin" id="fRY-RC-lgO"/>
|
||||
<constraint firstItem="8Vc-YY-1rh" firstAttribute="trailing" secondItem="wZ4-L7-UtM" secondAttribute="trailingMargin" id="gxG-bx-dTv"/>
|
||||
<constraint firstItem="YpQ-wa-mW3" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="wev-ws-P1D"/>
|
||||
</constraints>
|
||||
</view>
|
||||
@@ -1098,6 +1286,7 @@
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="textView" destination="yEk-7o-5Ua" id="a7p-wJ-DDg"/>
|
||||
<outlet property="versionDescriptionLabel" destination="YpQ-wa-mW3" id="ndm-Jp-4j0"/>
|
||||
<outlet property="versionLabel" destination="Jsi-LA-7jU" id="b1V-A3-5jG"/>
|
||||
</connections>
|
||||
@@ -1360,15 +1549,17 @@
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="DE.png" width="64" height="64"/>
|
||||
<image name="applogo" width="365" height="96"/>
|
||||
<image name="camera" width="24" height="24"/>
|
||||
<image name="edit" width="33" height="33"/>
|
||||
<image name="home_bg" width="980" height="653"/>
|
||||
<image name="profile_placeholder" width="360" height="256"/>
|
||||
<image name="retry" width="24" height="24"/>
|
||||
<image name="scan_white" width="214" height="214"/>
|
||||
<image name="settings" width="22" height="22"/>
|
||||
</resources>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="QMa-LQ-D8c"/>
|
||||
<segue reference="Xxn-jp-xEB"/>
|
||||
<segue reference="QMa-LQ-D8c"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
Regular → Executable
+57
@@ -25,12 +25,15 @@ class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDe
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet weak var tableViewHeight: NSLayoutConstraint!
|
||||
@IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
|
||||
@IBOutlet var vSplashScreen: UIView!
|
||||
|
||||
var fetchedResultsController: NSFetchedResultsController?
|
||||
private var qrCodeManager: QRCodeManager?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
loadSplashScreen()
|
||||
|
||||
// set QR manager, needed in case app is being open by custom URL
|
||||
qrCodeManager = QRCodeManager(delegate: self)
|
||||
@@ -61,6 +64,50 @@ class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDe
|
||||
let barButtonItem = UIBarButtonItem(customView: imageView)
|
||||
navigationItem.leftBarButtonItem = barButtonItem
|
||||
}
|
||||
|
||||
func loadSplashScreen() {
|
||||
|
||||
if let keyWindow = UIApplication.sharedApplication().keyWindow {
|
||||
|
||||
vSplashScreen.translatesAutoresizingMaskIntoConstraints = false
|
||||
keyWindow.addSubview(vSplashScreen)
|
||||
|
||||
let lcTop = NSLayoutConstraint(item: vSplashScreen,
|
||||
attribute: NSLayoutAttribute.Top,
|
||||
relatedBy: NSLayoutRelation.Equal,
|
||||
toItem: keyWindow,
|
||||
attribute: NSLayoutAttribute.Top,
|
||||
multiplier: 1.0,
|
||||
constant: 0)
|
||||
let lcBottom = NSLayoutConstraint(item: vSplashScreen,
|
||||
attribute: NSLayoutAttribute.Bottom,
|
||||
relatedBy: NSLayoutRelation.Equal,
|
||||
toItem: keyWindow,
|
||||
attribute: NSLayoutAttribute.Bottom,
|
||||
multiplier: 1.0,
|
||||
constant: 0)
|
||||
let lcLeft = NSLayoutConstraint(item: vSplashScreen,
|
||||
attribute: NSLayoutAttribute.Left,
|
||||
relatedBy: NSLayoutRelation.Equal,
|
||||
toItem: keyWindow,
|
||||
attribute: NSLayoutAttribute.Left,
|
||||
multiplier: 1.0,
|
||||
constant: 0)
|
||||
let lcRight = NSLayoutConstraint(item: vSplashScreen,
|
||||
attribute: NSLayoutAttribute.Right,
|
||||
relatedBy: NSLayoutRelation.Equal,
|
||||
toItem: keyWindow,
|
||||
attribute: NSLayoutAttribute.Right,
|
||||
multiplier: 1.0,
|
||||
constant: 0)
|
||||
keyWindow.addConstraints([lcTop, lcBottom, lcLeft, lcRight])
|
||||
|
||||
weak var weakself = self
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
|
||||
weakself?.vSplashScreen.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
NSNotificationCenter.defaultCenter().removeObserver(self)
|
||||
@@ -103,6 +150,12 @@ class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDe
|
||||
let url = notification.userInfo!["url"] as! String
|
||||
qrCodeManager!.parseUrl(url)
|
||||
}
|
||||
|
||||
func _DEBUG_OPEN_URL() {
|
||||
let url = "http://ec2-54-171-89-140.eu-west-1.compute.amazonaws.com:8888/tracking/checkin?event_id=71c7b531-fb1b-441c-b2fa-f4e9ff672d60&leaderboard_name=Ubigatta&competitor_id=9df7b4f6-611b-4be0-b028-c7b1bdd434c2"
|
||||
let url2 = "http://ec2-54-171-89-140.eu-west-1.compute.amazonaws.com:8888/tracking/checkin?event_id=71c7b531-fb1b-441c-b2fa-f4e9ff672d60&leaderboard_name=Ubigatta&competitor_id=a5a00800-daf8-0131-89e6-60a44ce903c3"
|
||||
qrCodeManager!.parseUrl(url2)
|
||||
}
|
||||
|
||||
// MARK: - UIActionSheetDelegate
|
||||
|
||||
@@ -208,6 +261,10 @@ class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDe
|
||||
// MARK: - Button actions
|
||||
|
||||
@IBAction func scanButtonTap(sender: AnyObject) {
|
||||
|
||||
// _DEBUG_OPEN_URL()
|
||||
// return;
|
||||
|
||||
if !UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) {
|
||||
let alertView = UIAlertView(title: NSLocalizedString("No camera available.", comment: ""), message: nil, delegate: nil, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""))
|
||||
alertView.tag = AlertView.NoCameraAvailable.rawValue;
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
+2
-1
@@ -73,8 +73,9 @@
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "167_iPhone_6Plus_App_Icon@2x.png",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "applogo@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "applogo@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "retry@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 870 B |
+2
-2
@@ -6,8 +6,8 @@
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x",
|
||||
"filename" : "sap_logo.png"
|
||||
"filename" : "sap_logo@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>SAP Sail inSight</string>
|
||||
<string>SAP Sail InSight</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -30,7 +30,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>27</string>
|
||||
<string>30</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
@@ -51,6 +51,8 @@
|
||||
<array>
|
||||
<string>location</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="applogo" translatesAutoresizingMaskIntoConstraints="NO" id="XA8-DM-z3j">
|
||||
<rect key="frame" x="117.5" y="252" width="365" height="96"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="XA8-DM-z3j" secondAttribute="height" multiplier="365:96" id="nLx-D5-TPO"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="XA8-DM-z3j" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4Bv-Zq-ZzP"/>
|
||||
<constraint firstItem="XA8-DM-z3j" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Ze5-6b-2t3" secondAttribute="leading" constant="30" id="EFm-df-I71"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="XA8-DM-z3j" secondAttribute="trailing" constant="30" id="KiK-A3-oaY"/>
|
||||
<constraint firstItem="XA8-DM-z3j" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="TsS-GE-Hlz"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="applogo" width="365" height="96"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -18,13 +18,18 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
|
||||
enum AlertView: Int {
|
||||
case CheckOut, Image, UploadFailed
|
||||
}
|
||||
|
||||
|
||||
struct RegattaViewUserDefaults {
|
||||
static let UploadDidFail = "UploadDidFail"
|
||||
}
|
||||
|
||||
var sourceTypes = [UIImagePickerControllerSourceType]()
|
||||
var sourceTypeNames = [String]()
|
||||
|
||||
@IBOutlet weak var imageView: UIImageView!
|
||||
@IBOutlet weak var yourTeamPhotoButton: UIButton!
|
||||
@IBOutlet weak var editTeamPhotoButton: UIButton!
|
||||
@IBOutlet weak var retryUploadTeamPhotoButton: UIButton!
|
||||
@IBOutlet weak var nameLabel: UILabel!
|
||||
@IBOutlet weak var flagImageView: UIImageView!
|
||||
@IBOutlet weak var sailLabel: UILabel!
|
||||
@@ -55,6 +60,10 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
|
||||
dateFormatter.dateFormat = "MMM d, yyyy, h:mm a" // As per request - see mail 19.02.2016
|
||||
super.init(coder: aDecoder)!
|
||||
}
|
||||
|
||||
var uploadKey: String {
|
||||
return RegattaViewUserDefaults.UploadDidFail + "_" + DataManager.sharedManager.selectedCheckIn!.competitorId
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
@@ -62,13 +71,15 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
|
||||
navigationItem.title = DataManager.sharedManager.selectedCheckIn!.leaderBoardName
|
||||
|
||||
openEventButton.setTitle(NSLocalizedString("Show Event", comment: ""), forState: UIControlState.Normal)
|
||||
|
||||
retryUploadTeamPhotoButton.hidden = !NSUserDefaults.standardUserDefaults().boolForKey(uploadKey)
|
||||
|
||||
// set regatta image, either load it from server or load from core data
|
||||
if DataManager.sharedManager.selectedCheckIn?.userImage != nil {
|
||||
imageView.image = UIImage(data: DataManager.sharedManager.selectedCheckIn!.userImage!)
|
||||
self.yourTeamPhotoButton.hidden = true
|
||||
} else if DataManager.sharedManager.selectedCheckIn?.imageUrl != nil {
|
||||
let imageUrl = NSURL(string: DataManager.sharedManager.selectedCheckIn!.imageUrl!)
|
||||
let imageUrl = NSURL(string: DataManager.sharedManager.selectedCheckIn!.imageUrl! + "sa")
|
||||
let urlRequest = NSURLRequest(URL: imageUrl!)
|
||||
imageView.setImageWithURLRequest(urlRequest,
|
||||
placeholderImage: nil,
|
||||
@@ -78,10 +89,12 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
|
||||
},
|
||||
failure: { (request:NSURLRequest!,response:NSHTTPURLResponse!, error:NSError!) -> Void in
|
||||
self.editTeamPhotoButton.hidden = true
|
||||
self.yourTeamPhotoButton.hidden = !self.retryUploadTeamPhotoButton.hidden
|
||||
}
|
||||
)
|
||||
} else {
|
||||
self.editTeamPhotoButton.hidden = true
|
||||
self.yourTeamPhotoButton.hidden = !self.retryUploadTeamPhotoButton.hidden
|
||||
}
|
||||
if (DataManager.sharedManager.selectedCheckIn?.competitor != nil) {
|
||||
let competitor = DataManager.sharedManager.selectedCheckIn!.competitor!
|
||||
@@ -261,6 +274,7 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
|
||||
imageView.image = image
|
||||
yourTeamPhotoButton.hidden = true
|
||||
editTeamPhotoButton.hidden = false
|
||||
retryUploadTeamPhotoButton.hidden = true
|
||||
let jpegData = UIImageJPEGRepresentation(image, 0.8)
|
||||
DataManager.sharedManager.selectedCheckIn!.userImage = jpegData
|
||||
APIManager.sharedManager.postTeamImage(DataManager.sharedManager.selectedCheckIn!.competitorId,
|
||||
@@ -271,14 +285,21 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
|
||||
let responseDictionary = responseObject as![String: AnyObject]
|
||||
let imageUrl = (responseDictionary["teamImageUri"]) as! String;
|
||||
DataManager.sharedManager.selectedCheckIn!.imageUrl = imageUrl;
|
||||
|
||||
NSUserDefaults.standardUserDefaults().setBool(false, forKey: self.uploadKey)
|
||||
NSUserDefaults.standardUserDefaults().synchronize()
|
||||
},
|
||||
failure: { (NSError error) -> Void in
|
||||
let alertView = UIAlertView(title: NSLocalizedString("Failed to upload image", comment: ""), message: error.localizedDescription, delegate: self, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""))
|
||||
alertView.tag = AlertView.UploadFailed.rawValue;
|
||||
alertView.show()
|
||||
alertView.show()
|
||||
|
||||
NSUserDefaults.standardUserDefaults().setBool(true, forKey: self.uploadKey)
|
||||
NSUserDefaults.standardUserDefaults().synchronize()
|
||||
self.retryUploadTeamPhotoButton.hidden = false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Check-out
|
||||
func showCheckOutAlertView() {
|
||||
let alertView = UIAlertView(title: NSLocalizedString("Check-out of Regatta?", comment: ""), message: "", delegate: self, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""), otherButtonTitles: NSLocalizedString("OK", comment: ""))
|
||||
|
||||
@@ -15,6 +15,11 @@ class SettingsViewController: UITableViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
batterySavingSwitch.setOn(BatteryManager.sharedManager.batterySavingPreference, animated: true)
|
||||
|
||||
// add logo to top left
|
||||
let imageView = UIImageView(image: UIImage(named: "sap_logo"))
|
||||
let barButtonItem = UIBarButtonItem(customView: imageView)
|
||||
navigationItem.leftBarButtonItem = barButtonItem
|
||||
}
|
||||
|
||||
@IBAction func batterySavingChanged(sender: UISwitch) {
|
||||
|
||||
@@ -25,8 +25,12 @@ class SpeedViewController: UIViewController {
|
||||
|
||||
func newLocation(notification: NSNotification) {
|
||||
let ms = notification.userInfo!["speed"] as! Double
|
||||
let kn = ms * knPerMs
|
||||
speedLabel.text = String(format: "%0.1f kn", kn)
|
||||
if(ms >= 0) {
|
||||
let kn = ms * knPerMs
|
||||
speedLabel.text = String(format: "%0.1f kn", kn)
|
||||
} else {
|
||||
speedLabel.text = "– kn"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
//
|
||||
// SplashScreenDummy.swift
|
||||
// SAPTracker
|
||||
//
|
||||
// Created by computing on 02/12/14.
|
||||
// Copyright (c) 2014 com.sap.sailing. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/* Helper class used for created launch screens. */
|
||||
class SplashScreenDummy: UIViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// add logo to top left
|
||||
let imageView = UIImageView(image: UIImage(named: "sap_logo"))
|
||||
let barButtonItem = UIBarButtonItem(customView: imageView)
|
||||
navigationItem.leftBarButtonItem = barButtonItem
|
||||
|
||||
//UIApplication.sharedApplication().statusBarHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
/* Uncomment this when creating launch screens. */
|
||||
/*
|
||||
extension UINavigationBar {
|
||||
public override func sizeThatFits(size: CGSize) -> CGSize {
|
||||
let newSize = CGSizeMake(UIScreen.mainScreen().bounds.width, 64)
|
||||
return newSize
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -8,11 +8,8 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class TimerViewController: UIViewController, UIAlertViewDelegate {
|
||||
enum AlertView: Int {
|
||||
case StopTracking
|
||||
}
|
||||
|
||||
class TimerViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var trackingTimeLabel: UILabel!
|
||||
|
||||
let startDate = NSDate()
|
||||
@@ -37,33 +34,4 @@ class TimerViewController: UIViewController, UIAlertViewDelegate {
|
||||
let timerDate = NSDate(timeIntervalSince1970: timeInterval)
|
||||
trackingTimeLabel.text = dateFormatter.stringFromDate(timerDate)
|
||||
}
|
||||
|
||||
// MARK:- Buttons
|
||||
|
||||
/* Stop tracking, go back to regattas view */
|
||||
@IBAction func stopTrackingButtonTapped(sender: AnyObject) {
|
||||
let alertView = UIAlertView(title: NSLocalizedString("Stop tracking?", comment: ""), message: "", delegate: self, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""), otherButtonTitles: NSLocalizedString("Stop", comment: ""))
|
||||
alertView.tag = AlertView.StopTracking.rawValue;
|
||||
alertView.show()
|
||||
}
|
||||
|
||||
/* Alert view delegate */
|
||||
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
|
||||
switch alertView.tag {
|
||||
// Stop tracking?
|
||||
case AlertView.StopTracking.rawValue:
|
||||
switch buttonIndex {
|
||||
case alertView.cancelButtonIndex:
|
||||
break
|
||||
default:
|
||||
LocationManager.sharedManager.stopTracking()
|
||||
SendGPSFixController.sharedManager.checkIn = nil
|
||||
self.dismissViewControllerAnimated(true, completion: nil)
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,23 @@ class TrackingViewController : UIViewController {
|
||||
NSNotificationCenter.defaultCenter().addObserver(self, selector:"newLocation:", name:LocationManager.NotificationType.newLocation, object: nil)
|
||||
NSNotificationCenter.defaultCenter().addObserver(self, selector:"locationManagerFailed:", name:LocationManager.NotificationType.locationManagerFailed, object: nil)
|
||||
}
|
||||
|
||||
// MARK:- Buttons
|
||||
|
||||
/* Stop tracking, go back to regattas view */
|
||||
@IBAction func stopTrackingButtonTapped(sender: AnyObject) {
|
||||
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Stop tracking?", comment: ""), message: "", preferredStyle: .Alert)
|
||||
let aCancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .Cancel, handler: nil)
|
||||
alertController.addAction(aCancel)
|
||||
let aStop = UIAlertAction(title: NSLocalizedString("Stop", comment: ""), style: .Default) { action in
|
||||
LocationManager.sharedManager.stopTracking()
|
||||
SendGPSFixController.sharedManager.checkIn = nil
|
||||
self.dismissViewControllerAnimated(true, completion: nil)
|
||||
}
|
||||
alertController.addAction(aStop)
|
||||
presentViewController(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK:- Notifications
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Require-Bundle: com.sap.sailing.domain,
|
||||
com.sap.sse.gwt.adminconsole,
|
||||
org.apache.commons.math;bundle-version="2.1.0"
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Import-Package: com.google.gwt.core.ext.linker,
|
||||
Import-Package: com.google.gwt.core.ext.linker;resolution:=optional,
|
||||
com.sap.sse.util,
|
||||
javax.servlet;version="3.1.0",
|
||||
javax.servlet.descriptor;version="3.1.0",
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
|
||||
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
|
||||
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
|
||||
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
|
||||
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
|
||||
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war "${project_loc:com.sap.sailing.dashboards.gwt}" -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9998 com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
|
||||
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
|
||||
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
|
||||
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
|
||||
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
|
||||
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-incremental -war "${project_loc:com.sap.sailing.dashboards.gwt}" -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9998 com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m"/>
|
||||
</launchConfiguration>
|
||||
</launchConfiguration>
|
||||
@@ -1,47 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
|
||||
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
|
||||
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
|
||||
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
|
||||
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
|
||||
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
|
||||
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war "${project_loc:com.sap.sailing.dashboards.gwt}" -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort auto com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
|
||||
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
|
||||
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
|
||||
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
|
||||
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
|
||||
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
|
||||
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war "${project_loc:com.sap.sailing.dashboards.gwt}" -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort auto com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m"/>
|
||||
</launchConfiguration>
|
||||
</launchConfiguration>
|
||||
+5
-4
@@ -47,12 +47,13 @@ public final class StartAnalysisDTOFactory extends AbstractStartAnalysisCreation
|
||||
List<MarkPassing> markPassingsInOrder = convertMarkpPassingsIteratorToList(trackedRace.getMarkPassingsInOrder(secondWaypoint).iterator());
|
||||
boolean isCompetitorNullOrOneOfFirstThreeCompetitors = false;
|
||||
for (int i = 0; i < MINIMUM_MARKPASSIINGS_AT_FIRST_MARK; i++) {
|
||||
competitors.add(createStartAnalysisCompetitorDTO(dashboardDispatchContext, trackedRace, i + 1, markPassingsInOrder.get(i).getCompetitor()));
|
||||
if (competitor == null || markPassingsInOrder.get(i).getCompetitor().equals(competitor)) {
|
||||
isCompetitorNullOrOneOfFirstThreeCompetitors = true;
|
||||
if (markPassingsInOrder.size() - 1 >= i) {
|
||||
competitors.add(createStartAnalysisCompetitorDTO(dashboardDispatchContext, trackedRace, i+1, markPassingsInOrder.get(i).getCompetitor()));
|
||||
if (competitor == null || markPassingsInOrder.get(i).getCompetitor().equals(competitor)) {
|
||||
isCompetitorNullOrOneOfFirstThreeCompetitors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (competitor != null && !isCompetitorNullOrOneOfFirstThreeCompetitors) {
|
||||
int rankOfCompetitorWhilePassingSecondWaypoint = getRankOfCompetitorWhilePassingSecondWaypoint(competitor, trackedRace);
|
||||
competitors.add(createStartAnalysisCompetitorDTO(dashboardDispatchContext, trackedRace, rankOfCompetitorWhilePassingSecondWaypoint, competitor));
|
||||
|
||||
+16
@@ -79,4 +79,20 @@ public abstract class AbstractDistance implements Distance {
|
||||
public Distance add(Distance d) {
|
||||
return new NauticalMileDistance(getNauticalMiles()+d.getNauticalMiles());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double divide(Distance other) {
|
||||
return getMeters() / other.getMeters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distance abs() {
|
||||
final Distance result;
|
||||
if (getMeters() >= 0) {
|
||||
result = this;
|
||||
} else {
|
||||
result = scale(-1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -164,7 +164,8 @@ public class AbstractPosition implements Position {
|
||||
@Override
|
||||
public Distance getDistanceToLine(Position left, Position right) {
|
||||
final Distance result;
|
||||
final int factor = this.crossTrackError(left, left.getBearingGreatCircle(right)).getMeters()>0?1:-1;
|
||||
final Distance crossTrackError = this.crossTrackError(left, left.getBearingGreatCircle(right));
|
||||
final int factor = crossTrackError.getMeters()>0?1:-1;
|
||||
double toLeft = Math.abs(left.getBearingGreatCircle(this).getDifferenceTo(left.getBearingGreatCircle(right))
|
||||
.getDegrees());
|
||||
double toRight = Math.abs(right.getBearingGreatCircle(this).getDifferenceTo(right.getBearingGreatCircle(left))
|
||||
@@ -172,9 +173,9 @@ public class AbstractPosition implements Position {
|
||||
if (toLeft > 90) {
|
||||
result = this.getDistance(left).scale(factor);
|
||||
} else if (toRight > 90) {
|
||||
result = this.getDistance(right).scale(factor);
|
||||
} else {
|
||||
result = this.crossTrackError(left, left.getBearingGreatCircle(right));
|
||||
result = this.getDistance(right).scale(factor);
|
||||
} else {
|
||||
result = crossTrackError;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,22 @@ public interface Distance extends Comparable<Distance>, Serializable {
|
||||
public String toString() {
|
||||
return "0m";
|
||||
}
|
||||
|
||||
@Override
|
||||
public double divide(Distance other) {
|
||||
final double result;
|
||||
if (other.getMeters() == 0) {
|
||||
result = 0./0.;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distance abs() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
double getGeographicalMiles();
|
||||
@@ -108,4 +124,16 @@ public interface Distance extends Comparable<Distance>, Serializable {
|
||||
Speed inTime(Duration duration);
|
||||
|
||||
Distance add(Distance d);
|
||||
|
||||
/**
|
||||
* Divides this distance by the {@code other} distance. Should the {@code other} distance
|
||||
* be {@link #<code>null</code>}, the result will be {@code NaN}.
|
||||
*/
|
||||
double divide(Distance other);
|
||||
|
||||
/**
|
||||
* If this distance is negative, returns a positive difference with the same absolute value; for positive
|
||||
* distances, this distance is returned.
|
||||
*/
|
||||
Distance abs();
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.sap.sailing.domain.common;
|
||||
|
||||
public enum FixType { BUOY }
|
||||
+1
@@ -14,6 +14,7 @@ package com.sap.sailing.domain.common;
|
||||
public enum PassingInstruction {
|
||||
None(1, 2),
|
||||
Port(1),
|
||||
Single_Unknown(1),
|
||||
Starboard(1),
|
||||
Gate(2),
|
||||
Line(2),
|
||||
|
||||
@@ -69,8 +69,8 @@ public interface Position extends Serializable {
|
||||
Distance alongTrackDistance(Position from, Bearing bearing);
|
||||
|
||||
/**
|
||||
* Computes the distance from this position to the line between <code>left<code> and <code>right<code>. This distance
|
||||
* is positive if <code>left<code> is actually the position farther to the left, as seen from this position.
|
||||
* Computes the distance from this position to the line between <code>left</code> and <code>right</code>. This distance
|
||||
* is positive if <code>left</code> is actually the position farther to the left, as seen from this position.
|
||||
*/
|
||||
Distance getDistanceToLine(Position left, Position right);
|
||||
|
||||
|
||||
+18
@@ -129,6 +129,24 @@ public class RaceColumnDTO extends NamedDTO implements Serializable {
|
||||
return start;
|
||||
}
|
||||
|
||||
public Date getStartOfTracking(FleetDTO fleet){
|
||||
Date start = null;
|
||||
RaceDTO raceDTO = racesPerFleet.get(fleet);
|
||||
if (raceDTO != null && raceDTO.isTracked) {
|
||||
start = raceDTO.trackedRace.startOfTracking;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
public Date getEndOfTracking(FleetDTO fleet){
|
||||
Date start = null;
|
||||
RaceDTO raceDTO = racesPerFleet.get(fleet);
|
||||
if (raceDTO != null && raceDTO.isTracked) {
|
||||
start = raceDTO.trackedRace.endOfTracking;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link PlacemarkOrderDTO places} or <code>null</code>, if no places are available
|
||||
*/
|
||||
|
||||
+2
-1
@@ -28,6 +28,7 @@ public enum Permission implements com.sap.sse.security.shared.Permission {
|
||||
MANAGE_USERS,
|
||||
MANAGE_FILE_STORAGE,
|
||||
MANAGE_MARK_PASSINGS,
|
||||
MANAGE_MARK_POSITIONS,
|
||||
CAN_REPLAY_DURING_LIVE_RACES,
|
||||
|
||||
// back-end permissions
|
||||
@@ -35,7 +36,7 @@ public enum Permission implements com.sap.sse.security.shared.Permission {
|
||||
REGATTA,
|
||||
LEADERBOARD,
|
||||
LEADERBOARD_GROUP,
|
||||
TRACKED_RACE
|
||||
TRACKED_RACE,
|
||||
;
|
||||
|
||||
// TODO once we can use Java8 here, move this up into a "default" method on the Permission interface
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ public class SailingPermissionsForRoleProvider implements PermissionsForRoleProv
|
||||
// RaceBoard:
|
||||
Permission.MANAGE_MEDIA,
|
||||
Permission.MANAGE_MARK_PASSINGS,
|
||||
Permission.MANAGE_MARK_POSITIONS,
|
||||
|
||||
// AdminConsole:
|
||||
Permission.MANAGE_ALL_COMPETITORS,
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ public class CompetitorJsonConstants {
|
||||
public static final String FIELD_DISPLAY_COLOR = "displayColor";
|
||||
public static final String FIELD_EMAIL = "email";
|
||||
public static final String FIELD_FLAG_IMAGE_URI = "flagImageUri";
|
||||
public static final String FIELD_TEAM_IMAGE_URI = "teamImageUri";
|
||||
public static final String FIELD_TIME_ON_TIME_FACTOR = "timeOnTimeFactor";
|
||||
public static final String FIELD_TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE = "timeOnDistanceAllowanceInSecondsPerNauticalMile";
|
||||
}
|
||||
+34
-19
@@ -1615,8 +1615,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
loadRegattaLogEvents(result, query, identifier);
|
||||
} catch (Throwable t) {
|
||||
// something went wrong during DB access; report, then use empty new regatta log
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded regatta log data. Check MongoDB settings.");
|
||||
logger.log(Level.SEVERE, "loadRegattaLog", t);
|
||||
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded regatta log data for "+identifier+". Check MongoDB settings.", t);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1708,7 +1707,15 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
private RegattaLogRegisterCompetitorEvent loadRegattaLogRegisterCompetitorEvent(TimePoint createdAt, AbstractLogEventAuthor author,
|
||||
TimePoint logicalTimePoint, Serializable id, DBObject dbObject) {
|
||||
Competitor comp = getCompetitorByID(dbObject);
|
||||
return new RegattaLogRegisterCompetitorEventImpl(createdAt, logicalTimePoint, author, id, comp);
|
||||
final RegattaLogRegisterCompetitorEvent result;
|
||||
if (comp == null) {
|
||||
result = null;
|
||||
logger.log(Level.SEVERE, "Couldn't resolve competitor with ID "+dbObject.get(FieldNames.REGATTA_LOG_COMPETITOR_ID.name())+
|
||||
" from registration event with ID "+id+". Skipping this competitor registration.");
|
||||
} else {
|
||||
result = new RegattaLogRegisterCompetitorEventImpl(createdAt, logicalTimePoint, author, id, comp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private RegattaLogCloseOpenEndedDeviceMappingEvent loadRegattaLogCloseOpenEndedDeviceMappingEvent(TimePoint createdAt,
|
||||
@@ -1795,23 +1802,31 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
final Serializable competitorId = (Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name());
|
||||
Competitor mappedTo = baseDomainFactory.getExistingCompetitorById(
|
||||
(Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name()));
|
||||
@SuppressWarnings("deprecation") // used only for auto-migration; may be removed in future releases
|
||||
final FieldNames deprecatedFromFieldName = FieldNames.RACE_LOG_FROM;
|
||||
@SuppressWarnings("deprecation") // used only for auto-migration; may be removed in future releases
|
||||
final FieldNames deprecatedToFieldName = FieldNames.RACE_LOG_TO;
|
||||
Triple<TimePoint, TimePoint, Boolean> times = loadFromToTimePoint(dbObject, FieldNames.REGATTA_LOG_FROM, deprecatedFromFieldName, FieldNames.REGATTA_LOG_TO, deprecatedToFieldName);
|
||||
final TimePoint from = times.getA();
|
||||
final TimePoint to = times.getB();
|
||||
final boolean needsMigration = times.getC();
|
||||
final RegattaLogDeviceCompetitorMappingEventImpl result = new RegattaLogDeviceCompetitorMappingEventImpl(createdAt, logicalTimePoint, author, id, mappedTo, device, from, to);
|
||||
if (needsMigration) {
|
||||
// remove old version of mapping event
|
||||
WriteResult removeResult = database.getCollection(CollectionNames.REGATTA_LOGS.name()).remove(outerDBObject);
|
||||
assert removeResult.getN() == 1;
|
||||
// and then insert using the fixed storage implementation
|
||||
new MongoObjectFactoryImpl(database, serviceFinderFactory).storeRegattaLogEvent(regattaLogIdentifier, result);
|
||||
competitorId);
|
||||
final RegattaLogDeviceCompetitorMappingEventImpl result;
|
||||
if (mappedTo == null) {
|
||||
logger.severe("Found a "+RegattaLogDeviceCompetitorMappingEventImpl.class.getName()+
|
||||
" event but couldn't find competitor with ID "+competitorId);
|
||||
result = null;
|
||||
} else {
|
||||
@SuppressWarnings("deprecation") // used only for auto-migration; may be removed in future releases
|
||||
final FieldNames deprecatedFromFieldName = FieldNames.RACE_LOG_FROM;
|
||||
@SuppressWarnings("deprecation") // used only for auto-migration; may be removed in future releases
|
||||
final FieldNames deprecatedToFieldName = FieldNames.RACE_LOG_TO;
|
||||
Triple<TimePoint, TimePoint, Boolean> times = loadFromToTimePoint(dbObject, FieldNames.REGATTA_LOG_FROM, deprecatedFromFieldName, FieldNames.REGATTA_LOG_TO, deprecatedToFieldName);
|
||||
final TimePoint from = times.getA();
|
||||
final TimePoint to = times.getB();
|
||||
final boolean needsMigration = times.getC();
|
||||
result = new RegattaLogDeviceCompetitorMappingEventImpl(createdAt, logicalTimePoint, author, id, mappedTo, device, from, to);
|
||||
if (needsMigration) {
|
||||
// remove old version of mapping event
|
||||
WriteResult removeResult = database.getCollection(CollectionNames.REGATTA_LOGS.name()).remove(outerDBObject);
|
||||
assert removeResult.getN() == 1;
|
||||
// and then insert using the fixed storage implementation
|
||||
new MongoObjectFactoryImpl(database, serviceFinderFactory).storeRegattaLogEvent(regattaLogIdentifier, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+4
-2
@@ -19,6 +19,7 @@ import org.junit.rules.ExpectedException;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLog;
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogRegisterCompetitorEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogUseCompetitorsFromRaceLogEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
|
||||
@@ -158,7 +159,6 @@ public class CreateAndTrackWithRaceLogTest {
|
||||
@Test
|
||||
public void canDenote_Add_Track() throws MalformedURLException, FileNotFoundException, URISyntaxException,
|
||||
Exception {
|
||||
|
||||
RaceColumn column = leaderboard.getRaceColumnByName(columnName);
|
||||
RegattaLog regattaLog = leaderboard.getRegattaLike().getRegattaLog();
|
||||
RaceLog raceLog = column.getRaceLog(fleet);
|
||||
@@ -176,10 +176,11 @@ public class CreateAndTrackWithRaceLogTest {
|
||||
addFixes0(dev1);
|
||||
raceLog.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(t(), author, t(), UUID.randomUUID(), 0));
|
||||
raceLog.add(new RaceLogRegisterCompetitorEventImpl(t(), author, 0, comp1));
|
||||
raceLog.add(new RaceLogStartOfTrackingEventImpl(t(0), author, /* passId */ 0));
|
||||
// start tracking
|
||||
adapter.startTracking(service, leaderboard, column, fleet);
|
||||
|
||||
// now there is a trackedrace
|
||||
// now there is a tracked race
|
||||
TrackedRace race = column.getTrackedRace(fleet);
|
||||
assertNotNull(race);
|
||||
|
||||
@@ -211,6 +212,7 @@ public class CreateAndTrackWithRaceLogTest {
|
||||
t(0), t(10)));
|
||||
addFixes0(dev1);
|
||||
regattaLog.add(new RegattaLogRegisterCompetitorEventImpl(t(), t(), author, UUID.randomUUID(), comp1));
|
||||
raceLog.add(new RaceLogStartOfTrackingEventImpl(t(0), author, /* passId */ 0));
|
||||
|
||||
// start tracking
|
||||
adapter.startTracking(service, leaderboard, column, fleet);
|
||||
|
||||
+28
-50
@@ -13,6 +13,8 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.AbstractLog;
|
||||
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
|
||||
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLog;
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent;
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent;
|
||||
@@ -20,7 +22,10 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
|
||||
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastPublishedCourseDesignFinder;
|
||||
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
|
||||
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesFinder;
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.BaseRaceLogEventVisitor;
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
|
||||
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent;
|
||||
import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RaceInformationFinder;
|
||||
@@ -107,6 +112,8 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
|
||||
private final Map<DeviceIdentifier, List<DeviceMapping<Competitor>>> competitorMappingsByDevices = new HashMap<>();
|
||||
|
||||
private DynamicTrackedRace trackedRace;
|
||||
private final AbstractLogEventAuthor raceLogEventAuthor = new LogEventAuthorImpl(
|
||||
RaceLogRaceTracker.class.getName(), 0);
|
||||
|
||||
private static final Logger logger = Logger.getLogger(RaceLogRaceTracker.class.getName());
|
||||
|
||||
@@ -175,6 +182,14 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
|
||||
|
||||
@Override
|
||||
public void stop(boolean preemptive) {
|
||||
RaceLog raceLog = params.getRaceLog();
|
||||
final Pair<TimePoint, TimePoint> trackingTimes = new TrackingTimesFinder(raceLog).analyze();
|
||||
if (trackingTimes == null || trackingTimes.getB() == null) {
|
||||
// seems the first time tracking for this race is started; enter "now" as start of tracking
|
||||
// into the race log
|
||||
raceLog.add(new RaceLogEndOfTrackingEventImpl(MillisecondsTimePoint.now(), raceLogEventAuthor, /* passId */ 0));
|
||||
}
|
||||
|
||||
// mark passing calculator is automatically stopped, when the race status is set to {@link
|
||||
// TrackedRaceStatusEnum#FINISHED}
|
||||
trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, 100));
|
||||
@@ -239,50 +254,6 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
|
||||
return new RegattaLogDeviceMarkMappingFinder(params.getRegattaLog()).analyze();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use mapping time ranges to set time start and end of tracking time for race
|
||||
*/
|
||||
private void updateStartAndEndOfTracking() {
|
||||
// TODO bug 3196: this would need to be evaluated lazily in TrackedRaceImpl.getStartOfTracking / getEndOfTracking
|
||||
final Pair<TimePoint, TimePoint> trackingTimesFromRaceLog = trackedRace.getTrackingTimesFromRaceLogs();
|
||||
if (trackingTimesFromRaceLog == null) {
|
||||
TimePoint earliestMappingStart = new MillisecondsTimePoint(Long.MAX_VALUE);
|
||||
TimePoint latestMappingEnd = new MillisecondsTimePoint(Long.MIN_VALUE);
|
||||
for (List<? extends DeviceMapping<?>> list : competitorMappings.values()) {
|
||||
for (DeviceMapping<?> mapping : list) {
|
||||
earliestMappingStart = getUpdatedEarliestMappingStart(earliestMappingStart, mapping.getTimeRange().from());
|
||||
latestMappingEnd = getUpdatedLatestMappingEnd(latestMappingEnd, mapping.getTimeRange().to());
|
||||
}
|
||||
}
|
||||
for (List<? extends DeviceMapping<?>> list : markMappings.values()) {
|
||||
for (DeviceMapping<?> mapping : list) {
|
||||
earliestMappingStart = getUpdatedEarliestMappingStart(earliestMappingStart, mapping.getTimeRange().from());
|
||||
latestMappingEnd = getUpdatedLatestMappingEnd(latestMappingEnd, mapping.getTimeRange().to());
|
||||
}
|
||||
}
|
||||
trackedRace.setStartOfTrackingReceived(earliestMappingStart == null ? TimePoint.BeginningOfTime : earliestMappingStart);
|
||||
trackedRace.setEndOfTrackingReceived(latestMappingEnd == null ? TimePoint.EndOfTime : latestMappingEnd);
|
||||
}
|
||||
}
|
||||
|
||||
private TimePoint getUpdatedEarliestMappingStart(TimePoint earliestMappingStart, final TimePoint from) {
|
||||
if (from == null) {
|
||||
earliestMappingStart = null; // no further updates; one open interval means startOfTracking shall be the beginning of time
|
||||
} else if (earliestMappingStart != null && from.before(earliestMappingStart)) {
|
||||
earliestMappingStart = from;
|
||||
}
|
||||
return earliestMappingStart;
|
||||
}
|
||||
|
||||
private TimePoint getUpdatedLatestMappingEnd(TimePoint latestMappingEnd, final TimePoint to) {
|
||||
if (to == null) {
|
||||
latestMappingEnd = null; // no further updates; one open interval means startOfTracking shall be the beginning of time
|
||||
} else if (latestMappingEnd != null && to.after(latestMappingEnd)) {
|
||||
latestMappingEnd = to;
|
||||
}
|
||||
return latestMappingEnd;
|
||||
}
|
||||
|
||||
private <ItemT extends WithID, FixT extends GPSFix> boolean hasMappingAlreadyBeenLoaded(
|
||||
DeviceMapping<ItemT> newMapping, List<DeviceMapping<ItemT>> oldMappings) {
|
||||
if (oldMappings == null) {
|
||||
@@ -354,8 +325,6 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
|
||||
|
||||
markMappingsByDevices.clear();
|
||||
markMappingsByDevices.putAll(transformToMappingsByDevice(markMappings));
|
||||
|
||||
updateStartAndEndOfTracking();
|
||||
}
|
||||
|
||||
private void onDeviceMarkMappingEvent(RegattaLogDeviceMarkMappingEvent event) {
|
||||
@@ -429,7 +398,6 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
|
||||
competitorMappings.putAll(newMappings);
|
||||
competitorMappingsByDevices.clear();
|
||||
competitorMappingsByDevices.putAll(transformToMappingsByDevice(competitorMappings));
|
||||
updateStartAndEndOfTracking();
|
||||
}
|
||||
|
||||
private void onStartTrackingEvent(RaceLogStartTrackingEvent event) {
|
||||
@@ -439,11 +407,15 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
|
||||
}
|
||||
|
||||
private void onStartOfTrackingEvent(RaceLogStartOfTrackingEvent event) {
|
||||
trackedRace.setStartOfTrackingReceived(event.getLogicalTimePoint());
|
||||
if (trackedRace != null) {
|
||||
trackedRace.setStartOfTrackingReceived(event.getLogicalTimePoint());
|
||||
}
|
||||
}
|
||||
|
||||
private void onEndOfTrackingEvent(RaceLogEndOfTrackingEvent event) {
|
||||
trackedRace.setEndOfTrackingReceived(event.getLogicalTimePoint());
|
||||
if (trackedRace != null) {
|
||||
trackedRace.setEndOfTrackingReceived(event.getLogicalTimePoint());
|
||||
}
|
||||
}
|
||||
|
||||
private void onCourseDesignChangedEvent(RaceLogCourseDesignChangedEvent event) {
|
||||
@@ -466,6 +438,12 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
|
||||
RaceColumn raceColumn = params.getRaceColumn();
|
||||
Fleet fleet = params.getFleet();
|
||||
RaceLogDenoteForTrackingEvent denoteEvent = new RaceInformationFinder(raceLog).analyze();
|
||||
final Pair<TimePoint, TimePoint> trackingTimes = new TrackingTimesFinder(raceLog).analyze();
|
||||
if (trackingTimes == null) {
|
||||
// seems the first time tracking for this race is started; enter "now" as start of tracking
|
||||
// into the race log
|
||||
raceLog.add(new RaceLogStartOfTrackingEventImpl(MillisecondsTimePoint.now(), raceLogEventAuthor, /* passId */ 0));
|
||||
}
|
||||
BoatClass boatClass = denoteEvent.getBoatClass();
|
||||
String raceName = denoteEvent.getRaceName();
|
||||
CourseBase courseBase = new LastPublishedCourseDesignFinder(raceLog).analyze();
|
||||
@@ -473,7 +451,7 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
|
||||
courseBase = new CourseDataImpl("Default course for " + raceName);
|
||||
logger.log(Level.FINE, "Using empty course in creation of race " + raceName);
|
||||
}
|
||||
Course course = new CourseImpl(raceName + " course", courseBase.getWaypoints());
|
||||
final Course course = new CourseImpl(raceName + " course", courseBase.getWaypoints());
|
||||
if (raceColumn.getTrackedRace(fleet) != null) {
|
||||
if (event != null) {
|
||||
try {
|
||||
|
||||
+3
-2
@@ -241,8 +241,9 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
|
||||
public void pingMark(RegattaLog log, Mark mark, GPSFix gpsFix, RacingEventService service) {
|
||||
final PingDeviceIdentifierImpl device = new PingDeviceIdentifierImpl();
|
||||
final TimePoint timePoint = gpsFix.getTimePoint();
|
||||
final RegattaLogEvent mapping = new RegattaLogDeviceMarkMappingEventImpl(timePoint,
|
||||
timePoint, service.getServerAuthor(), UUID.randomUUID(), mark, device, timePoint, timePoint);
|
||||
final TimePoint now = MillisecondsTimePoint.now();
|
||||
final RegattaLogEvent mapping = new RegattaLogDeviceMarkMappingEventImpl(now,
|
||||
now, service.getServerAuthor(), UUID.randomUUID(), mark, device, timePoint, timePoint);
|
||||
log.add(mapping);
|
||||
try {
|
||||
service.getGPSFixStore().storeFix(device, gpsFix);
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.sap.sailing.domain.regattalog.tracking.analyzing.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.shared.analyzing.CompetitorDeregistrator;
|
||||
import com.sap.sailing.domain.abstractlog.shared.analyzing.CompetitorsInLogAnalyzer;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.impl.CompetitorImpl;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
public class CompetitorDeregistrationTest extends AbstractRegattaLogTrackingTest {
|
||||
private final Competitor competitor = new CompetitorImpl("comp", "Comp", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
|
||||
private final Competitor competitor2 = new CompetitorImpl("comp2", "Comp2", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
|
||||
|
||||
@Test
|
||||
public void testCompetitorDeregistration() {
|
||||
log.add(new RegattaLogRegisterCompetitorEventImpl(MillisecondsTimePoint.now(), author, competitor));
|
||||
log.add(new RegattaLogRegisterCompetitorEventImpl(MillisecondsTimePoint.now(), author, competitor2));
|
||||
final CompetitorDeregistrator<RegattaLog, RegattaLogEvent, RegattaLogEventVisitor> deregistrator = new CompetitorDeregistrator<>(log,
|
||||
Collections.singleton(competitor2), author);
|
||||
final Set<RegattaLogEvent> events = deregistrator.analyze();
|
||||
deregistrator.deregister(events);
|
||||
final Set<Competitor> competitors = new CompetitorsInLogAnalyzer<>(log).analyze();
|
||||
assertEquals(1, competitors.size());
|
||||
assertSame(competitor, competitors.iterator().next());
|
||||
}
|
||||
}
|
||||
+223
@@ -1,9 +1,11 @@
|
||||
package com.sap.sailing.domain.regattalog.tracking.analyzing.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import junit.framework.Assert;
|
||||
@@ -17,6 +19,7 @@ import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCo
|
||||
import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceCompetitorMappingFinder;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.impl.CompetitorImpl;
|
||||
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
|
||||
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
|
||||
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
|
||||
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
|
||||
@@ -55,6 +58,226 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
|
||||
log.add(mapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expecting that a single, closed mapping interval spanning a fix will be cut into two intervals excluding the fix time point
|
||||
*/
|
||||
@Test
|
||||
public void testFixRemovalForMiddleOfInterval() throws NotRevokableException {
|
||||
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
|
||||
addMapping(author, device, 100l, 200l, competitor);
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
|
||||
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
|
||||
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
|
||||
}
|
||||
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(150));
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(2, deviceMappings.size());
|
||||
boolean found100 = false;
|
||||
for (final DeviceMapping<Competitor> mapping : deviceMappings) {
|
||||
if (mapping.getTimeRange().from().asMillis() == 100) {
|
||||
found100 = true;
|
||||
assertEquals(149, mapping.getTimeRange().to().asMillis());
|
||||
} else {
|
||||
assertEquals(151, mapping.getTimeRange().from().asMillis());
|
||||
assertEquals(200, mapping.getTimeRange().to().asMillis());
|
||||
}
|
||||
}
|
||||
assertTrue(found100);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFixRemovalForMiddleOfIntervalClosedSeparately() throws NotRevokableException {
|
||||
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
|
||||
final Serializable mappingId = addMapping(author, device, 100l, null, competitor);
|
||||
closeMapping(author, device, mappingId, 200);
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
|
||||
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
|
||||
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
|
||||
}
|
||||
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(150));
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(2, deviceMappings.size());
|
||||
for (final DeviceMapping<Competitor> mapping : deviceMappings) {
|
||||
if (mapping.getTimeRange().from().asMillis() == 100) {
|
||||
assertEquals(149, mapping.getTimeRange().to().asMillis());
|
||||
} else {
|
||||
assertEquals(151, mapping.getTimeRange().from().asMillis());
|
||||
assertEquals(200, mapping.getTimeRange().to().asMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFixRemovalForStartOfInterval() throws NotRevokableException {
|
||||
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
|
||||
addMapping(author, device, 100l, 200l, competitor);
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
|
||||
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
|
||||
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
|
||||
}
|
||||
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
assertEquals(101, deviceMappings.get(0).getTimeRange().from().asMillis());
|
||||
assertEquals(200, deviceMappings.get(0).getTimeRange().to().asMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFixRemovalForStartOfIntervalClosedSeparately() throws NotRevokableException {
|
||||
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
|
||||
final Serializable mappingId = addMapping(author, device, 100l, null, competitor);
|
||||
closeMapping(author, device, mappingId, 200);
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
|
||||
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
|
||||
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
|
||||
}
|
||||
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
assertEquals(101, deviceMappings.get(0).getTimeRange().from().asMillis());
|
||||
assertEquals(200, deviceMappings.get(0).getTimeRange().to().asMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFixRemovalForEndOfInterval() throws NotRevokableException {
|
||||
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
|
||||
addMapping(author, device, 100l, 200l, competitor);
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
|
||||
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
|
||||
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
|
||||
}
|
||||
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(200));
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
assertEquals(100, deviceMappings.get(0).getTimeRange().from().asMillis());
|
||||
assertEquals(199, deviceMappings.get(0).getTimeRange().to().asMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFixRemovalForEndOfIntervalClosedSeparately() throws NotRevokableException {
|
||||
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
|
||||
final Serializable mappingId = addMapping(author, device, 100l, null, competitor);
|
||||
closeMapping(author, device, mappingId, 200);
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
|
||||
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
|
||||
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
|
||||
}
|
||||
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(200));
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
assertEquals(100, deviceMappings.get(0).getTimeRange().from().asMillis());
|
||||
assertEquals(199, deviceMappings.get(0).getTimeRange().to().asMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFixRemovalForMillisecondInterval() throws NotRevokableException {
|
||||
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
|
||||
addMapping(author, device, 100l, 100l, competitor);
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
|
||||
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
|
||||
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().to());
|
||||
}
|
||||
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertTrue(mappings.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFixRemovalForMillisecondIntervalClosedSeparately() throws NotRevokableException {
|
||||
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
|
||||
final Serializable mappingId = addMapping(author, device, 100l, null, competitor);
|
||||
closeMapping(author, device, mappingId, 100);
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertEquals(1, mappings.size());
|
||||
assertTrue(mappings.containsKey(competitor));
|
||||
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
|
||||
assertEquals(1, deviceMappings.size());
|
||||
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
|
||||
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
|
||||
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().to());
|
||||
}
|
||||
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
|
||||
{
|
||||
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
|
||||
assertTrue(mappings.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notDisturbedByMappingsForOtherItemsAndDevices() {
|
||||
//two mappings for first competitor found? (competitor2 mappings should not "distract")
|
||||
|
||||
+14
@@ -1,9 +1,16 @@
|
||||
package com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
public class RegattaLogDeviceCompetitorMappingFinder extends RegattaLogDeviceMappingFinder<Competitor> {
|
||||
|
||||
@@ -15,4 +22,11 @@ public class RegattaLogDeviceCompetitorMappingFinder extends RegattaLogDeviceMap
|
||||
protected boolean isValidMapping(RegattaLogDeviceMappingEvent<?> mapping) {
|
||||
return mapping instanceof RegattaLogDeviceCompetitorMappingEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RegattaLogDeviceCompetitorMappingEvent createDeviceMappingEvent(Competitor item, AbstractLogEventAuthor author,
|
||||
TimePoint from, TimePoint to, DeviceIdentifier deviceId) {
|
||||
final TimePoint now = MillisecondsTimePoint.now();
|
||||
return new RegattaLogDeviceCompetitorMappingEventImpl(now, now, author, UUID.randomUUID(), item, deviceId, from, to);
|
||||
}
|
||||
}
|
||||
+52
-11
@@ -8,11 +8,13 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLog;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
|
||||
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
|
||||
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
|
||||
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
|
||||
import com.sap.sailing.domain.racelogtracking.impl.DeviceMappingImpl;
|
||||
@@ -34,7 +36,7 @@ import com.sap.sse.common.impl.TimeRangeImpl;
|
||||
* {@code DeviceMapping}, the only changes being introduced by closing open time ranges in cases where an
|
||||
* according {@code CloseOpenEndedDeviceMappingEvent} exists.
|
||||
*/
|
||||
public class RegattaLogDeviceMappingFinder<ItemT extends WithID> extends RegattaLogAnalyzer<Map<ItemT, List<DeviceMapping<ItemT>>>> {
|
||||
public abstract class RegattaLogDeviceMappingFinder<ItemT extends WithID> extends RegattaLogAnalyzer<Map<ItemT, List<DeviceMapping<ItemT>>>> {
|
||||
private static Logger logger = Logger.getLogger(RegattaLogDeviceMappingFinder.class.getName());
|
||||
|
||||
public RegattaLogDeviceMappingFinder(RegattaLog log) {
|
||||
@@ -51,41 +53,46 @@ public class RegattaLogDeviceMappingFinder<ItemT extends WithID> extends Regatta
|
||||
Collections.singletonList(originalEventId));
|
||||
}
|
||||
|
||||
private <T, U> List<U> getItemSet(Map<T, List<U>> map, T item) {
|
||||
List<U> list = map.get(item);
|
||||
private List<RegattaLogDeviceMappingEvent<ItemT>> getMappingEventsForItem(Map<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>> map, ItemT item) {
|
||||
List<RegattaLogDeviceMappingEvent<ItemT>> list = map.get(item);
|
||||
if (list == null) {
|
||||
list = new ArrayList<U>();
|
||||
list = new ArrayList<RegattaLogDeviceMappingEvent<ItemT>>();
|
||||
map.put(item, list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<ItemT, List<DeviceMapping<ItemT>>> performAnalysis() {
|
||||
Map<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>> events = new HashMap<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>>();
|
||||
Map<ItemT, List<DeviceMapping<ItemT>>> mappings = new HashMap<ItemT, List<DeviceMapping<ItemT>>>();
|
||||
Map<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent> closingEvents = new HashMap<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent>();
|
||||
findUnrevokedMappingAndClosingEvents(events, closingEvents);
|
||||
Map<ItemT, List<DeviceMapping<ItemT>>> mappings = new HashMap<ItemT, List<DeviceMapping<ItemT>>>();
|
||||
for (ItemT item : events.keySet()) {
|
||||
mappings.put(item, closeOpenRanges(events.get(item), item, closingEvents));
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
private void findUnrevokedMappingAndClosingEvents(Map<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>> events,
|
||||
Map<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent> closingEvents) {
|
||||
for (RegattaLogEvent e : getLog().getUnrevokedEvents()) {
|
||||
if (e instanceof RegattaLogDeviceMappingEvent && isValidMapping(((RegattaLogDeviceMappingEvent<?>) e))) {
|
||||
@SuppressWarnings("unchecked")
|
||||
RegattaLogDeviceMappingEvent<ItemT> mappingEvent = (RegattaLogDeviceMappingEvent<ItemT>) e;
|
||||
getItemSet(events, mappingEvent.getMappedTo()).add(mappingEvent);
|
||||
getMappingEventsForItem(events, mappingEvent.getMappedTo()).add(mappingEvent);
|
||||
} else if (e instanceof RegattaLogCloseOpenEndedDeviceMappingEvent) {
|
||||
//a higher priority closing events for the same mapping event overwrites the lower priority one
|
||||
RegattaLogCloseOpenEndedDeviceMappingEvent closingEvent = (RegattaLogCloseOpenEndedDeviceMappingEvent) e;
|
||||
closingEvents.put(closingEvent.getDeviceMappingEventId(), closingEvent);
|
||||
}
|
||||
}
|
||||
for (ItemT item : events.keySet()) {
|
||||
mappings.put(item, closeOpenRanges(events.get(item), item, closingEvents));
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
private List<DeviceMapping<ItemT>> closeOpenRanges(List<RegattaLogDeviceMappingEvent<ItemT>> events, ItemT item,
|
||||
Map<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent> closingEvents) {
|
||||
List<DeviceMapping<ItemT>> result = new ArrayList<DeviceMapping<ItemT>>();
|
||||
for (RegattaLogDeviceMappingEvent<ItemT> event : events) {
|
||||
for (final RegattaLogDeviceMappingEvent<ItemT> event : events) {
|
||||
TimePoint from = event.getFrom();
|
||||
TimePoint to = event.getTo();
|
||||
TimePoint closingTimePoint = closingEvents.containsKey(event.getId()) ? closingEvents.get(event.getId())
|
||||
@@ -100,4 +107,38 @@ public class RegattaLogDeviceMappingFinder<ItemT extends WithID> extends Regatta
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* By revoking existing mappings for {@code item} and replacing them by mappings that exclude {@code fixTimePoint}, any
|
||||
* fix at {@code fixTimePoint} will no longer be linked to {@code item}. If a mapping is limited to the exact fix time point,
|
||||
* the mapping is only revoked and not replaced. If one boundary is exactly the fix time point, the interval is revoked and
|
||||
* replaced by a single interval with that border adjusted so it excludes the fix.
|
||||
*/
|
||||
public void removeTimePointFromMapping(ItemT item, TimePoint fixTimePoint) throws NotRevokableException {
|
||||
Map<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>> events = new HashMap<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>>();
|
||||
Map<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent> closingEvents = new HashMap<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent>();
|
||||
findUnrevokedMappingAndClosingEvents(events, closingEvents);
|
||||
for (final RegattaLogDeviceMappingEvent<ItemT> event : events.get(item)) {
|
||||
final TimePoint from = event.getFrom();
|
||||
final RegattaLogCloseOpenEndedDeviceMappingEvent closingEvent = closingEvents.get(event.getId());
|
||||
final TimePoint to = closingEvent!= null ? closingEvent.getClosingTimePoint() : event.getTo();
|
||||
final TimeRange mappingTimeRange = new TimeRangeImpl(from, to);
|
||||
if (mappingTimeRange.includes(fixTimePoint)) {
|
||||
if (closingEvent != null) {
|
||||
log.revokeEvent(closingEvent.getAuthor(), closingEvent, "removing single time point "+fixTimePoint+" from mapping for "+item);
|
||||
}
|
||||
log.revokeEvent(event.getAuthor(), event, "removing single time point "+fixTimePoint+" from mapping for "+item);
|
||||
final TimePoint endOfFirstHalf = fixTimePoint.minus(1);
|
||||
final TimePoint startOfSecondHalf = fixTimePoint.plus(1);
|
||||
if (!endOfFirstHalf.before(from)) {
|
||||
log.add(createDeviceMappingEvent(item, event.getAuthor(), from, endOfFirstHalf, event.getDevice()));
|
||||
}
|
||||
if (!to.before(startOfSecondHalf)) {
|
||||
log.add(createDeviceMappingEvent(item, event.getAuthor(), startOfSecondHalf, to, event.getDevice()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract RegattaLogDeviceMappingEvent<ItemT> createDeviceMappingEvent(ItemT item, AbstractLogEventAuthor author, TimePoint plus, TimePoint to, DeviceIdentifier deviceId);
|
||||
}
|
||||
+14
@@ -1,9 +1,16 @@
|
||||
package com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
public class RegattaLogDeviceMarkMappingFinder extends RegattaLogDeviceMappingFinder<Mark> {
|
||||
|
||||
@@ -15,4 +22,11 @@ public class RegattaLogDeviceMarkMappingFinder extends RegattaLogDeviceMappingFi
|
||||
protected boolean isValidMapping(RegattaLogDeviceMappingEvent<?> mapping) {
|
||||
return mapping instanceof RegattaLogDeviceMarkMappingEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RegattaLogDeviceMarkMappingEvent createDeviceMappingEvent(Mark item, AbstractLogEventAuthor author,
|
||||
TimePoint from, TimePoint to, DeviceIdentifier deviceId) {
|
||||
final TimePoint now = MillisecondsTimePoint.now();
|
||||
return new RegattaLogDeviceMarkMappingEventImpl(now, now, author, UUID.randomUUID(), item, deviceId, from, to);
|
||||
}
|
||||
}
|
||||
+27
-14
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.domain.abstractlog.shared.analyzing;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -14,14 +15,21 @@ import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
|
||||
import com.sap.sse.common.Util;
|
||||
|
||||
/**
|
||||
* Generates events that close the events that are responsible for the {@link #mapping} that is passed to the
|
||||
* constructor.
|
||||
* Finds the competitor registration events for a given set of {@link Competitor competitors} that must be
|
||||
* {@link AbstractLog#revokeEvent(AbstractLogEventAuthor, AbstractLogEvent) revoked} in order to deregister the
|
||||
* competitors. Note that the de-registration cannot be performed during analysis because
|
||||
* {@link BaseLogAnalyzer#analyze()} obtains a read lock on the log whereas
|
||||
* {@link AbstractLog#revokeEvent(AbstractLogEventAuthor, AbstractLogEvent)} requires a write lock, and upgrading from a
|
||||
* read to a write lock is not possible.<p>
|
||||
*
|
||||
* Clients must call {@link #deregister} with the result of {@link #analyze()} in order to actually perform the
|
||||
* deregistration.
|
||||
*
|
||||
* @author Jan Bross
|
||||
*
|
||||
*/
|
||||
public class CompetitorDeregistrator<LogT extends AbstractLog<EventT, VisitorT>, EventT extends AbstractLogEvent<VisitorT>, VisitorT>
|
||||
extends BaseLogAnalyzer<LogT, EventT, VisitorT, Void> {
|
||||
extends BaseLogAnalyzer<LogT, EventT, VisitorT, Set<EventT>> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(CompetitorDeregistrator.class.getName());
|
||||
protected final Iterable<Competitor> competitorsToDeregister;
|
||||
@@ -34,24 +42,29 @@ extends BaseLogAnalyzer<LogT, EventT, VisitorT, Void> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void performAnalysis() {
|
||||
HashSet<Competitor> competitorSet = new HashSet<Competitor>();
|
||||
protected Set<EventT> performAnalysis() {
|
||||
final Set<EventT> result = new HashSet<>();
|
||||
final HashSet<Competitor> competitorSet = new HashSet<Competitor>();
|
||||
Util.addAll(competitorsToDeregister, competitorSet);
|
||||
|
||||
for (EventT event : log.getUnrevokedEventsDescending()) {
|
||||
if (event instanceof RegisterCompetitorEvent) {
|
||||
RegisterCompetitorEvent<?> registerEvent = (RegisterCompetitorEvent<?>) event;
|
||||
if (competitorSet.contains(registerEvent.getCompetitor())) {
|
||||
try {
|
||||
log.revokeEvent(eventAuthor, event,
|
||||
"unregistering competitor because no longer selected for registration");
|
||||
} catch (NotRevokableException e) {
|
||||
logger.log(Level.WARNING, "could not unregister competitor by adding RevokeEvent", e);
|
||||
}
|
||||
result.add(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void deregister(final Set<EventT> competitorRegistrationEvents) {
|
||||
for (final EventT event : competitorRegistrationEvents) {
|
||||
try {
|
||||
log.revokeEvent(eventAuthor, event,
|
||||
"unregistering competitor because no longer selected for registration");
|
||||
} catch (NotRevokableException e) {
|
||||
logger.log(Level.WARNING, "could not unregister competitor by adding RevokeEvent", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -2,11 +2,14 @@ package com.sap.sailing.domain.base;
|
||||
|
||||
import com.sap.sailing.domain.common.BoatHullType;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.IsManagedByCache;
|
||||
import com.sap.sse.common.Named;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
public interface BoatClass extends Named, IsManagedByCache<SharedDomainFactory> {
|
||||
final Duration APPROXIMATE_AVERAGE_MANEUVER_DURATION = Duration.ONE_SECOND.times(8); // as discussed with Dennis Gehrlein
|
||||
|
||||
/**
|
||||
* The distance returned by this method should be appropriate for use in
|
||||
* {@link TrackedRace#approximate(Competitor, Distance, TimePoint, TimePoint)} so that penalty circles and other
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ public class BoatClassImpl extends NamedImpl implements BoatClass {
|
||||
this.hullLength = hullLength;
|
||||
this.hullBeam = hullBeam;
|
||||
this.hullType = hullType;
|
||||
approximateManeuverDurationInMilliseconds = 8000; // as discussed with Dennis Gehrlein
|
||||
approximateManeuverDurationInMilliseconds = APPROXIMATE_AVERAGE_MANEUVER_DURATION.asMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+21
-4
@@ -313,19 +313,36 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
|
||||
}
|
||||
|
||||
protected boolean add(FixType fix) {
|
||||
return add(fix, /* replace */ false);
|
||||
}
|
||||
|
||||
protected boolean add(FixType fix, boolean replace) {
|
||||
lockForWrite();
|
||||
try {
|
||||
return addWithoutLocking(fix);
|
||||
return addWithoutLocking(fix, replace);
|
||||
} finally {
|
||||
unlockAfterWrite();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The caller must ensure to hold the write lock for this track when calling this methos
|
||||
* The caller must ensure to hold the write lock for this track when calling this method.
|
||||
*
|
||||
* @param replace
|
||||
* whether or not to replace an existing fix in the track that is equal to {@link #fix} as defined by the
|
||||
* comparator used for the {@link #fixes} set. By default this is a comparator only comparing the
|
||||
* fixes' time stamps. Subclasses may use different comparator implementations.
|
||||
*/
|
||||
protected boolean addWithoutLocking(FixType fix) {
|
||||
return getInternalRawFixes().add(fix);
|
||||
protected boolean addWithoutLocking(FixType fix, boolean replace) {
|
||||
final boolean result;
|
||||
final boolean added = getInternalRawFixes().add(fix);
|
||||
if (!added && replace) {
|
||||
getInternalRawFixes().remove(fix);
|
||||
result = getInternalRawFixes().add(fix);
|
||||
} else {
|
||||
result = added;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#Fri Jun 10 21:39:55 CEST 2011
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/com/sap/sailing/domain/test/WindTest.java=UTF-8
|
||||
encoding//src/com/sap/sailing/domain/test/markpassing/StarIDM2013MarkPassingTest.java=UTF-8
|
||||
Binary file not shown.
+4
-4
@@ -84,15 +84,15 @@ public class TimeRangeTest {
|
||||
two = create(11, 16);
|
||||
assertNull(one.union(two));
|
||||
|
||||
two = create(7, Long.MAX_VALUE);
|
||||
two = create(7, TimePoint.EndOfTime.asMillis());
|
||||
union = two.union(one);
|
||||
TimeRange union2 = one.union(two);
|
||||
assertEquals(5, union.from().asMillis());
|
||||
assertEquals(5, union2.from().asMillis());
|
||||
assertEquals(Long.MAX_VALUE, union.to().asMillis());
|
||||
assertEquals(Long.MAX_VALUE, union2.to().asMillis());
|
||||
assertEquals(TimePoint.EndOfTime.asMillis(), union.to().asMillis());
|
||||
assertEquals(TimePoint.EndOfTime.asMillis(), union2.to().asMillis());
|
||||
|
||||
one = create(Long.MIN_VALUE, 10);
|
||||
one = create(TimePoint.BeginningOfTime.asMillis(), 10);
|
||||
union = two.union(one);
|
||||
assertTrue(union.hasOpenBeginning());
|
||||
assertTrue(union.hasOpenEnd());
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package com.sap.sailing.domain.markpassingcalculation.impl;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl.AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
|
||||
public class CandidateFinderImplWhiteBoxTest {
|
||||
private static class CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances extends CandidateFinderImpl {
|
||||
public CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(
|
||||
DynamicTrackedRace race) {
|
||||
super(race);
|
||||
}
|
||||
|
||||
public Double getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(
|
||||
final List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors, boolean startIsLine) {
|
||||
return super.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors, startIsLine);
|
||||
}
|
||||
}
|
||||
|
||||
private CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances finder;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
DynamicTrackedRace trackedRace = mock(DynamicTrackedRace.class);
|
||||
final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29er");
|
||||
RaceDefinition race = mock(RaceDefinition.class);
|
||||
when(trackedRace.getRace()).thenReturn(race);
|
||||
when(race.getBoatClass()).thenReturn(boatClass);
|
||||
when(race.getCompetitors()).thenReturn(Collections.emptySet());
|
||||
finder = new CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(trackedRace);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneProbabilityForEmptyList() {
|
||||
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> emptyDistancesToStartLineOfOtherCompetitors = new ArrayList<>();
|
||||
final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(emptyDistancesToStartLineOfOtherCompetitors,
|
||||
/* startIsLine */ true);
|
||||
assertEquals(1.0, probability, 0.00001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighProbabilityForCloseProximityAndNoLine() {
|
||||
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors = new ArrayList<>();
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(5.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(8.0, null));
|
||||
final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
|
||||
/* startIsLine */ false);
|
||||
assertTrue(probability > 0.95);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighProbabilityForCloseProximityExceptOneAndNoLine() {
|
||||
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors = new ArrayList<>();
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(5.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(8.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(1.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.5, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.2, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2739.0, null));
|
||||
final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
|
||||
/* startIsLine */ false);
|
||||
assertTrue("Expected probability to exceed 55% but got "+probability, probability > 0.55);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowProbabilityForMostFarAwayAndNoLine() {
|
||||
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors = new ArrayList<>();
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(50.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(250.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(120.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(200.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(300.0, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.5, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.2, null));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2739.0, null));
|
||||
final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
|
||||
/* startIsLine */ false);
|
||||
assertTrue("Expected probability to be below 10% but got "+probability, probability <= 0.1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighProbabilityForLateStartAndStartIsLine() {
|
||||
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors = new ArrayList<>();
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(100.0, -80.));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(120.0, -82.));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(130.0, -81.));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(90.0, -79.));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(140.0, -75.));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(150, -88.));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(135, -77.));
|
||||
distancesToStartLineOfOtherCompetitors.add(createDistancePair(20.0, -10.)); // another late starter just across the line
|
||||
final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
|
||||
/* startIsLine */ true);
|
||||
assertTrue("Expected probability to be above 85% but got "+probability, probability >= 0.85);
|
||||
}
|
||||
|
||||
private AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine createDistancePair(double absoluteDistanceToLine,
|
||||
Double signedXTEDistanceFromLine) {
|
||||
return new AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine(
|
||||
new MeterDistance(absoluteDistanceToLine), signedXTEDistanceFromLine == null ? null : new MeterDistance(signedXTEDistanceFromLine));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -54,7 +54,7 @@ public abstract class AbstractTracTracLiveTest extends StoredTrackBasedTest {
|
||||
private IEventSubscriber eventSubscriber;
|
||||
private IRaceSubscriber raceSubscriber;
|
||||
private final Collection<Receiver> receivers;
|
||||
|
||||
|
||||
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(3 * 60 * 1000);
|
||||
|
||||
protected AbstractTracTracLiveTest() throws URISyntaxException, MalformedURLException {
|
||||
|
||||
Executable → Regular
+5
-7
@@ -256,7 +256,7 @@ public class TrackTest {
|
||||
protected Pair<GPSFixMoving, Speed> computeMaxSpeed(TimePoint from, TimePoint to) {
|
||||
Pair<GPSFixMoving, Speed> result = super.computeMaxSpeed(from, to);
|
||||
try {
|
||||
Thread.sleep(500); // just wait a bit; can't lock really because that would cause a deadlock
|
||||
Thread.sleep(1000); // just wait a bit; can't lock really because that would cause a deadlock
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -280,11 +280,9 @@ public class TrackTest {
|
||||
};
|
||||
}
|
||||
};
|
||||
new Thread(()->{
|
||||
GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(
|
||||
GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(
|
||||
1, new DegreeBearingImpl(123)));
|
||||
track.addGPSFix(fix1);
|
||||
}).start();
|
||||
track.addGPSFix(fix1);
|
||||
// The following getMaximumSpeedOverGround call will trigger a computeMaxSpeed(...) and a cache(...) call
|
||||
new Thread(()->
|
||||
assertEquals(1., track.getMaximumSpeedOverGround(new MillisecondsTimePoint(0), new MillisecondsTimePoint(7200000)).
|
||||
@@ -319,8 +317,8 @@ public class TrackTest {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).start();
|
||||
cacheBarrier.await(10, TimeUnit.SECONDS);
|
||||
cacheDone.await(10, TimeUnit.SECONDS);
|
||||
cacheBarrier.await(20, TimeUnit.SECONDS);
|
||||
cacheDone.await(20, TimeUnit.SECONDS);
|
||||
testDone.await();
|
||||
assertEquals(2., maxSpeed[0], 0.001);
|
||||
}
|
||||
|
||||
+49
-17
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.test.markpassing;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -11,6 +12,7 @@ import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -62,7 +64,7 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
|
||||
simpleName = getClass().getSimpleName();
|
||||
}
|
||||
|
||||
private void setUp(String raceNumber) throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
|
||||
protected void setUp(String raceNumber) throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
|
||||
super.setUp();
|
||||
URI storedUri = new URI("file:///"
|
||||
+ new File("resources/" + getFileName() + raceNumber + ".mtb").getCanonicalPath().replace('\\', '/'));
|
||||
@@ -83,9 +85,26 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
|
||||
givenMarkPasses.put(wp, markPassing);
|
||||
}
|
||||
givenPasses.put(c, givenMarkPasses);
|
||||
// now clear the mark passings in the TrackedRace for competitor c:
|
||||
getTrackedRace().updateMarkPassings(c, Collections.emptySet());
|
||||
}
|
||||
}
|
||||
|
||||
protected Iterable<Waypoint> getWaypoints() {
|
||||
return waypoints;
|
||||
}
|
||||
|
||||
protected Competitor getCompetitorByName(final String name) {
|
||||
Competitor result = null;
|
||||
for (Competitor c : getTrackedRace().getRace().getCompetitors()) {
|
||||
if (c.getName().equals(name)) {
|
||||
result = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract String getFileName();
|
||||
|
||||
protected void testRace(String raceNumber) throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
|
||||
@@ -210,29 +229,19 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
|
||||
}
|
||||
|
||||
private void testMiddleOfRace(int zeroBasedIndexOfLastWaypointToBePassed) {
|
||||
int mistakes = 0;
|
||||
CandidateFinder finder = new CandidateFinderImpl(getTrackedRace());
|
||||
CandidateChooser chooser = new CandidateChooserImpl(getTrackedRace());
|
||||
int mistakes = 0;
|
||||
Waypoint lastWaypointToBePassed = waypoints.get(zeroBasedIndexOfLastWaypointToBePassed);
|
||||
Waypoint wayPointAfterwards = waypoints.get(zeroBasedIndexOfLastWaypointToBePassed + 1);
|
||||
for (Competitor c : getRace().getCompetitors()) {
|
||||
MarkPassing markPassingAfter = givenPasses.get(c).get(wayPointAfterwards);
|
||||
if (markPassingAfter != null) {
|
||||
if (givenPasses.get(c).get(lastWaypointToBePassed) != null && givenPasses.get(c).get(wayPointAfterwards) != null) {
|
||||
TimePoint beforeNextPassing = markPassingAfter.getTimePoint().minus(20000);
|
||||
List<GPSFix> fixes = new ArrayList<GPSFix>();
|
||||
try {
|
||||
getTrackedRace().getTrack(c).lockForRead();
|
||||
for (GPSFixMoving fix : getTrackedRace().getTrack(c).getFixes()) {
|
||||
if (fix.getTimePoint().before(beforeNextPassing)) {
|
||||
fixes.add(fix);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
getTrackedRace().getTrack(c).unlockAfterRead();
|
||||
}
|
||||
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> f = finder.getCandidateDeltas(c, fixes);
|
||||
chooser.calculateMarkPassDeltas(c, f.getA(), f.getB());
|
||||
if (givenPasses.get(c).get(lastWaypointToBePassed) != null) {
|
||||
final TimePoint lastGivenPassingToDetectAt = givenPasses.get(c).get(lastWaypointToBePassed).getTimePoint();
|
||||
// add fixes up to 50% into the leg starting after the last passing to be detected
|
||||
final TimePoint beforeNextPassing = lastGivenPassingToDetectAt.plus(lastGivenPassingToDetectAt.until(markPassingAfter.getTimePoint()).times(0.5));
|
||||
calculateMarkPassingsForPartialTrack(c, beforeNextPassing, finder, chooser);
|
||||
boolean gotPassed = true;
|
||||
boolean gotOther = false;
|
||||
System.out.println(c);
|
||||
@@ -243,10 +252,16 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
|
||||
if (waypoints.indexOf(w) <= zeroBasedIndexOfLastWaypointToBePassed) {
|
||||
if ((old == null) != (newm == null)) {
|
||||
gotPassed = false;
|
||||
fail("Waypoint "+w+" was "+(old == null?"not ":"")+"passed by "+c+" originally"
|
||||
+(old==null?"":": "+old)+"; we detected it "+(newm==null?"not ":"")+"having been passed"+
|
||||
(newm == null ? "" : (": "+newm)));
|
||||
}
|
||||
} else {
|
||||
if (w != wayPointAfterwards && newm != null) {
|
||||
gotOther = true;
|
||||
fail("Received a park passing "+newm+" for the "+waypoints.indexOf(w)+
|
||||
"th waypoint "+w+" by "+c+" although only up to "+
|
||||
zeroBasedIndexOfLastWaypointToBePassed +" were to be reported");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,6 +275,23 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
|
||||
|
||||
}
|
||||
|
||||
protected void calculateMarkPassingsForPartialTrack(Competitor c, final TimePoint upToTimePoint,
|
||||
CandidateFinder finder, CandidateChooser chooser) {
|
||||
List<GPSFix> fixes = new ArrayList<GPSFix>();
|
||||
try {
|
||||
getTrackedRace().getTrack(c).lockForRead();
|
||||
for (GPSFixMoving fix : getTrackedRace().getTrack(c).getFixes()) {
|
||||
if (fix.getTimePoint().before(upToTimePoint)) {
|
||||
fixes.add(fix);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
getTrackedRace().getTrack(c).unlockAfterRead();
|
||||
}
|
||||
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> f = finder.getCandidateDeltas(c, fixes);
|
||||
chooser.calculateMarkPassDeltas(c, f.getA(), f.getB());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void createXML() throws IOException {
|
||||
double accuracy = correct / totalPasses;
|
||||
|
||||
+6
-4
@@ -17,6 +17,7 @@ import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Sideline;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.BoatClassImpl;
|
||||
import com.sap.sailing.domain.base.impl.BoatImpl;
|
||||
import com.sap.sailing.domain.base.impl.CompetitorImpl;
|
||||
import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl;
|
||||
import com.sap.sailing.domain.base.impl.CourseAreaImpl;
|
||||
@@ -30,6 +31,7 @@ import com.sap.sailing.domain.base.impl.WaypointImpl;
|
||||
import com.sap.sailing.domain.common.PassingInstruction;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
|
||||
import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore;
|
||||
@@ -43,9 +45,10 @@ import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
public class AbstractMockedRaceMarkPassingTest {
|
||||
protected Competitor ron = new CompetitorImpl("Ron", "Ron", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
|
||||
protected Competitor tom = new CompetitorImpl("Tom", "Tom", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
|
||||
protected Competitor ben = new CompetitorImpl("Ben", "Ben", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
|
||||
final BoatClassImpl boatClass = new BoatClassImpl("boat", true, "boat", new MeterDistance(10), new MeterDistance(5), null);
|
||||
protected Competitor ron = new CompetitorImpl("Ron", "Ron", null, null, null, null, new BoatImpl("boat", boatClass, "boot"), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
|
||||
protected Competitor tom = new CompetitorImpl("Tom", "Tom", null, null, null, null, new BoatImpl("boat", boatClass, "boot"), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
|
||||
protected Competitor ben = new CompetitorImpl("Ben", "Ben", null, null, null, null, new BoatImpl("boat", boatClass, "boot"), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
|
||||
|
||||
protected Mark m = new MarkImpl("Mark");
|
||||
protected Mark gate1 = new MarkImpl("Gate1");
|
||||
@@ -64,7 +67,6 @@ public class AbstractMockedRaceMarkPassingTest {
|
||||
Waypoint w4 = new WaypointImpl(m, PassingInstruction.Starboard);
|
||||
Waypoint w5 = new WaypointImpl(cp, PassingInstruction.Line);
|
||||
waypoints = Arrays.asList(w1, w2, w3, w4, w5);
|
||||
final BoatClassImpl boatClass = new BoatClassImpl("boat", true);
|
||||
Regatta r = new RegattaImpl(RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass,
|
||||
/*startDate*/ null, /*endDate*/ null, Arrays.asList(new SeriesImpl("Series", true, Arrays.asList(new FleetImpl("fleet")),
|
||||
new ArrayList<String>(), null)), true, new HighPoint(), "ID", new CourseAreaImpl("area", new UUID(5, 5)), OneDesignRankingMetric::new);
|
||||
|
||||
-1
@@ -153,7 +153,6 @@ public class FlorianopolisMarkPassingTest extends AbstractMarkPassingTest {
|
||||
*
|
||||
* @Test public void testRace26() throws IOException, InterruptedException, URISyntaxException { testRace(26); }
|
||||
*/
|
||||
|
||||
@Override
|
||||
protected String getExpectedEventName() {
|
||||
return "ESS Florianopolis 2013";
|
||||
|
||||
+19
-3
@@ -7,17 +7,33 @@ import java.text.ParseException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLog;
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartProcedureChangedEventImpl;
|
||||
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
import com.tractrac.model.lib.api.event.CreateModelException;
|
||||
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
|
||||
|
||||
public class Kiel2013505Test extends AbstractMarkPassingTest {
|
||||
|
||||
public Kiel2013505Test() throws MalformedURLException, URISyntaxException {
|
||||
public Kiel2013505Test() throws MalformedURLException, URISyntaxException {
|
||||
super();
|
||||
}
|
||||
|
||||
//Takes longer than 2 minutes, so the rule in AbstractTracTracLiveTest needs to be deactivated first.
|
||||
|
||||
/**
|
||||
* Marks the race as using a gate start
|
||||
*/
|
||||
@Override
|
||||
protected void setUp(String raceNumber) throws IOException, InterruptedException, URISyntaxException,
|
||||
ParseException, SubscriberInitializationException, CreateModelException {
|
||||
super.setUp(raceNumber);
|
||||
final RaceLog raceLog = new RaceLogImpl("505 Race Log");
|
||||
getTrackedRace().attachRaceLog(raceLog);
|
||||
raceLog.add(new RaceLogStartProcedureChangedEventImpl(MillisecondsTimePoint.now(), new LogEventAuthorImpl("Me", 0), 0, RacingProcedureType.GateStart));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
|
||||
testRace("5");
|
||||
|
||||
+6
-6
@@ -45,7 +45,7 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark
|
||||
|
||||
@AfterClass
|
||||
public static void createXMLFile() throws IOException {
|
||||
MeasurementXMLFile performanceReport = new MeasurementXMLFile(MarkPassingCalculatorPerformanceTest.class);
|
||||
final MeasurementXMLFile performanceReport = new MeasurementXMLFile(MarkPassingCalculatorPerformanceTest.class);
|
||||
for (String key : result.keySet()) {
|
||||
MeasurementCase performanceReportCase = performanceReport.addCase(key);
|
||||
performanceReportCase.addMeasurement(new Measurement(key, result.get(key)));
|
||||
@@ -72,7 +72,7 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark
|
||||
|
||||
@Test
|
||||
public void testChooser() {
|
||||
long time = timeToAddCandidatesToChooser(500, 1, 25);
|
||||
final long time = timeToAddCandidatesToChooser(500, 1, 25);
|
||||
System.out.println(time);
|
||||
result.put("ChooserPerformance", time);
|
||||
assertTrue(time < 5000);
|
||||
@@ -96,16 +96,16 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark
|
||||
}
|
||||
|
||||
private CandidateImpl randomCan() {
|
||||
int id = rnd.nextInt(3);
|
||||
final int id = rnd.nextInt(3);
|
||||
return new CandidateImpl(id + 1, new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000)),
|
||||
0.5 + 0.5 * rnd.nextDouble(), Util.get(race.getRace().getCourse().getWaypoints(), id));
|
||||
}
|
||||
|
||||
private GPSFixMoving rndFix() {
|
||||
DegreePosition position = new DegreePosition(0.001 - rnd.nextDouble() * 0.001,
|
||||
final DegreePosition position = new DegreePosition(0.001 - rnd.nextDouble() * 0.001,
|
||||
0.0002 - rnd.nextDouble() * 0.0004);
|
||||
TimePoint p = new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000));
|
||||
SpeedWithBearing speed = new KnotSpeedWithBearingImpl(rnd.nextInt(11), new DegreeBearingImpl(rnd.nextInt(360)));
|
||||
final TimePoint p = new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000));
|
||||
final SpeedWithBearing speed = new KnotSpeedWithBearingImpl(rnd.nextInt(11), new DegreeBearingImpl(rnd.nextInt(360)));
|
||||
return new GPSFixMovingImpl(position, p, speed);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -9,6 +9,7 @@ import java.util.List;
|
||||
import java.util.NavigableSet;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.Ignore;
|
||||
|
||||
import com.sap.sailing.domain.base.Course;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
@@ -34,6 +35,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
* Tests tricky situation that can fail easily. Created with help of http://itouchmap.com/latlong.html
|
||||
*
|
||||
*/
|
||||
@Ignore
|
||||
public class MarkPassingWhiteBoxTest extends AbstractMockedRaceMarkPassingTest {
|
||||
|
||||
@Test
|
||||
@@ -78,7 +80,6 @@ public class MarkPassingWhiteBoxTest extends AbstractMockedRaceMarkPassingTest {
|
||||
chooser.suppressMarkPassings(ron, 1);
|
||||
NavigableSet<MarkPassing> markPassingsAfterSupressing = race.getMarkPassings(ron);
|
||||
assertEquals(0, markPassingsAfterSupressing.size());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+95
-2
@@ -1,12 +1,31 @@
|
||||
package com.sap.sailing.domain.test.markpassing;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.markpassingcalculation.CandidateChooser;
|
||||
import com.sap.sailing.domain.markpassingcalculation.CandidateFinder;
|
||||
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
|
||||
import com.sap.sailing.domain.markpassingcalculation.impl.CandidateChooserImpl;
|
||||
import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sse.InvalidDateException;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sse.util.DateParser;
|
||||
import com.tractrac.model.lib.api.event.CreateModelException;
|
||||
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
|
||||
|
||||
@@ -18,8 +37,82 @@ public class StarIDM2013MarkPassingTest extends AbstractMarkPassingTest {
|
||||
|
||||
@Test
|
||||
public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException,
|
||||
SubscriberInitializationException, CreateModelException {
|
||||
testRace("5");
|
||||
SubscriberInitializationException, CreateModelException, InvalidDateException {
|
||||
// Race 5 is really broken on TracTrac's side. I've tried re-calculating the mark passing to no avail.
|
||||
// Now it's even worse, and only the windward mark passings and the start mark passings are recognized
|
||||
// half-way reasonably, but no mark passings seem to have been detected for subsequent waypoints.
|
||||
// To still use this race for testing purposes, let's do spot checks based on manually identified mark
|
||||
// passings, based on https://www.sapsailing.com/gwt/RaceBoard.html?eventId=a43b8f96-1e77-448a-9b8a-0f586f1bb18f&leaderboardName=Star%20IDM%202013&leaderboardGroupName=Star%20IDM%202013&raceName=Race%205&viewShowMapControls=true&viewShowNavigationPanel=true®attaName=IDM%20Starboot%202013%20%28Star%29
|
||||
setUp("5");
|
||||
synchronized (getSemaphor()) {
|
||||
while (!isStoredDataLoaded()) {
|
||||
getSemaphor().wait();
|
||||
}
|
||||
}
|
||||
testWholeRace5();
|
||||
testOnlyStartOfRace5();
|
||||
}
|
||||
|
||||
private void testWholeRace5() throws InvalidDateException {
|
||||
new MarkPassingCalculator(getTrackedRace(), false); // do our calculation, injecting into TrackedRace
|
||||
final Competitor GER7897 = getCompetitorByName("Tusch, Frank,Winkelmann, Sven");
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:10+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:57:12+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:06:32+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:15:01+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:21:17+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:29:18+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:36:36+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:38:09+0200"));
|
||||
|
||||
final Competitor GER7616 = getCompetitorByName("Seefelder, Jürgen,Seefelder, N"); // starts late
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:57+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:59:05+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:07:32+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:16:40+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:23:26+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:33:01+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:39:25+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:40:51+0200"));
|
||||
|
||||
final Competitor GER8340 = getCompetitorByName("Griese, Achim,Marcour, Michael"); // starts rather early
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:04+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:56:57+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:06:33+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:14:31+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:20:29+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:28:34+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:35:52+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:37:22+0200"));
|
||||
}
|
||||
|
||||
private void testOnlyStartOfRace5() throws InvalidDateException {
|
||||
// clear mark passings
|
||||
for (Competitor c : getRace().getCompetitors()) {
|
||||
getTrackedRace().updateMarkPassings(c, Collections.emptySet());
|
||||
}
|
||||
CandidateFinder finder = new CandidateFinderImpl(getTrackedRace());
|
||||
CandidateChooser chooser = new CandidateChooserImpl(getTrackedRace());
|
||||
final TimePoint afterStart = new MillisecondsTimePoint(DateParser.parse("2013-05-04T14:52:30+0200"));
|
||||
for (Competitor c : getRace().getCompetitors()) {
|
||||
calculateMarkPassingsForPartialTrack(c, afterStart, finder, chooser);
|
||||
}
|
||||
final Competitor GER7897 = getCompetitorByName("Tusch, Frank,Winkelmann, Sven");
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:10+0200"));
|
||||
final Competitor GER7616 = getCompetitorByName("Seefelder, Jürgen,Seefelder, N"); // starts late
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:57+0200"));
|
||||
final Competitor GER8340 = getCompetitorByName("Griese, Achim,Marcour, Michael"); // starts rather early
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:04+0200"));
|
||||
}
|
||||
|
||||
private void assertMarkPassing(Competitor competitor, Waypoint waypoint, Date when) {
|
||||
final MarkPassing markPassing = getTrackedRace().getMarkPassing(competitor, waypoint);
|
||||
if (markPassing == null) {
|
||||
fail("Expected mark passing for "+competitor+" and "+waypoint+" around "+when+" but no mark passing found");
|
||||
}
|
||||
final Duration offset = new MillisecondsTimePoint(when).until(markPassing.getTimePoint()).abs();
|
||||
assertTrue("Expected mark passing for "+competitor+" and "+waypoint+" around "+when+
|
||||
" but found one at "+markPassing.getTimePoint()+" which is "+offset+" off.", offset.asSeconds() < 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+5
@@ -100,6 +100,11 @@ public class MockedTrackedRace implements DynamicTrackedRace {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimePoint getStartOfRace(boolean inferred) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<TrackedLeg> getTrackedLegs() {
|
||||
return null;
|
||||
|
||||
+5
@@ -111,6 +111,11 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimePoint getStartOfRace(boolean inferred) {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimePoint getEndOfRace() {
|
||||
return null;
|
||||
|
||||
@@ -792,6 +792,6 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
public void deregisterCompetitors(Iterable<Competitor> competitors) {
|
||||
RegattaLog regattaLog = getRegattaLike().getRegattaLog();
|
||||
CompetitorDeregistrator<RegattaLog, RegattaLogEvent, RegattaLogEventVisitor> deregisterer = new CompetitorDeregistrator<>(regattaLog, competitors, regattaLogEventAuthorForRegatta);
|
||||
deregisterer.analyze();
|
||||
deregisterer.deregister(deregisterer.analyze());
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -224,6 +224,6 @@ public abstract class AbstractLeaderboardImpl extends AbstractSimpleLeaderboardI
|
||||
public void deregisterCompetitors(Iterable<Competitor> competitors) {
|
||||
RegattaLog regattaLog = getRegattaLike().getRegattaLog();
|
||||
CompetitorDeregistrator<RegattaLog, RegattaLogEvent, RegattaLogEventVisitor> deregisterer = new CompetitorDeregistrator<>(regattaLog, competitors, regattaLogEventAuthorForAbstractLeaderboard);
|
||||
deregisterer.analyze();
|
||||
deregisterer.deregister(deregisterer.analyze());
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -231,8 +231,7 @@ public class MarkPassingCalculator {
|
||||
@Override
|
||||
public void run() {
|
||||
logger.finer("Calculating MarkPassings for " + c + " (" + Util.size(fixes) + " new fixes)");
|
||||
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> candidateDeltas = finder.getCandidateDeltas(c,
|
||||
fixes);
|
||||
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> candidateDeltas = finder.getCandidateDeltas(c, fixes);
|
||||
logger.finer("Received " + Util.size(candidateDeltas.getA()) + " new Candidates and will remove "
|
||||
+ Util.size(candidateDeltas.getB()) + " old Candidates for " + c);
|
||||
chooser.calculateMarkPassDeltas(c, candidateDeltas.getA(), candidateDeltas.getB());
|
||||
|
||||
+81
-47
@@ -25,10 +25,12 @@ import com.sap.sailing.domain.markpassingcalculation.CandidateChooser;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.TrackedLeg;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.impl.MillisecondsDurationImpl;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
/**
|
||||
@@ -44,9 +46,28 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
*
|
||||
*/
|
||||
public class CandidateChooserImpl implements CandidateChooser {
|
||||
// TODO what is the meaning of this constant?
|
||||
private static final int MILLISECONDS_BEFORE_STARTTIME = 5000;
|
||||
private final static double MINIMUM_PROBABILITY = 1 - Edge.getPenaltyForSkipping();
|
||||
/**
|
||||
* Distance ratios of actual distance traveled and leg length above this threshold will receive
|
||||
* penalties on their probability. Ratios below 1.0 receive the ratio as the penalty.
|
||||
* See {@link #getDistanceEstimationBasedProbability(Competitor, Candidate, Candidate)}.
|
||||
*/
|
||||
final double MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH = 2.0;
|
||||
|
||||
/**
|
||||
* Start mark passings will be considered this much before the actual race start. The race start
|
||||
* as identified by {@link TrackedRace#getStartOfRace()} is therefore {@link #EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME}
|
||||
* after {@link #raceStartTime}.
|
||||
*/
|
||||
private static final Duration EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME = new MillisecondsDurationImpl(5000);
|
||||
|
||||
/**
|
||||
* The duration after which a start mark passing's probability is considered only 50%. A perfect start mark
|
||||
* passing happening exactly at the race start time has time-wise probability of 1.0. Another delay of this much
|
||||
* lets the probability drop to 1/3, and so on.
|
||||
*/
|
||||
private static final Duration DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES = Duration.ONE_MINUTE;
|
||||
|
||||
private final static double MINIMUM_PROBABILITY = Edge.getPenaltyForSkipping();
|
||||
|
||||
private static final Logger logger = Logger.getLogger(CandidateChooserImpl.class.getName());
|
||||
|
||||
@@ -55,14 +76,20 @@ public class CandidateChooserImpl implements CandidateChooser {
|
||||
private Map<Competitor, Set<Candidate>> candidates = new HashMap<>();
|
||||
private Map<Competitor, NavigableSet<Candidate>> fixedPassings = new HashMap<>();
|
||||
private Map<Competitor, Integer> suppressedPassings = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Set to {@link #EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME} milliseconds before the actual race start,
|
||||
* in case the actual start of race is known; {@code null} otherwise.
|
||||
*/
|
||||
private TimePoint raceStartTime;
|
||||
private final WaypointPositionAndDistanceCache waypointPositionAndDistanceCache;
|
||||
|
||||
/**
|
||||
* An artificial proxy candidate that comes before the start mark passing. Its time point is set to
|
||||
* {@link #MILLISECONDS_BEFORE_STARTTIME} milliseconds before the race start time or <code>null</code>
|
||||
* {@link #EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME} milliseconds before the race start time or <code>null</code>
|
||||
* in case the race start time is not known.
|
||||
*/
|
||||
|
||||
private final CandidateWithSettableTime start;
|
||||
private final CandidateWithSettableWaypointIndex end;
|
||||
private final DynamicTrackedRace race;
|
||||
@@ -70,7 +97,9 @@ public class CandidateChooserImpl implements CandidateChooser {
|
||||
public CandidateChooserImpl(DynamicTrackedRace race) {
|
||||
this.race = race;
|
||||
waypointPositionAndDistanceCache = new WaypointPositionAndDistanceCache(race, Duration.ONE_MINUTE);
|
||||
raceStartTime = race.getStartOfRace() != null ? race.getStartOfRace().minus(MILLISECONDS_BEFORE_STARTTIME) : null;
|
||||
final TimePoint startOfRaceWithoutInference = race.getStartOfRace(/* inferred */ false);
|
||||
raceStartTime = startOfRaceWithoutInference != null ? startOfRaceWithoutInference.
|
||||
minus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME) : null;
|
||||
start = new CandidateWithSettableTime(/* Index */0, raceStartTime, /* Probability */1, /* Waypoint */null);
|
||||
end = new CandidateWithSettableWaypointIndex(race.getRace().getCourse().getNumberOfWaypoints() + 1, /* TimePoint */null,
|
||||
/* Probability */1, /* Waypoint */null);
|
||||
@@ -78,7 +107,16 @@ public class CandidateChooserImpl implements CandidateChooser {
|
||||
List<Candidate> startAndEnd = Arrays.asList(start, end);
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
candidates.put(c, Collections.synchronizedSet(new TreeSet<Candidate>()));
|
||||
currentMarkPasses.put(c, new HashMap<Waypoint, MarkPassing>());
|
||||
final HashMap<Waypoint, MarkPassing> currentMarkPassesForCompetitor = new HashMap<Waypoint, MarkPassing>();
|
||||
currentMarkPasses.put(c, currentMarkPassesForCompetitor);
|
||||
// in case the tracked race already has mark passings, e.g., from another mark passing calculator,
|
||||
// ensure consistency of the currentMarkPasses map with the TrackedRace:
|
||||
for (final Waypoint w : race.getRace().getCourse().getWaypoints()) {
|
||||
final MarkPassing mp = race.getMarkPassing(c, w);
|
||||
if (mp != null) {
|
||||
currentMarkPassesForCompetitor.put(w, mp);
|
||||
}
|
||||
}
|
||||
TreeSet<Candidate> fixedPasses = new TreeSet<Candidate>(new Comparator<Candidate>() {
|
||||
@Override
|
||||
public int compare(Candidate o1, Candidate o2) {
|
||||
@@ -106,10 +144,10 @@ public class CandidateChooserImpl implements CandidateChooser {
|
||||
|
||||
@Override
|
||||
public void calculateMarkPassDeltas(Competitor c, Iterable<Candidate> newCans, Iterable<Candidate> oldCans) {
|
||||
TimePoint startOfRace = race.getStartOfRace();
|
||||
final TimePoint startOfRace = race.getStartOfRace(/* inference */ false);
|
||||
if (startOfRace != null) {
|
||||
if (raceStartTime == null || !startOfRace.minus(MILLISECONDS_BEFORE_STARTTIME).equals(raceStartTime)) {
|
||||
raceStartTime = startOfRace.minus(MILLISECONDS_BEFORE_STARTTIME);
|
||||
if (raceStartTime == null || !startOfRace.minus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME).equals(raceStartTime)) {
|
||||
raceStartTime = startOfRace.minus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME);
|
||||
List<Candidate> startList = new ArrayList<>();
|
||||
startList.add(start);
|
||||
for (Competitor com : candidates.keySet()) {
|
||||
@@ -223,15 +261,15 @@ public class CandidateChooserImpl implements CandidateChooser {
|
||||
// calculate the probability for the start being the start given its timing and multiply
|
||||
// with the estimation for the distance-based probability:
|
||||
final Duration timeGapBetweenStartOfRaceAndCandidateTimePoint = early.getTimePoint()
|
||||
.plus(MILLISECONDS_BEFORE_STARTTIME).until(late.getTimePoint());
|
||||
// Being MILLISECONDS_BEFORE_STARTTIME off means a probability of 1/2; being twice this time
|
||||
.plus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME).until(late.getTimePoint()).abs();
|
||||
// Being DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES off means a probability of 1/2; being twice this time
|
||||
// off means 1/3, and so on
|
||||
startTimingProbability = (double) MILLISECONDS_BEFORE_STARTTIME
|
||||
/ (double) (MILLISECONDS_BEFORE_STARTTIME + Math
|
||||
.abs(timeGapBetweenStartOfRaceAndCandidateTimePoint.asMillis()));
|
||||
startTimingProbability = DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES.divide(
|
||||
DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES.plus(
|
||||
timeGapBetweenStartOfRaceAndCandidateTimePoint));
|
||||
estimatedDistanceProbability = 1;
|
||||
} else {
|
||||
startTimingProbability = 0.1; // can't really tell how well the start time was matched when
|
||||
startTimingProbability = 1; // can't really tell how well the start time was matched when
|
||||
// we don't have a start candidate
|
||||
estimatedDistanceProbability = late == end ? 1 : getDistanceEstimationBasedProbability(c, early, late);
|
||||
}
|
||||
@@ -295,40 +333,40 @@ public class CandidateChooserImpl implements CandidateChooser {
|
||||
if (oneBasedIndexOfSuppressedWaypoint <= endOfFixedInterval.getOneBasedIndexOfWaypoint()) {
|
||||
endOfFixedInterval = end;
|
||||
}
|
||||
NavigableSet<Util.Pair<Edge, Double>> currentEdgesCheapestFirst = new TreeSet<>(new Comparator<Util.Pair<Edge, Double>>() {
|
||||
NavigableSet<Util.Pair<Edge, Double>> currentEdgesMoreLikelyFirst = new TreeSet<>(new Comparator<Util.Pair<Edge, Double>>() {
|
||||
@Override
|
||||
public int compare(Util.Pair<Edge, Double> o1, Util.Pair<Edge, Double> o2) {
|
||||
int result = o1.getB().compareTo(o2.getB());
|
||||
int result = o2.getB().compareTo(o1.getB());
|
||||
return result != 0 ? result : o1.getA().compareTo(o2.getA());
|
||||
}
|
||||
});
|
||||
Map<Candidate, Util.Pair<Candidate, Double>> candidateWithParentAndSmallestTotalCost = new HashMap<>();
|
||||
Map<Candidate, Util.Pair<Candidate, Double>> candidateWithParentAndHighestTotalProbability = new HashMap<>();
|
||||
int indexOfEndOfFixedInterval = endOfFixedInterval.getOneBasedIndexOfWaypoint();
|
||||
|
||||
boolean endFound = false;
|
||||
currentEdgesCheapestFirst.add(new Util.Pair<Edge, Double>(new Edge(new CandidateImpl(-1, null, /* estimated distance probability */ 1, null), startOfFixedInterval,
|
||||
0, race.getRace().getCourse().getNumberOfWaypoints()), 0.0));
|
||||
currentEdgesMoreLikelyFirst.add(new Util.Pair<Edge, Double>(new Edge(new CandidateImpl(-1, null, /* estimated distance probability */ 1, null), startOfFixedInterval,
|
||||
1, race.getRace().getCourse().getNumberOfWaypoints()), 1.0));
|
||||
while (!endFound) {
|
||||
Util.Pair<Edge, Double> cheapestEdgeWithCost = currentEdgesCheapestFirst.pollFirst();
|
||||
if (cheapestEdgeWithCost == null) {
|
||||
Util.Pair<Edge, Double> mostLikelyEdgeWithProbability = currentEdgesMoreLikelyFirst.pollFirst();
|
||||
if (mostLikelyEdgeWithProbability == null) {
|
||||
endFound = true;
|
||||
} else {
|
||||
Edge currentCheapestEdge = cheapestEdgeWithCost.getA();
|
||||
Double currentCheapestCost = cheapestEdgeWithCost.getB();
|
||||
Edge currentMostLikelyEdge = mostLikelyEdgeWithProbability.getA();
|
||||
Double currentHighestProbability = mostLikelyEdgeWithProbability.getB();
|
||||
// If the shortest path to this candidate is already known the new edge is not added.
|
||||
if (!candidateWithParentAndSmallestTotalCost.containsKey(currentCheapestEdge.getEnd())) {
|
||||
// The cheapest edge taking us to currentCheapestEdge.getEnd() is found. Remember it.
|
||||
candidateWithParentAndSmallestTotalCost.put(currentCheapestEdge.getEnd(), new Util.Pair<Candidate, Double>(
|
||||
currentCheapestEdge.getStart(), currentCheapestCost));
|
||||
if (!candidateWithParentAndHighestTotalProbability.containsKey(currentMostLikelyEdge.getEnd())) {
|
||||
// The most likely edge taking us to currentMostLikelyEdge.getEnd() is found. Remember it.
|
||||
candidateWithParentAndHighestTotalProbability.put(currentMostLikelyEdge.getEnd(), new Util.Pair<Candidate, Double>(
|
||||
currentMostLikelyEdge.getStart(), currentHighestProbability));
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.finest("Added "+ currentCheapestEdge + "as cheapest edge for " + c);
|
||||
logger.finest("Added "+ currentMostLikelyEdge + " as most likely edge for " + c);
|
||||
}
|
||||
endFound = currentCheapestEdge.getEnd() == endOfFixedInterval;
|
||||
endFound = currentMostLikelyEdge.getEnd() == endOfFixedInterval;
|
||||
if (!endFound) {
|
||||
// the end of the segment was not yet found; add edges leading away from
|
||||
// currentCheapestEdge.getEnd(), summing up their cost with the cost required
|
||||
// to reach currentCheapestEdge.getEnd()
|
||||
Set<Edge> edgesForNewCandidate = allCompetitorEdges.get(currentCheapestEdge.getEnd());
|
||||
// currentMostLikelyEdge.getEnd(), multiplying up their probabilities with the probability
|
||||
// of reaching currentMostLikelyEdge.getEnd()
|
||||
Set<Edge> edgesForNewCandidate = allCompetitorEdges.get(currentMostLikelyEdge.getEnd());
|
||||
if (edgesForNewCandidate != null) {
|
||||
for (Edge e : edgesForNewCandidate) {
|
||||
int oneBasedIndexOfEndOfEdge = e.getEnd().getOneBasedIndexOfWaypoint();
|
||||
@@ -336,7 +374,7 @@ public class CandidateChooserImpl implements CandidateChooser {
|
||||
// the next fixed mark passing
|
||||
if (oneBasedIndexOfEndOfEdge <= indexOfEndOfFixedInterval
|
||||
&& (oneBasedIndexOfEndOfEdge < oneBasedIndexOfSuppressedWaypoint || e.getEnd() == end)) {
|
||||
currentEdgesCheapestFirst.add(new Util.Pair<Edge, Double>(e, currentCheapestCost + e.getCost()));
|
||||
currentEdgesMoreLikelyFirst.add(new Util.Pair<Edge, Double>(e, currentHighestProbability * e.getProbability()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,10 +382,10 @@ public class CandidateChooserImpl implements CandidateChooser {
|
||||
}
|
||||
}
|
||||
}
|
||||
Candidate marker = candidateWithParentAndSmallestTotalCost.get(endOfFixedInterval).getA();
|
||||
Candidate marker = candidateWithParentAndHighestTotalProbability.get(endOfFixedInterval).getA();
|
||||
while (marker.getOneBasedIndexOfWaypoint() > 0) {
|
||||
mostLikelyCandidates.add(marker);
|
||||
marker = candidateWithParentAndSmallestTotalCost.get(marker).getA();
|
||||
marker = candidateWithParentAndHighestTotalProbability.get(marker).getA();
|
||||
}
|
||||
startOfFixedInterval = endOfFixedInterval;
|
||||
endOfFixedInterval = fixedPasses.higher(endOfFixedInterval);
|
||||
@@ -399,8 +437,8 @@ public class CandidateChooserImpl implements CandidateChooser {
|
||||
first = c1.getWaypoint();
|
||||
}
|
||||
final Waypoint second = c2.getWaypoint();
|
||||
Distance totalGreatCircleDistance = getApproximateTotalGreatCircleDistanceBetweenWaypoints(first, second, middleOfc1Andc2);
|
||||
Distance actualDistanceTraveled = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint());
|
||||
final Distance totalGreatCircleDistance = getApproximateTotalGreatCircleDistanceBetweenWaypoints(first, second, middleOfc1Andc2);
|
||||
final Distance actualDistanceTraveled = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint());
|
||||
result = getProbabilityOfActualDistanceGivenGreatCircleDistance(totalGreatCircleDistance, actualDistanceTraveled);
|
||||
return result;
|
||||
}
|
||||
@@ -418,23 +456,19 @@ public class CandidateChooserImpl implements CandidateChooser {
|
||||
* @return a number between 0 and 1 with 1 representing a "fair chance" that the actual distance sailed could have
|
||||
* been sailed for the given great circle distance; 1 is returned for actual distances being in the range of
|
||||
* 1..2 times the great circle distance. Actual distances outside this interval reduce probability linearly
|
||||
* for smaller distances (gradient 3.5) and varies with the square root for distances that exceed twice the
|
||||
* for smaller distances (gradient 0.5) and varies with the one over the ratio for distances that exceed twice the
|
||||
* great circle distance.
|
||||
*/
|
||||
private double getProbabilityOfActualDistanceGivenGreatCircleDistance(Distance totalGreatCircleDistance,
|
||||
Distance actualDistanceTraveled) {
|
||||
final double result;
|
||||
double differenceInMeters = actualDistanceTraveled.getMeters() - totalGreatCircleDistance.getMeters();
|
||||
double ratio = differenceInMeters / totalGreatCircleDistance.getMeters();
|
||||
final double ratio = actualDistanceTraveled.getMeters() / totalGreatCircleDistance.getMeters();
|
||||
// A smaller distance than great circle from mark to mark is very unlikely, somewhere between the distance
|
||||
// estimated and double that is likely and anything greater than that gradually becomes unlikely
|
||||
if (ratio < 0) {
|
||||
// TODO shouldn't these factors be constants in the class header for easy fine-tuning?
|
||||
result = 3.5 * ratio + 1;
|
||||
} else if (ratio > 1) {
|
||||
result = Math.sqrt((-ratio + 3) / 2);
|
||||
if (ratio <= MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH) {
|
||||
result = Math.min(1.0, ratio); // maximum probability is 1; use for the interval between 1 and MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH
|
||||
} else {
|
||||
result = 1;
|
||||
result = 1./(ratio-1.); // start at probability 1 for ratio==2
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+364
-60
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.markpassingcalculation.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@@ -35,6 +36,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.TimeRange;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
|
||||
/**
|
||||
* The standard implemantation of {@link CandidateFinder}. There are two kinds of {@link Candidate}s. First of all,
|
||||
@@ -49,22 +51,47 @@ import com.sap.sse.common.Util;
|
||||
public class CandidateFinderImpl implements CandidateFinder {
|
||||
|
||||
// The higher this is, the closer the fixes have to be to waypoint to become a Candidate
|
||||
private final int strictnessOfDistanceBasedProbability = 8;
|
||||
private final int STRICTNESS_OF_DISTANCE_BASED_PROBABILITY = 7;
|
||||
|
||||
// All of the penalties are multiplied onto the probability of a Candidate. A value of 0 excludes Candidates that do
|
||||
// not fit, a value of 1 imposes no penalty on each criteria
|
||||
private final double penaltyForWrongSide = 0.7;
|
||||
private final double penaltyForWrongDirection = 0.7;
|
||||
private final double penaltyForDistanceCandidates = 0.7;
|
||||
private static final double PENALTY_FOR_WRONG_SIDE = 0.8;
|
||||
|
||||
/**
|
||||
* The penalty factor multiplied with a candidate's probability in case the competitor passes the line of an XTE
|
||||
* candidate with passing instructions other than {@link PassingInstruction#Line} in the wrong direction, e.g.,
|
||||
* passing a single mark that is to be passed to port in the wrong direction.
|
||||
*/
|
||||
private static final double PENALTY_FOR_WRONG_DIRECTION = 0.7;
|
||||
|
||||
/**
|
||||
* The penalty factor multiplied with a candidate's probability in case the competitor passes an XTE candidate for a
|
||||
* {@link PassingInstruction#Line line} in the wrong direction, e.g., passing a start line the wrong way.
|
||||
*/
|
||||
private static final double PENALTY_FOR_LINE_PASSED_IN_WRONG_DIRECTION = 0.3;
|
||||
|
||||
/**
|
||||
* Normally, for each distance candidate there should also be a proper XTE candidate that also tells about the
|
||||
* direction in which the boat crosses the line. XTE candidates are more precise and get a penalty in case the competitor
|
||||
* crosses the virtual line in the wrong direction: {@link #PENALTY_FOR_WRONG_DIRECTION}. The penalty a competitor should
|
||||
* get for not triggering a proper XTE candidate and only producing a candidate by getting near a mark and then
|
||||
* moving away again should be penalized at least as badly as {@link #PENALTY_FOR_WRONG_DIRECTION}.
|
||||
*/
|
||||
private static final double PENALTY_FOR_DISTANCE_CANDIDATES = 0.9 * PENALTY_FOR_WRONG_DIRECTION;
|
||||
|
||||
private static final double WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START = 0.1;
|
||||
private static final double NUMBER_OF_HULL_LENGTHS_DISTANCE_FROM_START_AT_WHICH_WORST_PENALTY_APPLIES = 10;
|
||||
|
||||
private static final Logger logger = Logger.getLogger(CandidateFinderImpl.class.getName());
|
||||
|
||||
|
||||
private Map<Competitor, LinkedHashMap<GPSFix, Map<Waypoint, List<Distance>>>> distanceCache = new LinkedHashMap<>();
|
||||
private Map<Competitor, LinkedHashMap<GPSFix, Map<Waypoint, List<Distance>>>> xteCache = new LinkedHashMap<>();
|
||||
|
||||
private Map<Competitor, Map<Waypoint, Map<List<GPSFix>, Candidate>>> xteCandidates = new HashMap<>();
|
||||
private Map<Competitor, Map<Waypoint, Map<GPSFix, Candidate>>> distanceCandidates = new HashMap<>();
|
||||
private final DynamicTrackedRace race;
|
||||
private final double penaltyForSkipping = 1 - Edge.getPenaltyForSkipping();
|
||||
private final double penaltyForSkipping = Edge.getPenaltyForSkipping();
|
||||
private final Map<Waypoint, PassingInstruction> passingInstructions = new LinkedHashMap<>();
|
||||
private final Comparator<GPSFix> comp = new Comparator<GPSFix>() {
|
||||
@Override
|
||||
@@ -122,8 +149,7 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
TimePoint end = null;
|
||||
for (Entry<Mark, List<GPSFix>> fixes : markFixes.entrySet()) {
|
||||
for (GPSFix fix : fixes.getValue()) {
|
||||
TimeRange timePoints = race.getOrCreateTrack(fixes.getKey()).getEstimatedPositionTimePeriodAffectedBy(
|
||||
fix);
|
||||
TimeRange timePoints = race.getOrCreateTrack(fixes.getKey()).getEstimatedPositionTimePeriodAffectedBy(fix);
|
||||
TimePoint newStart = timePoints.from();
|
||||
TimePoint newEnd = timePoints.to();
|
||||
start = start == null || start.after(newStart) ? newStart : start;
|
||||
@@ -131,24 +157,28 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
}
|
||||
}
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
List<GPSFix> comFixes = new ArrayList<>();
|
||||
List<GPSFix> competitorFixes = new ArrayList<>();
|
||||
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = race.getTrack(c);
|
||||
GPSFix comFix = track.getFirstFixAtOrAfter(start);
|
||||
if (comFix != null) {
|
||||
if (end != null) {
|
||||
while (comFix != null && !comFix.getTimePoint().after(end)) {
|
||||
comFixes.add(comFix);
|
||||
competitorFixes.add(comFix);
|
||||
distanceCache.get(c).remove(comFix);
|
||||
xteCache.get(c).remove(comFix);
|
||||
comFix = track.getFirstFixAfter(comFix.getTimePoint());
|
||||
}
|
||||
} else {
|
||||
while (comFix != null) {
|
||||
comFixes.add(comFix);
|
||||
competitorFixes.add(comFix);
|
||||
distanceCache.get(c).remove(comFix);
|
||||
xteCache.get(c).remove(comFix);
|
||||
comFix = track.getFirstFixAfter(comFix.getTimePoint());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!comFixes.isEmpty()) {
|
||||
affectedFixes.put(c, comFixes);
|
||||
if (!competitorFixes.isEmpty()) {
|
||||
affectedFixes.put(c, competitorFixes);
|
||||
}
|
||||
}
|
||||
return affectedFixes;
|
||||
@@ -156,7 +186,6 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
|
||||
@Override
|
||||
public Util.Pair<Iterable<Candidate>, Iterable<Candidate>> getCandidateDeltas(Competitor c, Iterable<GPSFix> fixes) {
|
||||
|
||||
List<Candidate> newCans = new ArrayList<>();
|
||||
List<Candidate> wrongCans = new ArrayList<>();
|
||||
Course course = race.getRace().getCourse();
|
||||
@@ -293,7 +322,7 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
if (numberofMarks == 2) {
|
||||
instruction = PassingInstruction.Gate;
|
||||
} else if (numberofMarks == 1) {
|
||||
instruction = PassingInstruction.Port;
|
||||
instruction = PassingInstruction.Single_Unknown;
|
||||
} else {
|
||||
instruction = PassingInstruction.None;
|
||||
}
|
||||
@@ -370,6 +399,10 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
Boolean isCan = false;
|
||||
Candidate oldCan = null;
|
||||
Double probability = null;
|
||||
double sidePenalty = 1;
|
||||
Distance distance = null;
|
||||
Double startProbabilityBasedOnOtherCompetitors = null;
|
||||
double onCorrectSideOfWaypoint = 0.8;
|
||||
List<Distance> waypointDistances = fixDistances.get(w);
|
||||
List<Distance> waypointDistancesBefore = fixDistancesBefore.get(w);
|
||||
List<Distance> waypointDistancesAfter = fixDistancesAfter.get(w);
|
||||
@@ -394,12 +427,26 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
p = fix.getPosition();
|
||||
Double newProbability = getDistanceBasedProbability(w, t, dis);
|
||||
if (newProbability != null) {
|
||||
newProbability *= isOnCorrectSideOfWaypoint(w, p, t, portMark) ? penaltyForDistanceCandidates
|
||||
: penaltyForDistanceCandidates * penaltyForWrongSide;
|
||||
// FIXME why not generate the candidate here where we have all information at hand?
|
||||
final double newOnCorrectSideOfWaypointPenalty = getSidePenalty(w, p, t, portMark);
|
||||
newProbability *= newOnCorrectSideOfWaypointPenalty * PENALTY_FOR_DISTANCE_CANDIDATES;
|
||||
final Double newStartProbabilityBasedOnOtherCompetitors;
|
||||
if (isStartWaypoint(w)) {
|
||||
newStartProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t);
|
||||
if (newStartProbabilityBasedOnOtherCompetitors != null) {
|
||||
newProbability *= newStartProbabilityBasedOnOtherCompetitors;
|
||||
}
|
||||
} else {
|
||||
newStartProbabilityBasedOnOtherCompetitors = null;
|
||||
}
|
||||
if (newProbability > penaltyForSkipping
|
||||
&& (probability == null || newProbability > probability)) {
|
||||
isCan = true;
|
||||
probability = newProbability;
|
||||
sidePenalty = newOnCorrectSideOfWaypointPenalty;
|
||||
onCorrectSideOfWaypoint = newOnCorrectSideOfWaypointPenalty;
|
||||
distance = dis;
|
||||
startProbabilityBasedOnOtherCompetitors = newStartProbabilityBasedOnOtherCompetitors;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -411,8 +458,8 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
wasCan = true;
|
||||
}
|
||||
if (!wasCan && isCan) {
|
||||
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
|
||||
t, probability, w);
|
||||
Candidate newCan = new DistanceCandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
|
||||
t, probability, startProbabilityBasedOnOtherCompetitors, w, sidePenalty, distance);
|
||||
getDistanceCandidates(c, w).put(fix, newCan);
|
||||
result.getA().add(newCan);
|
||||
logger.finest("Added distance" + newCan.toString() + "for " + c);
|
||||
@@ -420,8 +467,8 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
getDistanceCandidates(c, w).remove(fix);
|
||||
result.getB().add(oldCan);
|
||||
} else if (wasCan && isCan && oldCan.getProbability() != probability) {
|
||||
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
|
||||
t, probability, w);
|
||||
Candidate newCan = new DistanceCandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
|
||||
t, probability, startProbabilityBasedOnOtherCompetitors, w, onCorrectSideOfWaypoint, distance);
|
||||
getDistanceCandidates(c, w).put(fix, newCan);
|
||||
result.getA().add(newCan);
|
||||
logger.finest("Added distance" + newCan.toString() + "for " + c);
|
||||
@@ -615,18 +662,245 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
* crossingInfo.getB()).getMeters()); } } } }
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@code xte1} and {@code xte2} are expected to have different signums or both be 0. The method
|
||||
* interpolates between the time points {@code t1} and {@code t2} according to where the XTE is
|
||||
* assumed to have crossed 0. The candidate is then generated for this interpolated time point.
|
||||
*/
|
||||
private Candidate createCandidate(Competitor c, double xte1, double xte2, TimePoint t1, TimePoint t2, Waypoint w,
|
||||
Boolean portMark) {
|
||||
long differenceInMillis = t2.asMillis() - t1.asMillis();
|
||||
double ratio = (Math.abs(xte1) / (Math.abs(xte1) + Math.abs(xte2)));
|
||||
TimePoint t = t1.plus((long) (differenceInMillis * ratio));
|
||||
Position p = race.getTrack(c).getEstimatedPosition(t, false);
|
||||
List<Distance> distances = calculateDistance(p, w, t);
|
||||
Distance d = portMark ? distances.get(0) : distances.get(1);
|
||||
double cost = getDistanceBasedProbability(w, t, d);
|
||||
cost = isOnCorrectSideOfWaypoint(w, p, t, portMark) ? cost : cost * penaltyForWrongSide;
|
||||
cost = passesInTheRightDirection(w, xte1, xte2, portMark) ? cost : cost * penaltyForWrongDirection;
|
||||
return new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t, cost, w);
|
||||
final long differenceInMillis = t2.asMillis() - t1.asMillis();
|
||||
final double ratio = (Math.abs(xte1) / (Math.abs(xte1) + Math.abs(xte2)));
|
||||
final TimePoint t = t1.plus((long) (differenceInMillis * ratio));
|
||||
final Position p = race.getTrack(c).getEstimatedPosition(t, false);
|
||||
final List<Distance> distances = calculateDistance(p, w, t);
|
||||
final Distance d = portMark ? distances.get(0) : distances.get(1);
|
||||
final double sidePenalty = getSidePenalty(w, p, t, portMark);
|
||||
double probability = getDistanceBasedProbability(w, t, d) * sidePenalty;
|
||||
final Double passesInTheRightDirectionProbability = passesInTheRightDirection(w, xte1, xte2, portMark);
|
||||
// null would mean "unknown"; no penalty for those cases
|
||||
probability = passesInTheRightDirectionProbability == null ? probability : probability * passesInTheRightDirectionProbability;
|
||||
final Double startProbabilityBasedOnOtherCompetitors;
|
||||
if (isStartWaypoint(w)) {
|
||||
// add a penalty for a start candidate if we don't know the start time, it's not a gate start and
|
||||
// at time point t the other competitors are largely not even close to the start waypoint;
|
||||
// this will make start candidates much more likely if many other competitors are also very close to the
|
||||
// start, and it will help ruling out those candidates where someone is practicing a start just for themselves
|
||||
startProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t);
|
||||
if (startProbabilityBasedOnOtherCompetitors != null) {
|
||||
probability *= startProbabilityBasedOnOtherCompetitors;
|
||||
}
|
||||
} else {
|
||||
startProbabilityBasedOnOtherCompetitors = null;
|
||||
}
|
||||
return new XTECandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t, probability,
|
||||
startProbabilityBasedOnOtherCompetitors, w, sidePenalty, passesInTheRightDirectionProbability);
|
||||
}
|
||||
|
||||
public static class AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine {
|
||||
private final Distance absoluteGeometricDistance;
|
||||
private final Distance signedProjectedDistance;
|
||||
public AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine(Distance absoluteGeometricDistance,
|
||||
Distance signedProjectedDistance) {
|
||||
super();
|
||||
this.absoluteGeometricDistance = absoluteGeometricDistance;
|
||||
this.signedProjectedDistance = signedProjectedDistance;
|
||||
}
|
||||
public Distance getAbsoluteGeometricDistance() {
|
||||
return absoluteGeometricDistance;
|
||||
}
|
||||
public Distance getSignedProjectedDistance() {
|
||||
return signedProjectedDistance;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine [absoluteGeometricDistance="
|
||||
+ absoluteGeometricDistance + ", signedProjectedDistance=" + signedProjectedDistance + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For a fixed line start it is very likely that all competitors pass the start waypoint at about the same time, or
|
||||
* that only a few competitors lag behind, e.g., because of start time penalties. This requires everyone to at least
|
||||
* be pretty close to the start waypoint at the start time, of if we're looking at a competitor starting late, the
|
||||
* field would at least have little variance and be consistently on the course side of the start line.
|
||||
* <p>
|
||||
*
|
||||
* Therefore, a start mark passing candidate is very likely when a large share of competitors is very close to the
|
||||
* start line. If the other competitors are not very close to the start line, a start may still be likely if the
|
||||
* others are on the course side and have similar distance to the start line, whereas it becomes pretty unlikely
|
||||
* when everyone is sailing around anywhere on the course or behind the line but not being in close proximity to the
|
||||
* line and not having similar distances to the start line while being on course side.
|
||||
*
|
||||
* <ul>
|
||||
* <li>check distance variance of other competitors; low variance could mean they started jointly</li>
|
||||
* <li>for start *line*, consider the side on which the other competitors are; don't rule out a late start if other
|
||||
* competitors have low distance variance and are on course side of the line</li>
|
||||
* <li>generally consider distance of other competitors to line</li>
|
||||
* <li>dampen "outliers" such as trackers forgotten in harbor or trackers not currently sending (80% of competitors
|
||||
* that look like they are starting seems like a good guess)</li>
|
||||
* </ul>
|
||||
*
|
||||
* Approach: compute signed distances to start line (positive meaning on course side). Sort by absolute distance and
|
||||
* compute a {@link #weight(double) weighted} average where the boats farthest away are considered less relevant than
|
||||
* the ones closer, leading to a natural outlier removal.<p>
|
||||
*
|
||||
* For the special case of a late starter, consider the weighted variance of the signed distance from the start line.
|
||||
* A low variance with a positive average (meaning on course side) indicates that everyone else may have started around the
|
||||
* same time, making this a probable late starter's candidate. Therefore, low variance with positive average shall increase
|
||||
* a probability that was low because of a large weighted average distance.
|
||||
*
|
||||
* @param t
|
||||
* the time point at which to consider the other competitors behavior relative to the start waypoint
|
||||
* @return {@code null} if the {@link #race} has a gate start or the start time is known; a probability between 0..1
|
||||
* (inclusive) where 0 means that based on all but {@code c}'s relation to the start line it seems
|
||||
* completely unlikely that a candidate for {@code c} at time {@code t} could have been a start candidate; 1
|
||||
* meaning that based on the other competitors' relation to the start line at time point {@code t} is seems
|
||||
* a fact that this must have been the start.
|
||||
*
|
||||
*/
|
||||
private Double getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(Competitor c, TimePoint t) {
|
||||
final Double result;
|
||||
if (race.getStartOfRace(/* inferred */ false) == null && race.isGateStart() != Boolean.TRUE) {
|
||||
final Waypoint start = race.getRace().getCourse().getFirstWaypoint();
|
||||
final Iterable<Pair<Position, Bearing>> crossingInformationForStart = getCrossingInformation(start, t);
|
||||
if (start == null) {
|
||||
result = 1.0;
|
||||
} else {
|
||||
// if the start waypoint's passing instructions are unknown, assume it's a line
|
||||
final boolean startIsLine = getPassingInstructions(start) == PassingInstruction.Line;
|
||||
final List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors = new ArrayList<>();
|
||||
for (final Competitor otherCompetitor : race.getRace().getCompetitors()) {
|
||||
if (otherCompetitor != c) {
|
||||
final DynamicGPSFixTrack<Competitor, GPSFixMoving> track = race.getTrack(otherCompetitor);
|
||||
if (track != null) {
|
||||
final Position estimatedPositionAtT = track.getEstimatedPosition(t, /* extrapolate */ true);
|
||||
final Distance otherCompetitorsDistanceToStartAtT = getMinDistanceOrNull(calculateDistance(estimatedPositionAtT, start, t));
|
||||
if (otherCompetitorsDistanceToStartAtT != null) {
|
||||
final Distance crossTrackError;
|
||||
if (startIsLine) {
|
||||
Pair<Position, Bearing> crossingInformationForStartLine = crossingInformationForStart.iterator().next();
|
||||
crossTrackError = estimatedPositionAtT.crossTrackError(crossingInformationForStartLine.getA(),
|
||||
crossingInformationForStartLine.getB());
|
||||
} else {
|
||||
crossTrackError = null;
|
||||
}
|
||||
distancesToStartLineOfOtherCompetitors.add(new
|
||||
AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine(otherCompetitorsDistanceToStartAtT, crossTrackError));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result = getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors, startIsLine);
|
||||
}
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method computes two probabilities of the candidate being a start: one based on the other competitors largely
|
||||
* being close to the start line, meaning a regular start where the competitor starts roughly at the same time as
|
||||
* everybody else; the other probability computed is based on the other competitors largely being in a similar
|
||||
* distance to the start line on the course side, suggesting that the competitor is starting late, but mostly
|
||||
* everybody else did start earlier and around the same time. The probability returned is the probability of
|
||||
* the start being the one <em>or</em> the other scenario which mathematically is represented by using the inverted
|
||||
* probabilities of the two start options, multiplying them and inverting the resulting probability.<p>
|
||||
*
|
||||
* Access needs to be protected to satisfy Maven-based test cases in com.sap.sailing.domain.test bundle which is not a fragment
|
||||
*/
|
||||
protected Double getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(
|
||||
final List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors, boolean startIsLine) {
|
||||
final Double result;
|
||||
// sort by the absolute distance to start line
|
||||
// boats within one hull length of the start line are great indicators that we're at the start:
|
||||
final Distance hullLength = race.getRace().getBoatClass().getHullLength();
|
||||
final Distance weightedAverageAbsoluteDistance;
|
||||
if (!distancesToStartLineOfOtherCompetitors.isEmpty()) {
|
||||
Collections.sort(distancesToStartLineOfOtherCompetitors, (a, b)->a.getAbsoluteGeometricDistance().compareTo(b.getAbsoluteGeometricDistance()));
|
||||
Distance weightedAbsoluteDistanceSum = Distance.NULL;
|
||||
int i=0;
|
||||
double weightSum = 0;
|
||||
for (final AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine d : distancesToStartLineOfOtherCompetitors) {
|
||||
final double weight = weight(((double) i)/distancesToStartLineOfOtherCompetitors.size());
|
||||
final Distance weightedDistance = d.getAbsoluteGeometricDistance().scale(weight);
|
||||
weightedAbsoluteDistanceSum = weightedAbsoluteDistanceSum.add(weightedDistance);
|
||||
weightSum += weight;
|
||||
i++;
|
||||
}
|
||||
weightedAverageAbsoluteDistance = weightedAbsoluteDistanceSum.scale(1. / weightSum);
|
||||
final double probabilityOfStartWithOthers = Math.max(WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START, Math.min(1.0,
|
||||
(1.-(1.-WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START)/NUMBER_OF_HULL_LENGTHS_DISTANCE_FROM_START_AT_WHICH_WORST_PENALTY_APPLIES
|
||||
* (weightedAverageAbsoluteDistance.divide(hullLength)-1))));
|
||||
if (startIsLine) {
|
||||
// Now look at the variance, particularly on course side: low variance may mean we are looking at a late starter
|
||||
// sort such that the boats with the least value (greatest negative amount) comes first and the last ~20% are largely ignored
|
||||
Collections.sort(distancesToStartLineOfOtherCompetitors, (a, b)->a.getSignedProjectedDistance().compareTo(b.getSignedProjectedDistance()));
|
||||
Distance weightedSignedDistanceSum = Distance.NULL;
|
||||
weightSum = 0;
|
||||
i=0;
|
||||
for (final AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine d : distancesToStartLineOfOtherCompetitors) {
|
||||
final double weight = weight(((double) i)/distancesToStartLineOfOtherCompetitors.size());
|
||||
weightedSignedDistanceSum = weightedSignedDistanceSum.add(d.getSignedProjectedDistance().scale(
|
||||
weight));
|
||||
weightSum += weight;
|
||||
i++;
|
||||
}
|
||||
final Distance weightedAverageSignedDistance = weightedSignedDistanceSum.scale(1. / weightSum);
|
||||
i=0;
|
||||
double signedDistanceVarianceSumInSquareMeters = 0;
|
||||
weightSum = 0;
|
||||
for (final AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine d : distancesToStartLineOfOtherCompetitors) {
|
||||
final double differenceFromWeightedAverageInMeters = d.getSignedProjectedDistance().getMeters() - weightedAverageSignedDistance.getMeters();
|
||||
final double weight = weight(((double) i)/distancesToStartLineOfOtherCompetitors.size());
|
||||
signedDistanceVarianceSumInSquareMeters += weight*differenceFromWeightedAverageInMeters*differenceFromWeightedAverageInMeters;
|
||||
weightSum += weight;
|
||||
i++;
|
||||
}
|
||||
final double weightedVarianceInSquareMeters = signedDistanceVarianceSumInSquareMeters / weightSum;
|
||||
final double weightedVarianceRatioToSquaredHullLength = weightedVarianceInSquareMeters / hullLength.getMeters() / hullLength.getMeters();
|
||||
final double probabilityOfLateStart = Math.max(WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START, Math.min(1.0,
|
||||
(1.-(1.-WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START)/NUMBER_OF_HULL_LENGTHS_DISTANCE_FROM_START_AT_WHICH_WORST_PENALTY_APPLIES
|
||||
* (weightedVarianceRatioToSquaredHullLength-1))));
|
||||
result = 1. - (1.-probabilityOfLateStart) * (1.-probabilityOfStartWithOthers);
|
||||
} else {
|
||||
result = probabilityOfStartWithOthers;
|
||||
}
|
||||
} else {
|
||||
// no distance for any other competitor from the start line was available
|
||||
result = 1.0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Idea: sort and cut off the worst 20%. We played with weight functions for averaging the distances such that after sorting them
|
||||
* in increasing order the smaller ones get greater weight and the "outlier" ones towards the end of the sorted set receive small
|
||||
* weights. Still, if even the "best" (least) distances are far away, the result will be a low probability.
|
||||
* A function that could be helpful is this: -atan(20*(x-0.8)) + Pi/2)/Pi. The 0.8 represents the 80% starting from which the
|
||||
* weight declines sharply. The factor of 20 represents how sharp the decline is. x is between 0 and 1, representing the start
|
||||
* and the end of the sorted distance collection, respectively. Outputs are between 0 and 1, representing the weight to assign
|
||||
* to a distance while averaging.
|
||||
*/
|
||||
private double weight(double relativePositionInDistanceCollectionInIncreasingOrder) {
|
||||
final double SHARPNESS_OF_DECLINE = 100;
|
||||
final double CENTER_OF_DECLINE = 0.8;
|
||||
return (-Math.atan(SHARPNESS_OF_DECLINE*(relativePositionInDistanceCollectionInIncreasingOrder-CENTER_OF_DECLINE))+Math.PI/2) / Math.PI;
|
||||
}
|
||||
|
||||
private Distance getMinDistanceOrNull(List<Distance> distances) {
|
||||
Distance result = null;
|
||||
for (Distance d : distances) {
|
||||
if (d != null && (result == null || result.compareTo(d) > 0)) {
|
||||
result = d;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isStartWaypoint(Waypoint w) {
|
||||
return race.getRace().getCourse().getFirstWaypoint() == w;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -635,32 +909,38 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
* to be negative. If the passing instructions are {@link PassingInstruction#Line}, it checks whether the boat
|
||||
* passed between the two marks.
|
||||
*/
|
||||
private boolean isOnCorrectSideOfWaypoint(Waypoint w, Position p, TimePoint t, boolean portMark) {
|
||||
boolean result = true;
|
||||
private double getSidePenalty(Waypoint w, Position p, TimePoint t, boolean portMark) {
|
||||
boolean isOnRightSide = true;
|
||||
Distance onWrongSide = new MeterDistance(0);
|
||||
PassingInstruction instruction = getPassingInstructions(w);
|
||||
if (instruction == PassingInstruction.Line) {
|
||||
List<Position> pos = new ArrayList<>();
|
||||
for (Mark m : w.getMarks()) {
|
||||
Position po = race.getOrCreateTrack(m).getEstimatedPosition(t, false);
|
||||
if (po == null) {
|
||||
return true;
|
||||
isOnRightSide = true;
|
||||
break;
|
||||
}
|
||||
pos.add(po);
|
||||
}
|
||||
if (pos.size() != 2){
|
||||
return true;
|
||||
if (pos.size() != 2) {
|
||||
isOnRightSide = true;
|
||||
}
|
||||
Bearing diff1 = pos.get(0).getBearingGreatCircle(p)
|
||||
.getDifferenceTo(pos.get(0).getBearingGreatCircle(pos.get(1)));
|
||||
Bearing diff2 = pos.get(1).getBearingGreatCircle(p)
|
||||
.getDifferenceTo(pos.get(1).getBearingGreatCircle(pos.get(0)));
|
||||
Position leftMarkPos = pos.get(0);
|
||||
Position rightMarkPos = pos.get(1);
|
||||
Bearing diff1 = leftMarkPos.getBearingGreatCircle(p)
|
||||
.getDifferenceTo(leftMarkPos.getBearingGreatCircle(rightMarkPos));
|
||||
Bearing diff2 = rightMarkPos.getBearingGreatCircle(p)
|
||||
.getDifferenceTo(rightMarkPos.getBearingGreatCircle(leftMarkPos));
|
||||
if (Math.abs(diff1.getDegrees()) > 90 || Math.abs(diff2.getDegrees()) > 90) {
|
||||
result = false;
|
||||
isOnRightSide = false;
|
||||
Distance leftDistance = p.getDistance(leftMarkPos);
|
||||
Distance rightDistance = p.getDistance(rightMarkPos);
|
||||
onWrongSide = leftDistance.getMeters() < rightDistance.getMeters() ? leftDistance : rightDistance;
|
||||
}
|
||||
|
||||
} else {
|
||||
Mark m = null;
|
||||
if (instruction == PassingInstruction.Port || instruction == PassingInstruction.Starboard
|
||||
if (instruction == PassingInstruction.Single_Unknown || instruction == PassingInstruction.Port || instruction == PassingInstruction.Starboard
|
||||
|| instruction == PassingInstruction.FixedBearing || instruction == PassingInstruction.Offset) {
|
||||
m = w.getMarks().iterator().next();
|
||||
} else if (instruction == PassingInstruction.Gate) {
|
||||
@@ -669,27 +949,48 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
}
|
||||
if (m != null) {
|
||||
Util.Pair<Position, Bearing> crossingInfo = Util.get(getCrossingInformation(w, t), portMark ? 0 : 1);
|
||||
result = p.crossTrackError(crossingInfo.getA(), crossingInfo.getB().add(new DegreeBearingImpl(90)))
|
||||
.getMeters() < 0;
|
||||
isOnRightSide = p.crossTrackError(crossingInfo.getA(), crossingInfo.getB().add(new DegreeBearingImpl(90))).getMeters() < 0;
|
||||
if (isOnRightSide == false) {
|
||||
onWrongSide = p.getDistance(crossingInfo.getA());
|
||||
}
|
||||
}
|
||||
}
|
||||
final double result;
|
||||
if (isOnRightSide == true) {
|
||||
result = 1;
|
||||
} else {
|
||||
//TODO There should be a constant (either the 1.5 or the -0.2) which controls how strict the the curve is.
|
||||
result = (1-PENALTY_FOR_WRONG_SIDE) * Math.pow(1.5, -0.2 * onWrongSide.getMeters()) + PENALTY_FOR_WRONG_SIDE;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a candidate passes a waypoint in the right direction. Can only be applied to XTE-Candidates.
|
||||
* For marks passed on port, the cross-track error should switch from positive to negative and vice versa. Lines are
|
||||
* also from positive to negative as the cross-track error to a line is always positive when approaching it from the
|
||||
* correct side.
|
||||
* Determines whether a candidate passes a waypoint in the right direction. A probability of 1 is assigned in case
|
||||
* it does. Otherwise, depending on the passing instructions, {@link #PENALTY_FOR_WRONG_DIRECTION} or
|
||||
* {@link #PENALTY_FOR_LINE_PASSED_IN_WRONG_DIRECTION} is returned as a "probability penalty" for passing the
|
||||
* waypoint in the wrong direction. The penalty is worse if {@link PassingInstruction#Line lines} are passed in the
|
||||
* wrong direction. Can only be applied to XTE-Candidates. For marks passed on port, the cross-track error should
|
||||
* switch from positive to negative and vice versa. Lines are also from positive to negative as the cross-track
|
||||
* error to a line is always positive when approaching it from the correct side.
|
||||
*
|
||||
* @return {@code null} in case the passing instructions aren't defined and therefore the correct direction is not
|
||||
* known
|
||||
*/
|
||||
private boolean passesInTheRightDirection(Waypoint w, double xte1, double xte2, boolean portMark) {
|
||||
boolean result = true;
|
||||
private Double passesInTheRightDirection(Waypoint w, double xte1, double xte2, boolean portMark) {
|
||||
final Double result;
|
||||
PassingInstruction instruction = getPassingInstructions(w);
|
||||
if (instruction == PassingInstruction.Port || instruction == PassingInstruction.Line
|
||||
if (instruction == PassingInstruction.Single_Unknown) {
|
||||
result = null;
|
||||
} else if (instruction == PassingInstruction.Port
|
||||
|| (instruction == PassingInstruction.Gate && portMark)) {
|
||||
result = xte1 > xte2 ? true : false;
|
||||
result = xte1 > xte2 ? 1 : PENALTY_FOR_WRONG_DIRECTION;
|
||||
} else if (instruction == PassingInstruction.Starboard || (instruction == PassingInstruction.Gate && !portMark)) {
|
||||
result = xte1 < xte2 ? true : false;
|
||||
result = xte1 < xte2 ? 1 : PENALTY_FOR_WRONG_DIRECTION;
|
||||
} else if (instruction == PassingInstruction.Line) {
|
||||
result = xte1 > xte2 ? 1 : PENALTY_FOR_LINE_PASSED_IN_WRONG_DIRECTION;
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -698,19 +999,21 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
* @return a probability based on the distance to <code>w</code> and the average leg lengths before and after.
|
||||
*/
|
||||
private Double getDistanceBasedProbability(Waypoint w, TimePoint t, Distance distance) {
|
||||
Distance legLength = getLegLength(t, w);
|
||||
final Double result;
|
||||
Distance legLength = getAverageLengthOfAdjacentLegs(t, w);
|
||||
if (legLength != null) {
|
||||
double result = 1 / (strictnessOfDistanceBasedProbability/* Raising this will make it stricter */
|
||||
result = 1 / (STRICTNESS_OF_DISTANCE_BASED_PROBABILITY/* Raising this will make it stricter */
|
||||
* Math.abs(distance.getMeters() / legLength.getMeters()) + 1);
|
||||
return result;
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
return null;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an average of the estimated legs before and after <code>w</code>.
|
||||
*/
|
||||
private Distance getLegLength(TimePoint t, Waypoint w) {
|
||||
private Distance getAverageLengthOfAdjacentLegs(TimePoint t, Waypoint w) {
|
||||
Course course = race.getRace().getCourse();
|
||||
if (w == course.getFirstWaypoint()) {
|
||||
return race.getTrackedLegStartingAt(w).getGreatCircleDistance(t);
|
||||
@@ -730,11 +1033,12 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
* Calculates the distance from p to each mark of w at the timepoint t.
|
||||
*/
|
||||
private List<Distance> calculateDistance(Position p, Waypoint w, TimePoint t) {
|
||||
List<Distance> distances = new ArrayList<>();
|
||||
final List<Distance> distances = new ArrayList<>();
|
||||
PassingInstruction instruction = getPassingInstructions(w);
|
||||
boolean singleMark = false;
|
||||
switch (instruction) {
|
||||
case Port:
|
||||
case Single_Unknown:
|
||||
case Starboard:
|
||||
case FixedBearing:
|
||||
singleMark = true;
|
||||
@@ -760,7 +1064,7 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
Position portLinePosition = race.getOrCreateTrack(posLine.getA()).getEstimatedPosition(t, false);
|
||||
Position starboardLinePosition = race.getOrCreateTrack(posLine.getB()).getEstimatedPosition(t, false);
|
||||
distances.add((portLinePosition != null && starboardLinePosition != null) ? p.getDistanceToLine(
|
||||
portLinePosition, starboardLinePosition) : null);
|
||||
portLinePosition, starboardLinePosition).abs() : null);
|
||||
}
|
||||
break;
|
||||
case Offset:
|
||||
|
||||
+2
-1
@@ -38,7 +38,8 @@ public class CandidateImpl implements Candidate {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Candidate for waypoint " + getOneBasedIndexOfWaypoint() + " with cost " + getProbability() + " and Timepoint " + p;
|
||||
return "Candidate for waypoint " + getOneBasedIndexOfWaypoint() + " with probability " +
|
||||
getProbability() + " and timepoint " + getTimePoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.sap.sailing.domain.markpassingcalculation.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
public abstract class CandidateWithSide extends CandidateImpl {
|
||||
private final double onCorrectSideOfWaypoint;
|
||||
private final Double startProbabilityBasedOnOtherCompetitors;
|
||||
|
||||
public CandidateWithSide(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Double startProbabilityBasedOnOtherCompetitors,
|
||||
Waypoint w, double onCorrectSideOfWaypoint) {
|
||||
super(oneBasedIndexOfWaypoint, p, probability, w);
|
||||
this.startProbabilityBasedOnOtherCompetitors = startProbabilityBasedOnOtherCompetitors;
|
||||
this.onCorrectSideOfWaypoint = onCorrectSideOfWaypoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ""+getClass().getSimpleName()+" "+super.toString()+", onCorrectSideOfWaypoint: "+onCorrectSideOfWaypoint+
|
||||
(startProbabilityBasedOnOtherCompetitors==null?"":", startProbabilityBasedOnOtherCompetitors="+startProbabilityBasedOnOtherCompetitors);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.sap.sailing.domain.markpassingcalculation.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
public class DistanceCandidateImpl extends CandidateWithSide {
|
||||
private final Distance distance;
|
||||
|
||||
public DistanceCandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Double startProbabilityBasedOnOtherCompetitors,
|
||||
Waypoint w, double onCorrectSideOfWaypoint, Distance distance) {
|
||||
super(oneBasedIndexOfWaypoint, p, probability, startProbabilityBasedOnOtherCompetitors, w, onCorrectSideOfWaypoint);
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString()+", distance: "+distance;
|
||||
}
|
||||
}
|
||||
+38
-18
@@ -4,7 +4,7 @@ import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.markpassingcalculation.Candidate;
|
||||
|
||||
/**
|
||||
* Represent the passage between two {@link Candidate}s, <code>start</code> and <code>end</code>. {@link #getCost()}
|
||||
* Represent the passage between two {@link Candidate}s, <code>start</code> and <code>end</code>. {@link #getProbability()}
|
||||
* returns the probability of this passage being correct, consisting of the probability of each Candidate, the
|
||||
* probability of the passage between them (an estimate whether the distance sailed between the candidates matches the
|
||||
* distance between their {@link Waypoint}s), and a penalty for skipping waypoints, which might happen if a tracker
|
||||
@@ -13,14 +13,30 @@ import com.sap.sailing.domain.markpassingcalculation.Candidate;
|
||||
* @author Nicolas Klose
|
||||
*
|
||||
*/
|
||||
|
||||
public class Edge implements Comparable<Edge> {
|
||||
private final Candidate start;
|
||||
private final Candidate end;
|
||||
// TODO what is the meaning of this constant?
|
||||
private final static double penaltyForSkipped = 0.7;
|
||||
// TODO what is the meaning of this constant?
|
||||
private final static double penaltyForSkippedToEnd = 0.6;
|
||||
|
||||
/**
|
||||
* The penalty for an edge's probability in case the edge skips one or more waypoints and does not
|
||||
* end at the end proxy node. Skipping a waypoint that is not the last means that either the waypoint
|
||||
* was passed in a very strange, unrecognized way or we got a tracker outage. This is both pretty
|
||||
* unlikely and is penalized by this factor being multiplied to the edge's general probability.<p>
|
||||
*
|
||||
* This factor is raised to the n-th power for n waypoints skipped.
|
||||
*/
|
||||
private final static double PENALTY_FOR_SKIPPED = 0.1;
|
||||
|
||||
/**
|
||||
* Similar to {@link #PENALTY_FOR_SKIPPED}, but applied to edges that skip to the end proxy node. Such skips
|
||||
* are regular business while the race is still going on, and paths that are otherwise likely but don't lead
|
||||
* up to the end shall be possible and therefore must not be penalized harshly, receiving less of a penalty
|
||||
* (greater factor).<p>
|
||||
*
|
||||
* As with {@link #PENALTY_FOR_SKIPPED}, this factor is raised to the n-th power for n waypoints skipped.
|
||||
*/
|
||||
private final static double PENALTY_FOR_SKIPPED_TO_END = 0.5;
|
||||
|
||||
private final double estimatedDistanceAndStartTimingProbability;
|
||||
private final int numberOfWaypoints;
|
||||
|
||||
@@ -32,20 +48,22 @@ public class Edge implements Comparable<Edge> {
|
||||
}
|
||||
|
||||
public static double getPenaltyForSkipping() {
|
||||
return penaltyForSkipped;
|
||||
return PENALTY_FOR_SKIPPED;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cost for skipping a waypoint is 2*{@link #penaltyForSkipped} but is reduced to {@link #penaltyForSkippedToEnd} when
|
||||
* skipping to the end. The reason for preferring skips to the end proxy node is that in live situations where the course
|
||||
* hasn't been completed yet it is required to skip to the end. Additional cost comes from the inverse probability of
|
||||
* the product of the start node, end node and distance-based probabilities. If these probabilities multiply to zero,
|
||||
* an additional cost of 1.0 is added to any skip penalty.
|
||||
* The probability-reducing factor for skipping a waypoint is {@link #PENALTY_FOR_SKIPPED} but is reduced to
|
||||
* {@link #PENALTY_FOR_SKIPPED_TO_END} when skipping to the end. The reason for preferring skips to the end proxy node
|
||||
* is that in live situations where the course hasn't been completed yet it is required to skip to the end. The main
|
||||
* probability comes from the probability of the product of the start node, end node and distance-based
|
||||
* probabilities.
|
||||
*/
|
||||
public Double getCost() {
|
||||
double penalty = end.getOneBasedIndexOfWaypoint() == numberOfWaypoints + 1 ? penaltyForSkippedToEnd : penaltyForSkipped;
|
||||
return 1 - (start.getProbability() * end.getProbability() * estimatedDistanceAndStartTimingProbability) + 2 * penalty
|
||||
* (end.getOneBasedIndexOfWaypoint() - start.getOneBasedIndexOfWaypoint() - 1);
|
||||
public Double getProbability() {
|
||||
final double penalty = end.getOneBasedIndexOfWaypoint() == numberOfWaypoints + 1 ? PENALTY_FOR_SKIPPED_TO_END : PENALTY_FOR_SKIPPED;
|
||||
// See bug 3241 comment #38: only use the edge's end candidate's probability; the start candidate's probability is the
|
||||
// previous edge's end probability. The only probability we'll miss this way is that of the start proxy node and that is always 1.
|
||||
return end.getProbability() * estimatedDistanceAndStartTimingProbability *
|
||||
Math.pow(penalty, (end.getOneBasedIndexOfWaypoint() - start.getOneBasedIndexOfWaypoint() - 1));
|
||||
}
|
||||
|
||||
public Candidate getStart() {
|
||||
@@ -57,11 +75,13 @@ public class Edge implements Comparable<Edge> {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Edge from Waypoint " + start.getOneBasedIndexOfWaypoint() + " to " + end.getOneBasedIndexOfWaypoint() + ": " + getCost();
|
||||
return "Edge from Waypoint " + start.getOneBasedIndexOfWaypoint() + " (" +
|
||||
start.getTimePoint()+") to " + end.getOneBasedIndexOfWaypoint() + " (" +
|
||||
end.getTimePoint()+"): " + getProbability();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Edge o) {
|
||||
return start != o.getStart() ? start.compareTo(o.getStart()) : end != o.getEnd() ? end.compareTo(o.getEnd()) : getCost().compareTo(o.getCost());
|
||||
return start != o.getStart() ? start.compareTo(o.getStart()) : end != o.getEnd() ? end.compareTo(o.getEnd()) : getProbability().compareTo(o.getProbability());
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.sap.sailing.domain.markpassingcalculation.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
public class XTECandidateImpl extends CandidateWithSide {
|
||||
private final Double passesInTheRightDirectionProbability;
|
||||
|
||||
public XTECandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability,
|
||||
Double startProbabilityBasedOnOtherCompetitors, Waypoint w, double onCorrectSideOfWaypoint,
|
||||
Double passesInTheRightDirectionProbability) {
|
||||
super(oneBasedIndexOfWaypoint, p, probability, startProbabilityBasedOnOtherCompetitors, w, onCorrectSideOfWaypoint);
|
||||
this.passesInTheRightDirectionProbability = passesInTheRightDirectionProbability;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString()+", passesInTheRightDirection: "+passesInTheRightDirectionProbability;
|
||||
}
|
||||
}
|
||||
+5
@@ -49,4 +49,9 @@ public class FlexibleLeaderboardAsRegattaLikeIdentifier implements RegattaLikeId
|
||||
public String getIdentifierType() {
|
||||
return FlexibleLeaderboard.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName()+": leaderboardName="+getName();
|
||||
}
|
||||
}
|
||||
+5
@@ -49,4 +49,9 @@ public class RegattaAsRegattaLikeIdentifier implements RegattaLikeIdentifier {
|
||||
public String getIdentifierType() {
|
||||
return Regatta.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName()+": regattaName="+getName();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,25 @@ import com.sap.sse.common.Timed;
|
||||
|
||||
public interface DynamicTrack<FixType extends Timed> extends Track<FixType> {
|
||||
/**
|
||||
* Tries to add {@code fix} to this track. If an {@link #equals(Object) equal} fix already exists
|
||||
* in this track, the track remains unchanged and {@code false} will be returned.
|
||||
*
|
||||
* @return <code>true</code> if the element was added, <code>false</code> otherwise.
|
||||
*/
|
||||
boolean add(FixType fix);
|
||||
|
||||
/**
|
||||
* Tries to add {@code fix} to this track. If an {@link #equals(Object) equal} fix already exists
|
||||
* in this track and {@code replace} is {@code false}, the track remains unchanged and {@code false} will be returned.
|
||||
* If {@code replace} is {@code true}, an equal (by definition of the comparator used for the fixes set) fix will
|
||||
* replace the fix contained in this track.
|
||||
*
|
||||
* @param replace
|
||||
* whether or not to replace an existing fix in the track that is equal to {@link #fix} as defined by the
|
||||
* comparator used for the {@link #fixes} set. By default this is a comparator only comparing the fixes'
|
||||
* time stamps. Subclasses may use different comparator implementations.
|
||||
*
|
||||
* @return <code>true</code> if the element was added, <code>false</code> otherwise.
|
||||
*/
|
||||
boolean add(FixType fix, boolean replace);
|
||||
}
|
||||
@@ -101,6 +101,14 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
|
||||
* If no start time can be determined this way, <code>null</code> is returned.
|
||||
*/
|
||||
TimePoint getStartOfRace();
|
||||
|
||||
/**
|
||||
* Like {@link #getStartOfRace()}, but ignoring any inference from start mark passings in case {@code inferred} is
|
||||
* {@code false}. In this case, if no official start time was set, e.g., in the {@link RaceLog} or explicitly using
|
||||
* {@link #getStartTimeReceived()}, {@code null} will be returned by this method even if start mark passings are
|
||||
* present.
|
||||
*/
|
||||
TimePoint getStartOfRace(boolean inferred);
|
||||
|
||||
/**
|
||||
* Determine the race end time is tricky. Boats may sink, stop, not finish, although they started the race. We
|
||||
|
||||
+8
-1
@@ -201,6 +201,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
|
||||
}
|
||||
}, CrossTrackErrorCache.class.getSimpleName()+" for race "+owner.getRace().getName());
|
||||
this.owner = owner;
|
||||
triggerUpdateForAllRaceCompetitors();
|
||||
owner.addListener(this);
|
||||
}
|
||||
|
||||
@@ -480,7 +481,13 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
|
||||
|
||||
public void resume() {
|
||||
owner.addListener(this);
|
||||
invalidate();
|
||||
triggerUpdateForAllRaceCompetitors();
|
||||
cachePerCompetitor.resume();
|
||||
}
|
||||
|
||||
private void triggerUpdateForAllRaceCompetitors() {
|
||||
for (Competitor competitor : owner.getRace().getCompetitors()) {
|
||||
cachePerCompetitor.triggerUpdate(competitor, null /* meaning: from the beginning of time */);
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -47,12 +47,17 @@ public class DynamicGPSFixMovingTrackImpl<ItemType> extends GPSFixTrackImpl<Item
|
||||
*/
|
||||
@Override
|
||||
public void addGPSFix(GPSFixMoving gpsFix) {
|
||||
add(gpsFix);
|
||||
add(gpsFix, /* replace */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(GPSFixMoving fix) {
|
||||
return super.add(new CompactGPSFixMovingImpl(fix));
|
||||
return super.add(fix); // ends up calling this.add(fix, false) where conversion in CompactGPSFixMovingImpl will happen
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(GPSFixMoving fix, boolean replace) {
|
||||
return super.add(new CompactGPSFixMovingImpl(fix), replace);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+7
-2
@@ -12,12 +12,17 @@ public class DynamicGPSFixTrackImpl<ItemType> extends GPSFixTrackImpl<ItemType,
|
||||
}
|
||||
|
||||
public void addGPSFix(GPSFix gpsFix) {
|
||||
add(gpsFix);
|
||||
add(gpsFix, /* replace */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(GPSFix fix) {
|
||||
return super.add(new CompactGPSFixImpl(fix));
|
||||
return super.add(fix); // ends up calling this.add(fix, false) where conversion in CompactGPSFixImpl will happen
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(GPSFix fix, boolean replace) {
|
||||
return super.add(new CompactGPSFixImpl(fix), replace);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
@@ -17,4 +17,9 @@ public class DynamicTrackImpl<FixType extends Timed> extends TrackImpl<FixType>
|
||||
public boolean add(FixType fix) {
|
||||
return super.add(fix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(FixType fix, boolean replace) {
|
||||
return super.add(fix, replace);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -575,7 +575,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
neededToCreateOrUpdateFinishMarkPassing = true;
|
||||
} else {
|
||||
finishMarkPassingToUse = originalMarkPassing.getOriginal();
|
||||
if (copyOfMarkPassings != originalMarkPassing) {
|
||||
if (finishMarkPassingToUse != originalMarkPassing) {
|
||||
logger.info(getRace().getName()+": Reverting race log-based finish mark passing "+originalMarkPassing+" to "+finishMarkPassingToUse+
|
||||
" because no finishing time found anymore for that competitor in race log");
|
||||
neededToCreateOrUpdateFinishMarkPassing = true;
|
||||
|
||||
+2
-2
@@ -1100,13 +1100,13 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean add(FixType fix) {
|
||||
protected boolean add(FixType fix, boolean replace) {
|
||||
final boolean result;
|
||||
final boolean firstFixInTrack;
|
||||
lockForWrite();
|
||||
try {
|
||||
firstFixInTrack = getRawFixes().isEmpty();
|
||||
result = addWithoutLocking(fix);
|
||||
result = addWithoutLocking(fix, replace);
|
||||
if (!validityCachingSuspended) {
|
||||
invalidateValidityAndEstimatedSpeedAndDistanceCaches(fix);
|
||||
}
|
||||
|
||||
+63
-22
@@ -202,7 +202,14 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
* The calculated race start time
|
||||
*/
|
||||
private TimePoint startTime;
|
||||
|
||||
|
||||
/**
|
||||
* Maintained in lock-step with {@link #startTime}, only that {@code null} will be contained if {@link #startTime}
|
||||
* was only inferred from start mark passings and no other, more official, information such as
|
||||
* {@link #getStartTimeReceived()} or a start time coming from an attached {@link RaceLog}.
|
||||
*/
|
||||
private TimePoint startTimeWithoutInferenceFromStartMarkPassings;
|
||||
|
||||
/**
|
||||
* The calculated race end time
|
||||
*/
|
||||
@@ -423,7 +430,6 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
this.startToNextMarkCacheInvalidationListeners = new ConcurrentHashMap<Mark, TrackedRaceImpl.StartToNextMarkCacheInvalidationListener>();
|
||||
this.maneuverCache = createManeuverCache();
|
||||
this.markTracks = new ConcurrentHashMap<Mark, GPSFixTrack<Mark, GPSFix>>();
|
||||
this.crossTrackErrorCache = new CrossTrackErrorCache(this);
|
||||
this.gpsFixStore = gpsFixStore;
|
||||
int i = 0;
|
||||
for (Waypoint waypoint : race.getCourse().getWaypoints()) {
|
||||
@@ -444,7 +450,6 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
getOrCreateTrack(mark);
|
||||
}
|
||||
}
|
||||
|
||||
trackedLegs = new LinkedHashMap<Leg, TrackedLeg>();
|
||||
race.getCourse().lockForRead();
|
||||
try {
|
||||
@@ -469,6 +474,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
MarkPassingsByTimeAndCompetitorIdComparator.INSTANCE));
|
||||
}
|
||||
markPassingsTimes = new ArrayList<com.sap.sse.common.Util.Pair<Waypoint, com.sap.sse.common.Util.Pair<TimePoint, TimePoint>>>();
|
||||
this.crossTrackErrorCache = new CrossTrackErrorCache(this);
|
||||
loadingFromWindStoreState = LoadingFromStoresState.NOT_STARTED;
|
||||
// When this tracked race is to be serialized, wait for the loading from stores to complete.
|
||||
new Thread("Mongo wind loader for tracked race " + getRace().getName()) {
|
||||
@@ -732,6 +738,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
|
||||
public void invalidateStartTime() {
|
||||
startTime = null;
|
||||
startTimeWithoutInferenceFromStartMarkPassings = null;
|
||||
}
|
||||
|
||||
public void invalidateEndTime() {
|
||||
@@ -771,13 +778,26 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
*/
|
||||
@Override
|
||||
public TimePoint getStartOfRace() {
|
||||
if (startTime == null) {
|
||||
return getStartOfRace(/* inferred */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimePoint getStartOfRace(boolean inferred) {
|
||||
final TimePoint result;
|
||||
// check if we have in our caching fields what is asked:
|
||||
if (inferred && startTime != null) {
|
||||
result = startTime;
|
||||
} else if (!inferred && startTimeWithoutInferenceFromStartMarkPassings != null) {
|
||||
result = startTimeWithoutInferenceFromStartMarkPassings;
|
||||
} else {
|
||||
// no cache match; re-calculate
|
||||
for (RaceLog raceLog : attachedRaceLogs.values()) {
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.finest("Analyzing race log "+raceLog+" for race "+this.getRace().getName());
|
||||
}
|
||||
startTime = new StartTimeFinder(raceLogResolver, raceLog).analyze().getStartTime();
|
||||
if (startTime != null) {
|
||||
startTimeWithoutInferenceFromStartMarkPassings = startTime;
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
logger.finest("Found start time "+startTime+" in race log "+raceLog+" for race "+this.getRace().getName());
|
||||
}
|
||||
@@ -789,12 +809,16 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
logger.finest("No start time found in race logs for race "+getRace().getName());
|
||||
}
|
||||
startTime = getStartTimeReceived();
|
||||
if (startTime != null) {
|
||||
startTimeWithoutInferenceFromStartMarkPassings = startTime;
|
||||
}
|
||||
// If not null, check if the first mark passing for the start line is too much after the
|
||||
// startTimeReceived; if so, return an adjusted, later start time.
|
||||
// If no official start time was received, try to estimate the start time using the mark passings for
|
||||
// the start line.
|
||||
final Waypoint firstWaypoint;
|
||||
if (getTrackedRegatta().getRegatta().useStartTimeInference() && (firstWaypoint = getRace().getCourse().getFirstWaypoint()) != null) {
|
||||
// in this "if" branch update only startTime, not startTimeWithoutInferenceFromStartMarkPassings
|
||||
if (startTimeReceived != null) {
|
||||
// plausibility check for start time received, based on start mark passings; if no boat started within
|
||||
// a grace period of MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS after the start time
|
||||
@@ -828,8 +852,13 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inferred) {
|
||||
result = startTime;
|
||||
} else {
|
||||
result = startTimeWithoutInferenceFromStartMarkPassings;
|
||||
}
|
||||
}
|
||||
return startTime;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1498,7 +1527,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
return getOrCreateTrack(mark, false);
|
||||
}
|
||||
|
||||
private GPSFixTrack<Mark, GPSFix> getOrCreateTrack(Mark mark, boolean createIfNotExistent){
|
||||
private GPSFixTrack<Mark, GPSFix> getOrCreateTrack(Mark mark, boolean createIfNotExistent) {
|
||||
GPSFixTrack<Mark, GPSFix> result = markTracks.get(mark);
|
||||
if (result == null) {
|
||||
// try again, this time with more expensive synchronization
|
||||
@@ -3248,22 +3277,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
logger.info("Finished loading competitor tracks for " + getRace().getName());
|
||||
logger.info("Started loading mark tracks for " + getRace().getName());
|
||||
for (Mark mark : getMarksFromRegattaLogs()) {
|
||||
try {
|
||||
gpsFixStore.loadMarkTrack((DynamicGPSFixTrack<Mark, GPSFix>) getOrCreateTrack(mark),
|
||||
log, mark, startOfTimeWindowToLoad, endOfTimeWindowToLoad);
|
||||
if (getOrCreateTrack(mark).getFirstRawFix() == null) {
|
||||
logger.fine("Loading mark positions from outside of start/end of tracking interval ("+
|
||||
startOfTimeWindowToLoad+".."+endOfTimeWindowToLoad+
|
||||
") because no fixes were found in that interval");
|
||||
// got an empty track for the mark; try again without constraining the mapping interval
|
||||
// by start/end of tracking to at least attempt to get fixes at all in case there were any
|
||||
// within the device mapping interval specified
|
||||
gpsFixStore.loadMarkTrack((DynamicGPSFixTrack<Mark, GPSFix>) getOrCreateTrack(mark),
|
||||
log, mark, /* startOfTimeWindowToLoad */ null, /* endOfTimeWindowToLoad */ null);
|
||||
}
|
||||
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
|
||||
logger.log(Level.WARNING, "Could not load track for " + mark, e);
|
||||
}
|
||||
final DynamicGPSFixTrack<Mark, GPSFix> markTrack = (DynamicGPSFixTrack<Mark, GPSFix>) getOrCreateTrack(mark);
|
||||
loadMarkTrack(log, mark, markTrack, startOfTimeWindowToLoad, endOfTimeWindowToLoad);
|
||||
}
|
||||
logger.info("Finished loading mark tracks for " + getRace().getName());
|
||||
} finally {
|
||||
@@ -3287,6 +3302,32 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the GPS fixes for a mark from the {@link #gpsFixStore} and adds them to {@code intoTrack}. The device
|
||||
* mappings are expected to be found in {@code logWithDeviceMappings}. The fixes added have to be within
|
||||
* {@code from} and {@code to}, inclusively, with one exception: if no fix is found within this interval at all, all
|
||||
* fixes found for the mark are loaded to make sure that any position data that may give a hint for the interval
|
||||
* requests is loaded.
|
||||
*/
|
||||
private void loadMarkTrack(final RegattaLog logWithDeviceMappings, Mark mark,
|
||||
final DynamicGPSFixTrack<Mark, GPSFix> intoTrack, final TimePoint from, final TimePoint to) {
|
||||
try {
|
||||
gpsFixStore.loadMarkTrack(intoTrack, logWithDeviceMappings, mark, from, to);
|
||||
if (intoTrack.getFirstRawFix() == null) {
|
||||
logger.fine("Loading mark positions from outside of start/end of tracking interval ("+
|
||||
from+".."+to+
|
||||
") because no fixes were found in that interval");
|
||||
// got an empty track for the mark; try again without constraining the mapping interval
|
||||
// by start/end of tracking to at least attempt to get fixes at all in case there were any
|
||||
// within the device mapping interval specified
|
||||
gpsFixStore.loadMarkTrack(intoTrack,
|
||||
logWithDeviceMappings, mark, /* startOfTimeWindowToLoad */ null, /* endOfTimeWindowToLoad */ null);
|
||||
}
|
||||
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
|
||||
logger.log(Level.WARNING, "Could not load track for " + mark, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if currently the race is loading GPS fixes from the {@link GPSFixStore}. Clients may {@link Object#wait()} on <code>this</code>
|
||||
* object and will be notified whenever a change of this flag's value occurs.
|
||||
|
||||
Loaded 100 of 271 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user